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.
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.
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.
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.
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With