在 Android 中平滑滚动画布
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2079328/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Scrolling a Canvas smoothly in Android
提问by prepbgg
I'm new to Android.
我是安卓新手。
I am drawing bitmaps, lines and shapes onto a Canvas inside the OnDraw(Canvas canvas) method of my view. I am looking for help on how to implement smooth scrolling in response to a drag by the user. I have searched but not found any tutorials to help me with this.
我正在我的视图的 OnDraw(Canvas canvas) 方法内的 Canvas 上绘制位图、线条和形状。我正在寻求有关如何实现平滑滚动以响应用户拖动的帮助。我已经搜索过但没有找到任何教程来帮助我解决这个问题。
The reference for Canvas seems to say that if a Canvas is constructed from a Bitmap (called bmpBuffer, say) then anything drawn on the Canvas is also drawn on bmpBuffer. Would it be possible to use bmpBuffer to implement a scroll ... perhaps copy it back to the Canvas shifted by a few pixels at a time? But if I use Canvas.drawBitmap to draw bmpBuffer back to Canvas shifted by a few pixels, won't bmpBuffer be corrupted? Perhaps, therefore, I should copy bmpBuffer to bmpBuffer2 then draw bmpBuffer2 back to the Canvas.
Canvas 的参考似乎是说,如果 Canvas 是从 Bitmap(称为 bmpBuffer,比如说)构造的,那么在 Canvas 上绘制的任何内容也会在 bmpBuffer 上绘制。是否可以使用 bmpBuffer 来实现滚动……也许将它复制回一次移动几个像素的画布?但是如果我使用 Canvas.drawBitmap 将 bmpBuffer 绘制回移动了几个像素的 Canvas,那么 bmpBuffer 不会被破坏吗?因此,也许我应该将 bmpBuffer 复制到 bmpBuffer2,然后将 bmpBuffer2 绘制回 Canvas。
A more straightforward approach would be to draw the lines, shapes, etc. straight into a buffer Bitmap then draw that buffer (with a shift) onto the Canvas but so far as I can see the various methods: drawLine(), drawShape() and so on are not available for drawing to a Bitmap ... only to a Canvas.
一种更直接的方法是将线条、形状等直接绘制到缓冲区位图中,然后将该缓冲区(通过移位)绘制到画布上,但据我所知,有多种方法:drawLine()、drawShape()等等不能用于绘制到位图……只能绘制到画布。
Could I have 2 Canvases? One of which would be constructed from the buffer bitmap and used simply for plotting the lines, shapes, etc. and then the buffer bitmap would be drawn onto the other Canvas for display in the View?
我可以有 2 个画布吗?其中一个将从缓冲区位图构造并仅用于绘制线条、形状等,然后将缓冲区位图绘制到另一个 Canvas 上以在视图中显示?
I should welcome any advice!
我应该欢迎任何建议!
Answers to similar questions here (and on other websites) refer to "blitting". I understand the concept but can't find anything about "blit" or "bitblt" in the Android documentation. Are Canvas.drawBitmap and Bitmap.Copy Android's equivalents?
此处(以及其他网站)对类似问题的回答指的是“位块传输”。我理解这个概念,但在 Android 文档中找不到任何关于“blit”或“bitblt”的信息。Canvas.drawBitmap 和 Bitmap.Copy 是 Android 的等价物吗?
采纳答案by Mervin
I had this problem too,
我也有这个问题,
I did the drawing like this:
我是这样画的:
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);
}
for smooth scrolling you'd need to make some sort of method that takes a few points after scrolling (i.e the first scroll point and the 10th) , subtract those and scroll by that number in a for each loop that makes it gradually slower ( ScrollAmount - turns - Friction ).
为了平滑滚动,您需要制作某种在滚动后需要几个点的方法(即第一个滚动点和第 10 个),减去这些并在每个循环中滚动该数字,使其逐渐变慢( ScrollAmount - 转弯 - 摩擦)。
I Hope this gives some more insight.
我希望这能提供更多的见识。
回答by prepbgg
I seem to have found an answer. I have put the bulk of the drawing code (which was previously in onDraw()) in a new doDrawing() method. This method starts by creating a new bitmap larger than the screen (large enough to hold the complete drawing). It then creates a second Canvas on which to do the detailed drawing:
我似乎找到了答案。我已经将大部分绘图代码(以前在 onDraw() 中)放在一个新的 doDrawing() 方法中。此方法首先创建一个大于屏幕的新位图(大到足以容纳完整的绘图)。然后它会创建第二个 Canvas 来进行详细绘图:
BufferBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
Canvas BufferCanvas = new Canvas(BufferBitmap);
The rest of the doDrawing() method is taken up with detailed drawing to BufferCanvas.
doDrawing() 方法的其余部分用于对 BufferCanvas 进行详细绘制。
The entire onDraw() method now reads as follows:
整个 onDraw() 方法现在如下所示:
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(BufferBitmap, (float) -posX, (float) -posY, null);
}
The position variables, posX and posY, are initialised at 0 in the application's onCreate()method. The application implements OnGestureListener and uses the distanceX and distanceY arguments returned in the OnScroll notification to increment posX and posY.
位置变量 posX 和 posY 在应用程序的 onCreate() 方法中被初始化为 0。应用程序实现 OnGestureListener 并使用 OnScroll 通知中返回的 distanceX 和 distanceY 参数来增加 posX 和 posY。
That seems to be about all that's needed to implement smooth scrolling. Or am I over-looking something!?
这似乎是实现平滑滚动所需的全部内容。还是我看多了!?
回答by Ribo
No need for the activity to be restarted! (Per prepbgg's Jan 27 10 reply to his Jan 17 10 'answer') Rather than recycling the bitmap and incurring the overhead of having the activity reloaded, you can avoid having the application loaded by putting the 'android:configChanges' attribute shown below, in the 'activity' element of the AndroidManifest.xml file for the app. This tells the system the the app will handle orientation changes and that it doesn't need to restart the app.
无需重新启动活动!(根据 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>
This method can be used to get a notification when the orienation is changed:
此方法可用于在方向更改时获取通知:
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
回答by prepbgg
Continuation of reply to Viktor ...
继续回复维克托......
In fact, the situation is more complicated. Because the doDrawing process is quite slow (taking 2-3 seconds on my slow old HTC Hero phone) I found it desirable to pop up a Toast message to advise the user that it was happening and to indicate the reason. The obvious way to do this was to create a new method containing just 2 lines:
事实上,情况更为复杂。由于 doDrawing 过程非常缓慢(在我的旧 HTC Hero 手机上需要 2-3 秒),我发现最好弹出 Toast 消息以告知用户它正在发生并指出原因。显而易见的方法是创建一个仅包含 2 行代码的新方法:
public void redrawBuffer(String strReason) {
Toaster.Toast(strReason, "Short");`
doDrawing();
}
and to call this method from other places in my program instead of doDrawing().
并从我程序中的其他地方调用此方法而不是 doDrawing()。
However, I found that the Toaster either never appeared or flashed up so briefly that it could not be read. My workaround has been to use a time check Handler to force the program to sleep for 200 milliseconds between displaying the Toast and calling doDrawing(). Although this slightly delays the start of a redraw I feel this is a price worth paying in terms of the program's usability because the user knows what is going on. reDrawBuffer() now reads:
然而,我发现烤面包机要么从未出现过,要么闪过太短以至于无法读取。我的解决方法是使用时间检查处理程序强制程序在显示 Toast 和调用 doDrawing() 之间休眠 200 毫秒。尽管这会稍微延迟重绘的开始,但我觉得就程序的可用性而言,这是值得付出的代价,因为用户知道发生了什么。reDrawBuffer() 现在读取:
public void redrawBuffer(String strReason) {
Toaster.Toast(strReason, "Short");
mTimeCheckHandler.sleep(200);
}`
and the Handler code (which is nested within my View class) is:
处理程序代码(嵌套在我的 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);
}
}`
回答by Gaurav Vaish
prepbgg: I don't think the code will work because canvas.drawBitmap does not draw into the bitmap but draws the bitmap on-to the canvas.
prepbgg:我不认为代码会起作用,因为 canvas.drawBitmap 不会绘制到位图中,而是将位图绘制到画布上。
Correct me if I am wrong!
如果我错了,请纠正我!