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

olof Dec 26 2011 Android

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);
    }
}
Android
Comments powered by Disqus