Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animating the drawing of a canvas path on Android

I'd like to animate the drawing of a path, i.e. to have it progressively appear on the screen. I am using the canvas and my best guess so far is to use an ObjectAnimator to take care of the animation. However, I cannot figure out how to actually draw the corresponding segment of the path in the onDraw() method. Is there a method that would allow to do this? Would I need to involve path effects for that?

Edit: Using a DashPathEffect and setting its "on" and "off" intervals in the animation to cover the part of the path we want to draw for that step seems to work here, but it requires allocating a new DashPathEffect for every step of the animation. I will leave the question open in case there is a better way.

like image 467
Gnurou Avatar asked Oct 07 '12 13:10

Gnurou


People also ask

Can we animate in Android?

On Android 4.4 (API level 19) and higher, you can use the transition framework to create animations when you swap the layout within the current activity or fragment. All you need to do is specify the starting and ending layout, and what type of animation you want to use.

What is object animator in Android?

ObjectAnimator is a Subclass of ValueAnimator, which allows us to set a target object and object property to animate. It is an easy way to change the properties of a view with a specified duration. We can provide the end position and duration of the animation.


2 Answers

Answering my own question, as I figured out a satisfying way to do that.

The trick is to use an ObjectAnimator to progressively change the current length of the stroke, and a DashPathEffect to control the length of the current stroke. The DashPathEffect will have its dashes parameter initially set to the following:

float[] dashes = { 0.0f, Float.MAX_VALUE };

First float is the length of the visible stroke, second length of non-visible part. Second length is chosen to be extremely high. Initial settings thus correspond to a totally invisible stroke.

Then everytime the object animator updates the stroke length value, a new DashPathEffect is created with the new visible part and set to the Painter object, and the view is invalidated:

dashes[0] = newValue;
mPaint.setPathEffect(new DashPathEffect(dashes, 0));
invalidate();

Finally, the onDraw() method uses this painter to draw the path, which will only comprise the portion we want:

canvas.drawPath(path, mPaint);

The only drawback I see is that we must create a new DashPathEffect at every animation step (as they cannot be reused), but globally this is satisfying - the animation is nice and smooth.

like image 123
Gnurou Avatar answered Oct 10 '22 09:10

Gnurou


package com.nexoslav.dashlineanimatedcanvasdemo;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;

import androidx.annotation.Nullable;

public class CustomView extends View {
    float[] dashes = {30, 20};
    Paint mPaint;
    private Path mPath;

    private void init() {

        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(10f);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setPathEffect(new DashPathEffect(dashes, 0));

        mPath = new Path();
        mPath.moveTo(200, 200);
        mPath.lineTo(300, 100);
        mPath.lineTo(400, 400);
        mPath.lineTo(1000, 200);
        mPath.lineTo(1000, 1000);
        mPath.lineTo(200, 400);

        ValueAnimator animation = ValueAnimator.ofInt(0, 100);
        animation.setInterpolator(new LinearInterpolator());
        animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                Log.d("bla", "bla: " + valueAnimator.getAnimatedValue());
                mPaint.setPathEffect(new DashPathEffect(dashes, (Integer) valueAnimator.getAnimatedValue()));
                invalidate();
            }
        });
        animation.setDuration(1000);
        animation.setRepeatMode(ValueAnimator.RESTART);
        animation.setRepeatCount(ValueAnimator.INFINITE);
        animation.start();
    }

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


    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

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

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

        canvas.drawPath(mPath, mPaint);
    }
}
like image 30
uberchilly Avatar answered Oct 10 '22 09:10

uberchilly