android.graphics从一个视图指向另一个视图绘制一条线

时间:2018-08-01 02:14:41

标签: android android-animation android-view

我知道android.graphics很老,但是我在做简单的事情时遇到了麻烦。

  

我想画一个线动画,其中一个View指向一个箭头/线到另一个View

First Button --------------------------------> Second Button

我尝试创建自定义View类并覆盖onDraw(Canvas c)方法,然后使用drawLine(startX, startY, stopX, stopY, paint)对象中的Canvas方法。但我不知道要获得哪个坐标才能将一个View指向另一个View

我不想在XML布局中创建高度很小的静态View,因为View可以由用户动态添加,我认为动态绘制线条是最好的方式。

请帮帮我。谢谢!

2 个答案:

答案 0 :(得分:2)

使用路径和路径测量来绘制动画线。我已经制造并测试了它。

创建自定义视图并将视图坐标点数组传递给它,

top -H

答案 1 :(得分:2)

如果所有视图都位于同一父布局上,则可以更好地在视图之间绘制线条。对于问题的条件(Second Button恰好在First Button的右边),您可以使用如下自定义布局:

public class ArrowLayout extends RelativeLayout {

    public static final String PROPERTY_X = "PROPERTY_X";
    public static final String PROPERTY_Y = "PROPERTY_Y";

    private final static double ARROW_ANGLE = Math.PI / 6;
    private final static double ARROW_SIZE = 50;

    private Paint mPaint;

    private boolean mDrawArrow = false;
    private Point mPointFrom = new Point();   // current (during animation) arrow start point
    private Point mPointTo = new Point();     // current (during animation)  arrow end point

    public ArrowLayout(Context context) {
        super(context);
        init();
    }

    public ArrowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ArrowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public ArrowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        setWillNotDraw(false);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.BLUE);
        mPaint.setStrokeWidth(5);
    }

    @Override
    public void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.save();
        if (mDrawArrow) {
            drawArrowLines(mPointFrom, mPointTo, canvas);
        }
        canvas.restore();
    }

    private Point calcPointFrom(Rect fromViewBounds, Rect toViewBounds) {
        Point pointFrom = new Point();

        pointFrom.x = fromViewBounds.right;
        pointFrom.y = fromViewBounds.top + (fromViewBounds.bottom - fromViewBounds.top) / 2;

        return pointFrom;
    }


    private Point calcPointTo(Rect fromViewBounds, Rect toViewBounds) {
        Point pointTo = new Point();

        pointTo.x = toViewBounds.left;
        pointTo.y = toViewBounds.top + (toViewBounds.bottom - toViewBounds.top) / 2;

        return pointTo;
    }


    private void drawArrowLines(Point pointFrom, Point pointTo, Canvas canvas) {
        canvas.drawLine(pointFrom.x, pointFrom.y, pointTo.x, pointTo.y, mPaint);

        double angle = Math.atan2(pointTo.y - pointFrom.y, pointTo.x - pointFrom.x);

        int arrowX, arrowY;

        arrowX = (int) (pointTo.x - ARROW_SIZE * Math.cos(angle + ARROW_ANGLE));
        arrowY = (int) (pointTo.y - ARROW_SIZE * Math.sin(angle + ARROW_ANGLE));
        canvas.drawLine(pointTo.x, pointTo.y, arrowX, arrowY, mPaint);

        arrowX = (int) (pointTo.x - ARROW_SIZE * Math.cos(angle - ARROW_ANGLE));
        arrowY = (int) (pointTo.y - ARROW_SIZE * Math.sin(angle - ARROW_ANGLE));
        canvas.drawLine(pointTo.x, pointTo.y, arrowX, arrowY, mPaint);
    }

    public void animateArrows(int duration) {
        mDrawArrow = true;

        View fromView = getChildAt(0);
        View toView = getChildAt(1);

        // find from and to views bounds
        Rect fromViewBounds = new Rect();
        fromView.getDrawingRect(fromViewBounds);
        offsetDescendantRectToMyCoords(fromView, fromViewBounds);

        Rect toViewBounds = new Rect();
        toView.getDrawingRect(toViewBounds);
        offsetDescendantRectToMyCoords(toView, toViewBounds);

        // calculate arrow sbegin and end points
        Point pointFrom = calcPointFrom(fromViewBounds, toViewBounds);
        Point pointTo = calcPointTo(fromViewBounds, toViewBounds);

        ValueAnimator arrowAnimator = createArrowAnimator(pointFrom, pointTo, duration);
        arrowAnimator.start();
    }

    private ValueAnimator createArrowAnimator(Point pointFrom, Point pointTo, int duration) {

        final double angle = Math.atan2(pointTo.y - pointFrom.y, pointTo.x - pointFrom.x);

        mPointFrom.x = pointFrom.x;
        mPointFrom.y = pointFrom.y;

        int firstX = (int) (pointFrom.x + ARROW_SIZE * Math.cos(angle));
        int firstY = (int) (pointFrom.y + ARROW_SIZE * Math.sin(angle));

        PropertyValuesHolder propertyX = PropertyValuesHolder.ofInt(PROPERTY_X, firstX, pointTo.x);
        PropertyValuesHolder propertyY = PropertyValuesHolder.ofInt(PROPERTY_Y, firstY, pointTo.y);

        ValueAnimator animator = new ValueAnimator();
        animator.setValues(propertyX, propertyY);
        animator.setDuration(duration);
        // set other interpolator (if needed) here:
        animator.setInterpolator(new AccelerateDecelerateInterpolator());

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mPointTo.x = (int) valueAnimator.getAnimatedValue(PROPERTY_X);
                mPointTo.y = (int) valueAnimator.getAnimatedValue(PROPERTY_Y);

                invalidate();
            }
        });

        return animator;
    }
}

.xml的布局如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:id="@+id/layout_main"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

    <{YOUR_PACKAGE_NAME}.ArrowLayout
            android:id="@+id/arrow_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <Button
            android:id="@+id/first_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:text="First Button"/>

        <Button
            android:id="@+id/second_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:text="Second Button"/>

    </{YOUR_PACKAGE_NAME}.ArrowLayout>

</RelativeLayout>

MainActivity.java,例如:

public class MainActivity extends AppCompatActivity {

    private ArrowLayout mArrowLayout;
    private Button mFirstButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mArrowLayout = (ArrowLayout) findViewById(R.id.arrow_layout);

        mFirstButton = (Button) findViewById(R.id.first_button);
        mFirstButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mArrowLayout.animateArrows(1000);
            }
        });
    }
}

您得到了类似的内容(点击First Button

[Arrow to View animated

对于其他情况(Second Button恰好位于First Button的左侧(或上方或下方)或更复杂的右上/左下等),您应该修改用于计算箭头的部分起点和终点:

private Point calcPointFrom(Rect fromViewBounds, Rect toViewBounds) {
    Point pointFrom = new Point();

    //                                Second Button above
    //                                ----------+----------
    //                               |                     |
    //  Second Button tho the left   +     First Button    + Second Button tho the right
    //                               |                     |
    //                                ----------+----------
    //                                  Second Button below
    //
    //   + - is arrow start point position

    if (toViewBounds to the right of fromViewBounds){
        pointFrom.x = fromViewBounds.right;
        pointFrom.y = fromViewBounds.top + (fromViewBounds.bottom - fromViewBounds.top) / 2;
    } else if (toViewBounds to the left of fromViewBounds) {
        pointFrom.x = fromViewBounds.left;
        pointFrom.y = fromViewBounds.top + (fromViewBounds.bottom - fromViewBounds.top) / 2;
    } else if () {
        ...
    }

    return pointFrom;
}