How to Animate a Path on an Android Canvas

How to draw a path on an Android canvas with animation?

Try this code, I used it to draw a heartbeat using Path & Canvas:

public class TestActivity extends Activity {

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new HeartbeatView(this));

}

public static class HeartbeatView extends View {

private static Paint paint;
private int screenW, screenH;
private float X, Y;
private Path path;
private float initialScreenW;
private float initialX, plusX;
private float TX;
private boolean translate;
private int flash;
private Context context;

public HeartbeatView(Context context) {
super(context);

this.context=context;

paint = new Paint();
paint.setColor(Color.argb(0xff, 0x99, 0x00, 0x00));
paint.setStrokeWidth(10);
paint.setAntiAlias(true);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStyle(Paint.Style.STROKE);
paint.setShadowLayer(7, 0, 0, Color.RED);

path= new Path();
TX=0;
translate=false;

flash=0;

}

@Override
public void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);

screenW = w;
screenH = h;
X = 0;
Y = (screenH/2)+(screenH/4)+(screenH/10);

initialScreenW=screenW;
initialX=((screenW/2)+(screenW/4));
plusX=(screenW/24);

path.moveTo(X, Y);

}

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

//canvas.save();

flash+=1;
if(flash<10 || (flash>20 && flash<30))
{
paint.setStrokeWidth(16);
paint.setColor(Color.RED);
paint.setShadowLayer(12, 0, 0, Color.RED);
}
else
{
paint.setStrokeWidth(10);
paint.setColor(Color.argb(0xff, 0x99, 0x00, 0x00));
paint.setShadowLayer(7, 0, 0, Color.RED);
}

if(flash==100)
{
flash=0;
}

path.lineTo(X,Y);
canvas.translate(-TX, 0);
if(translate==true)
{
TX+=4;
}

if(X<initialX)
{
X+=8;
}
else
{
if(X<initialX+plusX)
{
X+=2;
Y-=8;
}
else
{
if(X<initialX+(plusX*2))
{
X+=2;
Y+=14;
}
else
{
if(X<initialX+(plusX*3))
{
X+=2;
Y-=12;
}
else
{
if(X<initialX+(plusX*4))
{
X+=2;
Y+=6;
}
else
{
if(X<initialScreenW)
{
X+=8;
}
else
{
translate=true;
initialX=initialX+initialScreenW;
}
}
}
}
}

}

canvas.drawPath(path, paint);

//canvas.restore();

invalidate();
}
}

}

It uses drawing a Path point by point with couple of effects using counters. You can take what you need and transfer it to SurfaceView which is more efficient.

How to animate a path on an Android Canvas

You can transform your canvas by time, i.e:

class MyView extends View {

int framesPerSecond = 60;
long animationDuration = 10000; // 10 seconds

Matrix matrix = new Matrix(); // transformation matrix

Path path = new Path(); // your path
Paint paint = new Paint(); // your paint

long startTime;

public MyView(Context context) {
super(context);

// start the animation:
this.startTime = System.currentTimeMillis();
this.postInvalidate();
}

@Override
protected void onDraw(Canvas canvas) {

long elapsedTime = System.currentTimeMillis() - startTime;

matrix.postRotate(30 * elapsedTime/1000); // rotate 30° every second
matrix.postTranslate(100 * elapsedTime/1000, 0); // move 100 pixels to the right
// other transformations...

canvas.concat(matrix); // call this before drawing on the canvas!!

canvas.drawPath(path, paint); // draw on canvas

if(elapsedTime < animationDuration)
this.postInvalidateDelayed( 1000 / framesPerSecond);
}

}

Animating the drawing of a canvas path on Android

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.

How to do Canvas Path animation?

you need create class extend view and draw in by animationValue.

public class XXX extends View {

//...

int valueAnimation;

void animationStart() {

ValueAnimator valueAnimatorLoading = ValueAnimator.ofInt(startValue, endValue);
valueAnimatorLoading.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
valueAnimation = (int) (Integer) valueAnimator.getAnimatedValue(); //factor for use in onDraw()
invalidate();// call onDraw
}
});
valueAnimatorLoading.start();

}

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

//draw in canvas by valueAnimation
}

}

Android move object along a path

You would need to move your circle a little bit each frame towards the next waypoint and detect once it gets there, then start moving toward the next. There is no built in system that I know of.

Animating a clipPath from onDraw

Ok, you got some problems here. The first, you shouldn't init Paint, Path in onDraw(), it should be initialized first and you can modify it later and you will get better performance. When you update clipAmount value, you need to call postInvalidateOnAnimation() to make your BlackGraph view to draw again, this will trigger the method onDraw(). The last thing is clipPath need to call reset() to clear before add new path with the method addRect().

class BlackGraph(context: Context) : View(context) {

var clipAmount:Float = 0.0f
val paint = Paint().apply {
style = Paint.Style.FILL
color = Color.parseColor("#000000")
}
val path = Path()
val clipPath = Path()

override fun onDraw(canvas: Canvas) {
clipPath.apply {
reset()
addRect(clipAmount, 0f, width.toFloat(), height.toFloat(), Path.Direction.CW)
}
canvas.clipPath(clipPath)

path.moveTo(0f, height-30.toFloat())
path.lineTo(width.toFloat(), 0f)
path.lineTo(width.toFloat(), height.toFloat())
path.lineTo(0f, height.toFloat())
path.lineTo(0f, 0f)

canvas.drawPath(path, paint)
}

fun animateClipAmount() {
val valueAnimator = ValueAnimator.ofFloat(0f, 450f)
valueAnimator.addUpdateListener {
val value = it.animatedValue as Float
clipAmount = value
println("Value -> $clipAmount")
postInvalidateOnAnimation()
}
valueAnimator.duration = 2000
valueAnimator.start()
}

}

In onCreate:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val layout1 = findViewById<android.support.constraint.ConstraintLayout>(R.id.layout1)

val blackGraph = BlackGraph(this)
layout1 .addView(blackGraph)
blackGraph.animateClipAmount()
}

}


Related Topics



Leave a reply



Submit