保存画布然后恢复,这是为什么?

56

我经常看到以下代码

canvas.save().
canvas translate or rotate
some drawing
canvas.restore

我不明白为什么我们要保存然后恢复。撤消刚刚完成的事情的意义何在!我确定我在这里漏掉了什么


如果您不确定文档的含义,可以尝试使用这些方法进行一些实验,看看它们实际上做了什么。 - pskink
为什么我们要使用canvas.save()或canvas.restore()? - petey
为了更好的理解,请参考这篇文章:https://dev59.com/LW025IYBdhLWcg3wzZT2Canvas translate是什么意思? - Huy Nguyen
请参考以下文章以更好地理解:https://dev59.com/LW025IYBdhLWcg3wzZT2 - Huy Nguyen
5个回答

125

我知道这个问题有点过时了,但是对于仍在寻找答案的人,我可以用通俗易懂的语言解释:

想象画布是一张纸,你的任务是在底部右侧画一个正立的机器人,然后在顶部稍微向右移动并缩小约40%的位置上画一个倒立的机器人。 就像这样:Android canvas example

你会怎么开始呢?哪个更容易做到先做?

你可能会先画底部的大机器人,因为它是正立的,并且朝着更自然的方向绘制更容易。 那么现在该如何处理第二个倒置的机器人呢?

  • 你可以尝试按原样绘制,但这会有些困难,因为你是倒着的。

或者

  • 你可以将纸翻转180度,稍微移动起始点,以较小的比例开始绘制,完成后再将纸翻转回来。

这就是 canvas.save()canvas.restore() 的作用,它们允许您以任何使绘制更容易的方式修改画布。 您不需要使用这些方法,但是它们确实简化了很多过程。 上述过程看起来会像下面这样:

drawRobot()
canvas.save()
canvas.rotate(180)
canvas.translate(100, 0)
canvas.scale(40,40)
drawRobot()
canvas.restore()

如果我们查看 restore() 文档,它说:

用于自上次保存调用以来删除对矩阵/剪辑状态的所有修改

而要了解这些修改是什么,我们看一下 save(),它说:

平移、缩放、旋转、扭曲、串联或剪辑矩形、剪辑路径

嗯,看起来我们确实使用了 translaterotatescale,但我们也调用了 drawRobot(),那么调用 restore 不会抹掉我们的图形吗?不会,因为它只影响修改,而不会影响图形。因此,当我们调用 restore 时,它将使画布恢复到开始第二次绘图之前的状态。


1
非常好的答案,谢谢 :) - Mina Samir
太棒了!谢谢! - Hooni
一直在犹豫不决,但还是谢谢。 - Tonnie

34
“撤销我们刚才所做的事情有什么意义!”
不过,实际情况并非如此。如果您只是根据表面字面理解,似乎是这样的,但实际上并不是。
可以将其想象成这样:您希望在同一onDraw(Canvas)调用中应用一系列非常复杂的平移和旋转。现在,由于您对Canvas应用的每个平移/旋转都是按顺序发生的,因此您必须撤消最后一次对Canvas所做的调整,或以某种方式根据之前的调整计算新的调整,然后再绘制您想要绘制的内容。这将变得非常混乱而且很快就会变得难以管理。
使用canvas.save()canvas.restore()可以轻松简化该过程。
通过在保存/还原块内运行适用于Canvas的调整,可以有效地将这些调整隔离,以便下一个要绘制的内容不会受到当前正在绘制的内容的影响。
现在,让我们更好地解释一下名称: canvas.save()表示我要保存当前Canvas的调整状态,以便稍后可以返回到该状态。 canvas.restore()表示我要将我的Canvas的调整恢复到上次调用cavas.save()的状态。
这种方法之美在于其简单性。如果您已经在保存/还原块中绘制了您想要绘制的任何内容,并且您不再需要这些调整来进行下一次绘图,那么使用此功能可以让您丢弃那些不必要的调整并返回到您希望从中开始下一次绘图的状态。
希望这有助于解释它!

2
非常感谢您的解释。因此,如果我们保存状态,进行绘图和矩阵变换,然后进行还原,那么我们实际上删除了我们所做的内容。因此,当我们完成ondraw时,事情将与我们开始ondraw时相同。那么这种方法就没有用了,是吗?我本来想象你会在开始ondraw时执行还原而不是在结束时执行还原。我仍然有些不明白,难道不是吗? - Snake
1
"那么这个方法就没用了,是吗?" - 取决于你所说的无用是什么意思。是的,下一个onDraw调用将具有未调整的状态,但这正是你想要的。当onDraw被调用时,意味着你的View的状态已经改变并且需要重新绘制,因此你希望你的Canvas处于归零状态,否则你将不得不手动找出如何将其恢复到那里,以便你能够准确地再次绘制。当你只做一项任务时,保存/恢复很难理解;事实上,在那种情况下使用它甚至没有意义。(续...) - Cruceo
2
现在,如果您在同一个onDraw方法中对几个不同的对象执行几个不同的调整操作,则save/restore会变得非常有用。假设您有A、B两个对象,都位于(0,0)。您想将A移动到(30,0),将B移动到(0,50)。第一个很容易,将画布平移30即可。现在第二个就有趣了,因为您不仅要在y轴上从0到50,还要在x轴上从30到0,然后再从0到50,所以您需要平移(-30,50),而不是简单的(0,50)。现在,如果您在save/restore内部完成了A的平移操作,您只需平移(继续...) - Cruceo
1
对象B使用自己的坐标系,使您的工作(以及需要计算的数学)变得简单得多。是的,只有两个球,你可以很容易地完成这个数学问题,但想象一下屏幕上有1000个球,每个球都有自己的x/y坐标。你必须撤销每一个调整,否则绘制下一个球将变得几乎不可能。因此,基本上我想说的是:对于绘制单个物体,请不要使用它。当在onDraw中绘制多个物体并对画布进行更改时,这就是您使用它的时候,因为它会让您的生活更轻松。 - Cruceo
1
非常感谢@Guardanis..你的例子帮了我很多。我想我知道哪里出了问题。当你进行还原操作时,只有矩阵变换被重置到它们原来的位置,而实际的绘图则保留在原地。我以为它会把绘图带回到旧的位置(甚至删除它们),所以我很困惑为什么有人会这样做。再次感谢你为我澄清这个难题。 - Snake

1
我想最简单的方式是:
它移除了设置的更改,但不会移除绘画本身。
设置包括缩放画布等,它将还原缩放到调用canvas.save()时的初始状态。

1
使用画布需要在画布上执行各种平移、缩放、旋转和扭曲过程。save() 方法可以在所有这些增强之前保存状态,restore() 方法可以回到没有增强时的状态。换句话说,您可以在进行画布变换之前保存预设状态,在处理过程中旋转和进行其他操作,但完成后可以回到没有增强的状态。

0

当您有由多个对象组成的背景时,一个很好的方法是保存这个“静态”背景,并仅重新绘制已更改的对象。这可以节省(处理器)时间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接