SebasSBM
SebasSBM

Reputation: 908

How can I make a drawing animation with a child of View, drawing each Path's line one by one?

Using the code I found in this StackOverflow answer I successfully can draw anything in a Canvas by finger, and I will see what I draw while I draw it. From this, I want to make a function triggered on button press that will do two things:

  1. Erase what is drawn over the canvas.
  2. Replay whatever picture was drawn over it before canvas was cleared, by redrawing each path's lines one by one at a constant speed.

In order to do so, I have slightly modificated the onTouchEvent code, so it stores each drawn point accordingly:

@Override
public boolean onTouchEvent(MotionEvent event) {
    StrokePoint point;

    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            mPath.moveTo(event.getX(), event.getY());

            // Retrieve strokes in memory
            stroke_buffer = new Stroke();
            stroke_buffer.points = new ArrayList<StrokePoint>();
            point = new StrokePoint();
            point.x = event.getX();
            point.y = event.getY();
            stroke_buffer.points.add( point );

            break;
        case MotionEvent.ACTION_MOVE:
            mPath.lineTo(event.getX(), event.getY());

            // Retrieve strokes in memory
            point = new StrokePoint();
            point.x = event.getX();
            point.y = event.getY();
            stroke_buffer.points.add( point );

            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            // Retrieve strokes in memory
            strokes.add(stroke_buffer);
            break;
        default:
            break;
    }
    return true;
}

That way, I can load the X and Y points later in loops (that's the idea at least). The problem is I am not sure how to trigger the "redraw" on the canvas for each line. I tried to store the canvas from the "onDraw" like this:

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawPath(mPath, mPaint);

    // For trying to make the "redraw" later
    this.mCanvas = canvas;

    super.onDraw(canvas);
}

And then, I make this method for the button which I want to erase and replay the previously drawn strokes:

public void replayStrokes() {
    // I start a new path from scratch ad-hoc the "redraw" animation.
    mPath = new Path();
    Log.i("SANDBOX_INFO", "Button pressed");
    // Intention here is leaving the "whiteboard" clear again.
    mCanvas.drawRGB(255, 255, 255);
    invalidate();
    this.draw(mCanvas);
    SystemClock.sleep(1000);

    // Redraw "line by line" loop
    mPath.moveTo(strokes.get(0).points.get(0).x,
                 strokes.get(0).points.get(0).y);
    for (int i = 0; i < strokes.size(); i++) {
        for (int j = 0; j < strokes.get(i).points.size(); j++) {
            if (i == 0 && j == 0) continue;
            mPath.lineTo(strokes.get(i).points.get(j).x,
                         strokes.get(i).points.get(j).y);
            mCanvas.drawPath(mPath, mPaint);
            invalidate();
            this.draw(mCanvas);
            SystemClock.sleep(100);
        }
    }
}

When I press the button, the method replayStrokes doesn't behave as expected. It's like it remains "thinking" (processing) and, when you least expect it, refreshes all the changes in a single blow; instead, I want it to be a smooth animation where you can see the entire Path previously drawn by finger, replayed line by line, point by point. What am I doing wrong? What should I change to obtain the expected behaviour?

Upvotes: 6

Views: 164

Answers (1)

SebasSBM
SebasSBM

Reputation: 908

I ended up figuring out a solution thanks to @VitorHugoSchwaab 's comment. Little reminder: all the code I have posted in this question is supposed to be inside a public class DrawCanvas extends View class. That said:

So, i left the "trigger method" like this:

private int s, p;
private boolean replay_mode;

public void replayStrokes() {
    s = 0;
    p = 0;
    replay_mode = true;
    invalidate();
}

And I modified the onDraw method like this (I transformed it into a Megamoth, but that I can fix later :P) :

@Override
protected void onDraw(Canvas canvas) {
    if (replay_mode) {
        if (s == 0 && p == 0) {
            Log.i("SANDBOX_INFO", "Replay Mode START");
            canvas.drawRGB(255, 255, 255);
            p++;
            replayPath.reset();
            replayPath.moveTo(strokes.get(0).points.get(0).x,
                    strokes.get(0).points.get(0).y);
            invalidate();
        } else {
            Log.i("SANDBOX_INFO", "Replaying --> S:" + s + " P:" + p);
            if (p == 0) {
                replayPath.moveTo(strokes.get(s).points.get(p).x,
                        strokes.get(s).points.get(p).y);
            } else {
                replayPath.lineTo(strokes.get(s).points.get(p).x,
                        strokes.get(s).points.get(p).y);
                canvas.drawPath(replayPath, mPaint);
            }

            int points_size = strokes.get(s).points.size();
            if (s == strokes.size() - 1 && p == points_size - 1) {
                Log.i("SANDBOX_INFO", "Replay Mode END");
                replay_mode = false;
            }
            else if (p == points_size - 1) {
                s++;
                p = 0;
            } else {
                p++;
            }
        }
        invalidate();
    } else {
        canvas.drawPath(mPath, mPaint);
    }

    super.onDraw(canvas);
}

This works. However, there's a glitchy effect of "flashing" while the strokes are replayed. I think that might be because it's actually "redrawing" the entire "replay path" on every frame. Based on this guess, I will try to workaround the issue when I have somemore time to code.

Upvotes: 1

Related Questions