【问题标题】:Scrolling a Canvas smoothly in Android在 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我可以有第二个画布,所以我会探索这个想法。
  • 在下面的新“答案”中查看更多信息。

标签: android android-canvas smooth-scrolling


【解决方案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 日对此问题的回答。
  • 抱歉,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 中。该代码似乎确实有效(尽管它偶尔会卡顿和/或崩溃......我不知道这是因为我的绘图代码有问题还是有其他错误。)
    猜你喜欢
    • 2012-09-17
    • 2013-10-23
    • 2015-06-27
    • 1970-01-01
    • 2011-06-24
    • 1970-01-01
    • 2013-05-13
    • 2012-11-07
    • 2016-12-18
    相关资源
    最近更新 更多