在 Android 中平滑滚动画布
Posted
技术标签:
【中文标题】在 Android 中平滑滚动画布【英文标题】:Scrolling a Canvas smoothly in Android 【发布时间】:2011-01-05 23:09:41 【问题描述】:我是 android 新手。
我在视图的 OnDraw(Canvas canvas) 方法内的 Canvas 上绘制位图、线条和形状。我正在寻求有关如何实现平滑滚动以响应用户拖动的帮助。我已经搜索但没有找到任何教程来帮助我。
Canvas 的参考似乎是说,如果 Canvas 是由 Bitmap(例如称为 bmpBuffer)构造的,那么在 Canvas 上绘制的任何内容也会在 bmpBuffer 上绘制。是否可以使用 bmpBuffer 来实现滚动...也许将其复制回 Canvas 一次移动几个像素?但是如果我使用 Canvas.drawBitmap 将 bmpBuffer 绘制回移动了几个像素的 Canvas,bmpBuffer 不会被破坏吗?因此,也许我应该将 bmpBuffer 复制到 bmpBuffer2,然后将 bmpBuffer2 绘制回 Canvas。
一种更直接的方法是将线条、形状等直接绘制到缓冲区位图中,然后将该缓冲区(带有移位)绘制到画布上,但据我所知,有各种方法:drawLine()、 drawShape() 等不可用于绘制到位图……只能绘制到画布上。
我可以有 2 个画布吗?其中一个将从缓冲区位图构造并仅用于绘制线条、形状等,然后将缓冲区位图绘制到另一个 Canvas 上以在视图中显示?
我应该欢迎任何建议!
此处(以及其他网站上)类似问题的答案请参阅“blitting”。我理解这个概念,但在 Android 文档中找不到任何关于“blit”或“bitblt”的信息。 Canvas.drawBitmap 和 Bitmap.Copy 是 Android 的等价物吗?
【问题讨论】:
今天早上做了更多的谷歌搜索。根据这个网页markmail.org/message/oedvjxi3dhokzq23我可以有第二个画布,所以我会探索这个想法。 在下面的新“答案”中查看更多信息。 【参考方案1】:我似乎找到了答案。我已将大部分绘图代码(以前在 onDraw() 中)放在一个新的 doDrawing() 方法中。此方法首先创建一个比屏幕大的新位图(大到足以容纳完整的绘图)。然后它会创建第二个 Canvas 来进行详细绘图:
BufferBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
Canvas BufferCanvas = new Canvas(BufferBitmap);
doDrawing() 方法的其余部分用于详细绘制到 BufferCanvas。
整个 onDraw() 方法现在如下所示:
@Override protected void onDraw(Canvas canvas)
super.onDraw(canvas);
canvas.drawBitmap(BufferBitmap, (float) -posX, (float) -posY, null);
位置变量 posX 和 posY 在应用程序的 onCreate() 方法中初始化为 0。应用程序实现 OnGestureListener 并使用 OnScroll 通知中返回的 distanceX 和 distanceY 参数来增加 posX 和 posY。
这似乎是实现平滑滚动所需的全部内容。还是我忽略了什么!?
【讨论】:
自从写下这个“答案”后我发现的一件事是,当屏幕旋转时,需要额外的代码来“回收”缓冲区位图(和任何其他位图对象)。这是因为当屏幕旋转时,应用程序的主要活动结束并重新启动,但系统不会自动恢复位图对象使用的内存。因此,当不再需要时,为每个位图调用 recycle() 方法似乎很重要。对于缓冲区位图,答案似乎是覆盖 Activity 的 onDestroy 方法并在其中调用位图的 recycle()。 只需将android:configChanges="orientation"
放入Manifest 即可避免此代码。请参阅 Ribo 于 2011 年 1 月 4 日对此问题的回答。
请帮忙解决这个问题***.com/questions/11720702/canvas-zoom-in-partially/…
抱歉,kamal 和 WildBill,响应缓慢。我最近没有看这个线程。我很高兴你解决了你的问题,卡马尔。 WildBill,在创建视图后,我首先从主 Activity 的 onCreate() 调用 doDrawing();每当需要更改缓冲区位图中的图像时,我都会再次调用 doDrawing()。【参考方案2】:
我也遇到过这个问题
我是这样画的:
Canvas BigCanvas = new Canvas();
Bitmap BigBitmap = new Bitmap(width,height);
int ScrollPosX , ScrollPosY // (calculate these with the onScrollEvent handler)
void onCreate()
BigCanvas.SetBitmap(BigBitmap);
onDraw(Canvas TargetCanvas)
// do drawing stuff
// ie. BigCanvas.Draw.... line/bitmap/anything
//draw to the screen with the scrolloffset
//drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
TargetCanvas.DrawBitmap(BigBitmap(new Rect(ScrollPosX,ScrollPosY,ScrollPosX + BigBitmap.getWidth(),ScrollPosY + BigBitmap.getHeight(),new Rect(0,0,ScreenWidth,ScreenHeight),null);
为了平滑滚动,您需要制作某种方法,在滚动后需要几个点(即第一个滚动点和第 10 个),减去这些并在每个循环中滚动该数字,使其逐渐较慢(ScrollAmount - 转数 - 摩擦)。
我希望这能提供更多见解。
【讨论】:
感谢默文,您的回复。我不确定我是否完全理解你的滚动是如何工作的。您是否等到收到 10 个滚动事件后才开始移动位图?我曾想象过,为了让屏幕显示感觉完全响应,我需要在收到第一个滚动事件后立即将位图重绘到 Canvas 上。而且,因为我的“绘图内容”非常耗时,所以我决定将其移出 onDraw 方法,以使程序尽可能响应滚动。【参考方案3】:继续回复维克多……
其实情况更复杂。因为 doDrawing 过程非常缓慢(在我的旧 HTC Hero 手机上需要 2-3 秒),我发现最好弹出一条 Toast 消息来告知用户它正在发生并指出原因。显而易见的方法是创建一个只包含 2 行的新方法:
public void redrawBuffer(String strReason)
Toaster.Toast(strReason, "Short");`
doDrawing();
并从我程序中的其他地方调用此方法,而不是 doDrawing()。
但是,我发现烤面包机要么从未出现,要么闪现过短以至于无法读取。我的解决方法是使用时间检查处理程序来强制程序在显示 Toast 和调用 doDrawing() 之间休眠 200 毫秒。尽管这会稍微延迟重绘的开始,但我认为就程序的可用性而言,这是值得付出的代价,因为用户知道发生了什么。 reDrawBuffer() 现在读取:
public void redrawBuffer(String strReason)
Toaster.Toast(strReason, "Short");
mTimeCheckHandler.sleep(200);
`
处理程序代码(嵌套在我的 View 类中)是:
private timeCheckHandler mTimeCheckHandler = new timeCheckHandler();
class timeCheckHandler extends Handler
@Override
public void handleMessage(Message msg)
doDrawing();
public void sleep(long delayMillis)
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
`
【讨论】:
【参考方案4】:无需重新启动活动! (根据 prepbgg 的 Jan 27 10 对他 Jan 17 10 'answer' 的回复),您可以通过放置如下所示的 'android:configChanges' 属性来避免加载应用程序,而不是回收位图并产生重新加载活动的开销,在应用程序的 AndroidManifest.xml 文件的“活动”元素中。这告诉系统应用程序将处理方向更改并且不需要重新启动应用程序。
<activity android:name=".ANote"
android:label="@string/app_name"
android:configChanges="orientation|screenLayout">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
此方法可用于在方向更改时获取通知:
public void onConfigurationChanged(Configuration newConfig)
super.onConfigurationChanged(newConfig);
prt("onConfigurationChanged: "+newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
prt(" PORTRAIT");
else
prt(" LANDSCAPE");
// end of onConfigurationChanged
【讨论】:
这很有趣。 (事实上,我确实在相当早的阶段通过将 android:screenOrientation="portrait" 放入 Manifest 来阻止应用程序更改方向。我这样做是为了解决内存问题。)但是,我想处理旋转如果可能的。当您谈到应用程序处理方向更改时,我需要做些什么来实现这一点? View 的 onDraw 方法还会被自动调用吗?还是我只是简单地通过“无效”视图来响应旋转? 我刚刚将 Manifest 中的android:screenOrientation="portrait"
替换为 android:configChanges="orientation"
并且该应用程序正常运行! (不需要任何代码来检查方向变化......屏幕会在我没有任何干预的情况下重新绘制。)LogCat 建议手机在每次方向变化时花费大约 250 毫秒的垃圾收集时间。我还看不出有什么问题。非常感谢您带着这个建议来到这里。【参考方案5】:
prepbgg:我认为代码不会起作用,因为 canvas.drawBitmap 不会绘制到位图中,而是将位图绘制到画布上。
如果我错了,请纠正我!
【讨论】:
谢谢,MasterGaurav。我同意 canvas.drawBitmap(BufferBitmap, ...) 不会在 BufferBitmap 中绘制任何内容。但是,我有一个单独的 doDrawing() 方法,其中事物(线条、位图等)被绘制到 BufferCanvas 中,从而被绘制到 BufferBitmap 中。该代码似乎确实有效(尽管它偶尔会卡顿和/或崩溃......我不知道这是因为我的绘图代码有问题还是有其他错误。)以上是关于在 Android 中平滑滚动画布的主要内容,如果未能解决你的问题,请参考以下文章