Brandspace
home
Hey there and welcome to Tokotoko Studio!
Check out our latest blog post:
Django tip: Debugging templates
Animated paths on Android
let's draw that line
Lately I've been working on an Android app for learning to write Japanese characters. When showing how they should be written I wanted to show an animation that draws the character correctly. However, it turns out that there is no ready to use solution for this, so I had to write my own, though it was quite easy to get working. Here's how to do it.
Creating a custom View
Let's assume you have a Path object that you want animated. For example, it could be a single line or a collection of lines.
The animation is done by continually calling invalidate
from the onDraw
method. So when we have drawn one frame, we request to draw another frame. Then we calculate the time since the last frame and fetch the corresponding part to draw.
Parts of a path can be retrieved using a PathMeasure. For each time the draw method is called, we use the PathMeasure to add the part of the path that corresponds to the time passed since the last draw. All in all, it's quite simple really. But since there were no good explanations examples out there, I thought I'd share this one.
Here's the full code for an animated path view:
public class AnimPathView extends View implements View.OnClickListener {
private Path path;
private Paint paint;
private static final long animSpeedInMs = 1000;
private static final long animMsBetweenStrokes = 200;
private long animLastUpdate;
private boolean animRunning;
private int animCurrentCountour;
private float animCurrentPos;
private Path animPath;
private PathMeasure animPathMeasure;
public AnimPathView(Context context, AttributeSet attrs) {
super(context, attrs);
initDisplayKanjiView();
}
public AnimPathView(Context context) {
super(context);
initDisplayKanjiView();
}
private final void initDisplayKanjiView() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setColor(0xff336699);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(2);
path = new Path();
animRunning = false;
this.setOnClickListener(this);
}
public void setPath(Path p) {
path = p;
}
@Override
public void onClick(View v) {
startAnimation();
}
public void startAnimation() {
animRunning = true;
animPathMeasure = null;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
if (animRunning) {
drawAnimation(canvas);
} else {
drawStatic(canvas);
}
}
private void drawAnimation(Canvas canvas) {
if (animPathMeasure == null) {
// Start of animation. Set it up.
animPathMeasure = new PathMeasure(path, false);
animPathMeasure.nextContour();
animPath = new Path();
animLastUpdate = System.currentTimeMillis();
animCurrentCountour = 0;
animCurrentPos = 0.0f;
} 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 + animCurrentPos;
boolean moveTo = (animCurrentPos == 0.0f);
animPathMeasure.getSegment(animCurrentPos, newPos, animPath, moveTo);
animCurrentPos = newPos;
animLastUpdate = now;
// If this stroke is done, move on to next
if (newPos > animPathMeasure.getLength()) {
animCurrentPos = 0.0f;
animCurrentCountour++;
boolean more = animPathMeasure.nextContour();
// Check if finished
if (!more) { animRunning = false; }
}
}
// Draw path
canvas.drawPath(animPath, paint);
}
invalidate();
}
private void drawStatic(Canvas canvas) {
canvas.drawPath(path, paint);
}
}