【问题标题】:How can I make a drawing animation with a child of View, drawing each Path's line one by one?如何使用 View 的子级制作绘图动画,一一绘制每个 Path 的线?
【发布时间】:2022-02-07 01:25:28
【问题描述】:

使用我在this StackOverflow answer 中找到的代码,我成功地可以用手指在画布中绘制任何东西,并且我会在绘制时看到我绘制的内容。由此,我想制作一个在按钮按下时触发的函数,它会做两件事:

  1. 擦除画布上绘制的内容。
  2. 通过以恒定速度一一重绘每条路径的线条,重放在清除画布之前在其上绘制的任何图片。

为此,我稍微修改了onTouchEvent 代码,因此它相应地存储了每个绘制的点:

@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;
}

这样,我可以稍后在循环中加载 X 和 Y 点(至少是这样的想法)。问题是我不确定如何在画布上为每一行触发“重绘”。我试图像这样从“onDraw”存储画布:

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

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

    super.onDraw(canvas);
}

然后,我为要擦除并重放先前绘制的笔划的按钮创建此方法:

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);
        }
    }
}

当我按下按钮时,replayStrokes 方法的行为不符合预期。就好像它仍然在“思考”(处理),并且在你最不期待的时候,一口气刷新了所有的变化;相反,我希望它是一个流畅的动画,您可以在其中看到以前用手指绘制的整个路径,逐行,逐点重播。我究竟做错了什么?我应该改变什么以获得预期的行为?

【问题讨论】:

  • 我最近发现了一个包含an answer 的问题,它可能会帮助我找到自己问题的答案。
  • this 可能是我一直在寻找的解决方案吗?
  • 我对您的问题没有确切的答案,但我可以说这不是Canvas 的工作方式。当您调用canvas.doSomething() 时,它会将像素写入位图。在onDraw 结束后,您将存储画布以执行绘图,此时Canvas 无法再绘制任何其他内容。您需要在onDraw 期间执行绘图,而不是稍后!
  • @Vitor 所以,每次我创建一个mPath.lineTo 时,不知何故我必须等到onDraw 结束执行,然后再输入下一行......嗯......我唯一的方法可以突然发现是通过CustomListener 或类似的东西...
  • 哈哈哈……CustomListener……最后,那个想法太疯狂了……简直太疯狂了……在一个好的头脑风暴环境中,这个想法会在与这个问题的问题类似的情况下,可能在不到 30 秒的时间内死亡......

标签: java android draw


【解决方案1】:

感谢@VitorHugoSchwaab 的评论,我最终找到了解决方案。小提醒:我在这个问题中发布的所有代码都应该在 public class DrawCanvas extends View 类中。也就是说:

所以,我留下了这样的“触发方法”:

private int s, p;
private boolean replay_mode;

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

我像这样修改了onDraw 方法(我将其转换为Megamoth,但我可以稍后修复: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);
}

这行得通。但是,在重放笔画时会出现“闪烁”的故障效果。我认为这可能是因为它实际上是在每一帧上“重绘”整个“重播路径”。基于这个猜测,当我有更多时间编写代码时,我会尝试解决这个问题。

【讨论】:

  • 很好,你找到了解决它的方法 :) 另一种可能的解决方法是使用ValueAnimator 并使用从zerototalPoints.size - 1ofInt。在动画回调中,您可以更新sp 的值并调用invalidate() 来触发绘图。这样动画和绘图逻辑就分开了,你可以更好地控制它。
  • 听起来不错。我会考虑ValueAnimator 用于未来的重构。
猜你喜欢
  • 2014-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-06
  • 1970-01-01
  • 2015-10-23
  • 2018-04-27
相关资源
最近更新 更多