web-dev-qa-db-fra.com

android.graphics trace une ligne d'une vue pointant vers une autre vue

Je sais que Android.graphics est vieux, mais j'ai du mal à faire des choses simples.

Je veux dessiner une animation de ligne où une View pointe une flèche/ligne dans une autre View

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

J'ai essayé de créer une classe View personnalisée, de remplacer la méthode onDraw(Canvas c), puis d'utiliser la méthode drawLine(startX, startY, stopX, stopY, Paint) à partir de l'objet Canvas. Mais je ne sais pas quelles coordonnées obtenir pour pointer une View à l'autre View

Je ne veux pas créer une View statique dans la présentation XML avec une hauteur réduite, car la View peut être ajoutée dynamiquement par l'utilisateur, ce qui, à mon avis, est de tracer la ligne de manière dynamique.

Sil te plait aide moi. Je vous remercie!

10
GGWP

Pour tracer des lignes entre les vues, il est préférable de placer toutes les lignes sur la même disposition parent. Pour les conditions de la question (Second Button est exactement à droite de First Button), vous pouvez utiliser une présentation personnalisée comme celle-ci:

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;
    }
}

avec la disposition .xml comme:

<?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>

et MainActivity.Java comme:

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);
            }
        });
    }
}

vous avez quelque chose comme ça (sur First Button clic):

 [Arrow to View animated

Pour les autres cas (Second Button est exactement à gauche (ou au-dessus ou au-dessous) ou plus complexe au-dessus de droite/en dessous à gauche etc. de First Button), vous devez modifier une partie pour calculer les points de début et de fin de flèche:

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;
}
2
Andrii Omelchenko

Utilisez Path et Pathmeasure pour dessiner une ligne animée. J'ai fait et tester. 

Créez une vue personnalisée et transmettez-lui un tableau de points de coordonnées, 

public class AnimatedLine extends View {
    private final Paint mPaint;
    public Canvas mCanvas;
    AnimationListener animationListener;

    Path path;
    private static long animSpeedInMs = 2000;
    private static final long animMsBetweenStrokes = 100;
    private long animLastUpdate;
    private boolean animRunning = true;
    private int animCurrentCountour;
    private float animCurrentPos;
    private Path animPath;
    private PathMeasure animPathMeasure;

    float pathLength;


    float distance = 0;
    float[] pos;
    float[] tan;
    Matrix matrix;
    Bitmap bm;


    public AnimatedLine(Context context) {
        this(context, null);
        mCanvas = new Canvas();
    }

    public AnimatedLine(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(15);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setColor(context.getResources().getColor(R.color.materialcolorpicker__red));


        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KitKat) {
            setLayerType(LAYER_TYPE_SOFTWARE, mPaint);
        }
        bm = BitmapFactory.decodeResource(getResources(), R.drawable.hand1);
        bm = Bitmap.createScaledBitmap(bm, 20,20, false);
        distance = 0;
        pos = new float[2];
        tan = new float[2];

        matrix = new Matrix();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mCanvas = canvas;

        if (path != null) {

            if (animRunning) {
                drawAnimation(mCanvas);
            } else {
                drawStatic(mCanvas);
            }

        }

    }


    /**
     * draw Path With Animation
     *
     * @param time in milliseconds
     */
    public void drawWithAnimation(ArrayList<PointF> points, long time,AnimationListener animationListener) {
        animRunning = true;
        animPathMeasure = null;
        animSpeedInMs = time;
        setPath(points);
        setAnimationListener(animationListener);
        invalidate();
    }

    public void setPath(ArrayList<PointF> points) {
        if (points.size() < 2) {
            throw new IllegalStateException("Pass atleast two points.");
        }
        path = new Path();
        path.moveTo(points.get(0).x, points.get(0).y);
        path.lineTo(points.get(1).x, points.get(1).y);
    }

    private void drawAnimation(Canvas canvas) {
        if (animPathMeasure == null) {
            // Start of animation. Set it up.
            animationListener.onAnimationStarted();
            animPathMeasure = new PathMeasure(path, false);
            animPathMeasure.nextContour();
            animPath = new Path();
            animLastUpdate = System.currentTimeMillis();
            animCurrentCountour = 0;
            animCurrentPos = 0.0f;

            pathLength = animPathMeasure.getLength();


        } else {
            // Get time since last frame
            long now = System.currentTimeMillis();
            long timeSinceLast = now - animLastUpdate;

            if (animCurrentPos == 0.0f) {
                timeSinceLast -= animMsBetweenStrokes;
            }

            if (timeSinceLast > 0) {
                // Get next segment of path
                float newPos = (float) (timeSinceLast) / (animSpeedInMs / pathLength) + animCurrentPos;
                boolean moveTo = (animCurrentPos == 0.0f);
                animPathMeasure.getSegment(animCurrentPos, newPos, animPath, moveTo);
                animCurrentPos = newPos;
                animLastUpdate = now;

                 //start draw bitmap along path
                animPathMeasure.getPosTan(newPos, pos, tan);
                matrix.reset();
                matrix.postTranslate(pos[0], pos[1]);
                canvas.drawBitmap(bm, matrix, null);
                //end drawing bitmap



                //take current position
                animationListener.onAnimationUpdate(pos);

                // If this stroke is done, move on to next
                if (newPos > pathLength) {
                    animCurrentPos = 0.0f;
                    animCurrentCountour++;
                    boolean more = animPathMeasure.nextContour();
                    // Check if finished
                    if (!more) {
                        animationListener.onAnimationEnd();
                        animRunning = false;
                    }
                }
            }

            // Draw path
            canvas.drawPath(animPath, mPaint);

        }

        invalidate();
    }

    private void drawStatic(Canvas canvas) {
        canvas.drawPath(path, mPaint);
        canvas.drawBitmap(bm, matrix, null);
    }


    public void setAnimationListener(AnimationListener animationListener) {
        this.animationListener = animationListener;
    }



    public interface AnimationListener {
        void onAnimationStarted();

        void onAnimationEnd();

        void onAnimationUpdate(float[] pos);
    }
}
2
Abhay Koradiya