撤销/重做位图不能正常工作



我已经被这个问题困扰了好几天了,我真的不明白为什么我不能让这么简单的东西工作。我有一个小的绘图应用程序,非常类似于Android FingerPaint演示,但我在绘图发生后保存每个位图。这是因为我计划稍后在我的应用程序中添加一个填充工具,并且将新填充的位图加载到画布上更容易,而不是弄清楚如何将填充保存为描边以重新绘制到画布上。

画得很好-问题是,我不能撤消和重做。我已经尝试了下面的代码,但没有得到撤消或重做。理论上,在undo上,mbitmapsdraw应该删除它最近的位图并将其推入mBitmapsUndone堆栈,然后调用loadSurfaceBitmap()方法来绘制在我们刚刚取消的位图之前保存的位图。

对于redo,mBitmapsUndone堆栈应该弹出顶部位图并将其推回mBitmapsDrawn,然后由loadSurfaceBitmap()重新加载到画布上。

当点击重做/撤销时,没有对可视化画布的改变发生,但是数据结构的大小会发生变化(因为我在前后检查了日志语句),并且它们的大小会减少/增加。所以我认为我的问题是在重做/撤消发生后mBitmapsDrawn中最新位图的实际渲染。

private Stack<Bitmap> mBitmapsDrawn = new Stack<>();
private Stack<Bitmap> mBitmapsUndone = new Stack<>();
protected void touchStart(float x, float y) {

mBitmapsUndone.clear();
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
onDraw();
}
protected void touchMove(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
mX = x;
mY = y;
onDraw();
}
}
protected void touchUp() {
mPath.lineTo(mX, mY);
// Add path to list of paths to draw
onDraw();
mBitmapsDrawn.push(mBitmap);
loadSurfaceBitmap();
// Kill this so we don't double draw
mPath = new Path();
mPaint = new Paint(mPaint);
mIsDrawing = false;
}
protected void onDraw() {
if (mCanvas == null) {
return;
}
mCanvas.drawPath(mPath, mPaint);
}
protected void loadSurfaceBitmap() {
if (mBitmapsDrawn.size() != 0) { 
mBitmap = mBitmapsDrawn.peek();
} else {
mBitmap = mBgBitmap;
}
Canvas surfaceCanvas = mSurfaceHolder.lockCanvas();
surfaceCanvas.drawBitmap(mBitmap, 0, 0, null);
mSurfaceHolder.unlockCanvasAndPost(surfaceCanvas);
}
protected void undo() {
int undoSize = mBitmapsUndone.size();
int drawnSize = mBitmapsDrawn.size();
if (drawnSize > 0) {
Bitmap removedBitmap = mBitmapsDrawn.pop();
mBitmapsUndone.push(removedBitmap);
loadSurfaceBitmap();
}
}
protected void redo() {
int undoSize = mBitmapsUndone.size();
int pathsSize = mBitmapsDrawn.size();
if (undoSize > 0) {
Bitmap mostRecentUndo = mBitmapsUndone.pop();
mBitmapsDrawn.push(mostRecentUndo);
loadSurfaceBitmap();
}
}

TLDR:在我的应用程序中撤消/重做时,我遇到了弹出堆栈/添加到数组列表的问题,我应该改变加载在表面上的位图,但它从未发生过。

编辑:根据答案更新代码,仍然没有运气。重做不会改变表面上的可视位图。

如果你的画工作得很好,它似乎没有渲染问题-它都调用loadSurfaceBitmap()相同的方式,对吗?我不知道处理一个触摸事件是否会产生影响,如果你需要调用invalidate(),否则——我从来没有像这样处理过表面画布。

你的undo代码有点奇怪

Bitmap removedBitmap = mBitmapsUndone.remove(drawnSize - 1);
mPathsUndone.push(removedBitmap);
loadSurfaceBitmap();

当你应该从Drawn列表中删除它时,你正在从Undone堆栈中删除位图-我假设这就是为什么你正在做remove(size - 1)而不仅仅是pop(),因为你打算从List而不是Stack中删除(为什么不只是使用两个Stacks呢?)。

然后你把它放在mPathsUndone上这可能是一个拼写错误从重命名PathsBitmaps这个问题?如果是这样,你的撤销代码就是从撤销堆栈中移除一个位图,然后把它放回顶部——否则它就会去某个神秘的地方。(您还使用绘制的列表的大小来获取undo堆栈的索引,因此这也将导致大问题)

你的redo代码对我来说看起来很好,我猜唯一的问题是你的undo代码实际上并没有向它的堆栈添加任何东西,所以没有什么要重做的。我猜这可能会解决它:

protected void undo() {
int undoSize = mBitmapsUndone.size();
int drawnSize = mBitmapsDrawn.size();
if (drawnSize > 0) {
Bitmap removedBitmap = mBitmapsDrawn.remove(drawnSize - 1);
mBitmapsUndone.push(removedBitmap);
loadSurfaceBitmap();
}
}

我觉得对两者都使用堆栈会更直观,因为它是一个很好的弹出,推动另一个流,但这取决于你!


编辑这里有一些代码更新你发布的文件,像这样的东西应该工作:

protected void onDraw() {
if (mCanvas == null) {
return;
}
// a new drawing event needs a new bitmap layer to draw it on,
// starting with the contents of the current layer
mBitmap = mBitmap.copy(mBitmap.getConfig(), true);
// we also need a canvas for the new bitmap so we can draw on it
mCanvas = new Canvas(mBitmap);
mCanvas.drawPath(mPath, mFgPaint);
}

我个人可能会将"添加此位图"移至mBitmapsDrawn"这里也有代码,以及"将当前位图绘制到表面"。代码——你在touchUp中做这些,但我觉得它们更直接地与绘图的动作相关,而touchUp更多地是关于完成触摸手势和启动绘制调用。由你决定!

如果有帮助的话,我可能会这样组织。因为每个绘制事件都会创建一个新的位图,所以你不需要保持对位图Canvas的引用——无论如何你都必须创建一个新的。我将这样做剩下的部分:
getCurrentBitmap() {
return the top of mBitmapsDrawn, or if empty, the background bitmap
}
drawCurrentBitmap() {
calls getCurrentBitmap(), draws it to the Surface
}
undo() {
pops a bitmap off Drawn and pushes onto Undo, calls drawCurrentBitmap()
}
redo() {
pops a bitmap off Undo and pushes onto Drawn, calls drawCurrentBitmap()
}
drawPath() {
call getCurrentBitmap to get a source image
create a new bitmap from that
create a canvas for it and draw to it
push the bitmap onto Drawn
clear Undo
call drawCurrentBitmap
}

所以一切都是关于操作绘制和撤销堆栈,每次绘制的堆栈改变时,你调用一个函数在它上面显示图像。getCurrentBitmap函数把这个逻辑放在一个地方,所以所有更新堆栈的东西也不需要担心用合适的值更新mBitmap

我还会说,像这样存储位图工作得很好,但是效率很低——你的内存使用可能会很快堆积起来!因为你在做绘图操作,你可能想要考虑存储那些的表示,这样你就可以得到"current"按顺序重播过去的所有绘制操作。

你可以想象一下,缓存除最近操作外所有操作的结果的位图,并在其上重播最近的操作以使重绘更快——但你仍然会在你的历史中保留所有这些操作,几乎是无限的撤销。也许只是考虑一下!

最新更新