上篇"
SurfaceView的烦恼(一)-双缓存与清屏
"提供了一个解决办法:每次画的时候,先清屏然后再全部重新画。这里有两重意思:清屏就把上次的残留清除掉了,不会出现重叠现象;全部重新画,信息也就不
会因为清屏而不全。这种办法用起来很有效,不管SurfaceView的双缓冲显示(flip)的底层原理,也不会出现下面要说的“第一、二帧的猜想”的
问题。
办法虽然有效,但对于一些每次只画一小部分区域,且这些画的区域不会重叠时,这办法的效率性就很差了;而这种情况是希望在不全清屏的情况下,在原来的基础上继续画。下面是测试代码,大部分代码与上篇的一样,除了MyTimerTask中的run()方法:
main.xml
- xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android=""
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <com.tutor.surface.MySurfaceView
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- />
- LinearLayout>
MySurfaceView.java
- package com.tutor.surface;
- import java.util.Timer;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.view.SurfaceHolder;
- import android.view.SurfaceView;
- public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
- private Timer timer = null;
- private MyTimerTask task = null;
- public MySurfaceView(Context context, AttributeSet attrs) {
- super(context, attrs);
- SurfaceHolder holder = getHolder();
- holder.addCallback(this);
- }
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width,
- int height) {
-
- }
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
-
- timer = new Timer();
- task = new MyTimerTask(holder);
-
-
- timer.schedule(task, 500, 1000);
- }
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
-
- if(timer != null) {
- timer.cancel();
- timer = null;
- }
- task = null;
- }
-
- }
MyTimerTask.java
- package com.tutor.surface;
- import java.util.TimerTask;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.Paint;
- import android.graphics.Rect;
- import android.view.SurfaceHolder;
- public class MyTimerTask extends TimerTask {
- private SurfaceHolder holder = null;
-
- private int left = 50;
- private int top = 5;
- private int width = 20;
-
- private Paint paint = null;
- boolean isRunning = true;
- private int count = 0;
-
- public MyTimerTask(SurfaceHolder _holder) {
- holder = _holder;
- paint = new Paint();
- paint.setColor(Color.WHITE);
- }
-
- @Override
- public void run() {
-
- if(top > 400)
- return;
-
- switch(count%5) {
- case 0:
- paint.setColor(Color.BLUE);
- break;
- case 1:
- paint.setColor(Color.WHITE);
- break;
- case 2:
- paint.setColor(Color.YELLOW);
- break;
- case 3:
- paint.setColor(Color.RED);
- break;
- case 4:
- paint.setColor(Color.GREEN);
- break;
- }
-
- Canvas canvas = null;
- try {
- Rect rectDirty = new Rect(left, top, left+width, top+width);
- Rect rectangle = new Rect(left, top, left+width, top+width);
-
- canvas = holder.lockCanvas(rectDirty);
- canvas.drawRect(rectangle, paint);
- top += 25;
- if(top > 400) {
- isRunning = false;
- }
- }catch (Exception e) {
-
- }finally {
- if(canvas != null) {
- holder.unlockCanvasAndPost(canvas);
- }
- }
- count++;
- }
-
- public void clearDraw() {
- Canvas canvas = null;
- try {
- canvas = holder.lockCanvas(null);
- canvas.drawColor(Color.BLACK);
- }catch (Exception e) {
-
- }finally {
- if(canvas != null) {
- holder.unlockCanvasAndPost(canvas);
- }
- }
- }
- }
结果如下:
(1) 在MySurfaceView中,如果在Timer启动前不先调用清屏(即把task.clearDraw()注释掉),出现的结果是:
(2) 如果加上那句先清屏,则结果如下:
很明显,第一个结果在一开始的时候缺少了一个蓝块,代表着有部分想要画的信息被冲掉了(或覆盖掉了);第二个结果才是我们想要的结果。
(1) 从代码中可以看到,所画的矩形的位置每次都不一样,且每次画的位置都不会和以前画的区域重叠时,是可以使用递增的方式去画(即在保持原来的情况下再画新的),这样就不需要每次都全部重新画了,效率也就提高了。
(2)
代码中貌似没有什么第一帧、第二帧的现象。其实一开始尝试的时候,并不会想到在Timer启动之前先清屏;而是在MyTimerTask中的run()方
法使用count==0、count==1来测试第一帧、第二帧的情况。由于SurfaceView的原理全部是使用jni调用底层库来实现的(使用了
JAVA的代码没有几行),这代表着想要真正了解其原理,得深入到非JAVA代码中(目前我没有什么好办法,如果有的话麻烦告诉我一下)。因而,在此只能
进行一下猜想。
第一、二帧猜想:
虽然部分更新的时候,每次画canvas时都仅锁住一小部分区域,但我猜想第一帧、第二帧是全部换的,而不仅仅是锁住的那一小部分区域。这里说的第一帧是
第一次调用MyTimerTask的run()方法进行画面板,这里根据SurfaceView的双缓冲原理中的交替显示,整个屏幕大小的back
buffer换到前面来(并把第一个蓝方块画上),此时front buffer转回后台(此时front
buffer是全屏黑的);第二次调用MyTimerTask的run()方法进行画面板时,在front
buffer中画上白方块并显示(注意:由于front buffer之前并没有方块,第一个蓝方块是在back
buffer中的),这时看到的现象是蓝方块被冲掉了,只剩白方块。特殊的是,只有这两帧是整个面板全部切换的,后面的front
buffer、back
buffer切换的空间变成了锁定面板区域,由于锁定的面板区域每次都是不一样的小区域,这时双缓冲的交替只有这一小部分区域,而不是整个屏幕;除了锁定
的区域,其它区域的内容保留。
如果在Timer启动前先清一下屏(实际上是为了调用holder.unlockCanvasAndPost(canvas)使得原来的back
buffer先交换一下),然后再在此基础上画,就没有问题了。所以在run()方法中使用读数的方式,第一帧仅post,从第二帧开始画真正的内容也是
一样的。
上面仅仅是猜想,只有真正深入到下面的代码中了解,才知道是否正确,如果有了解的朋友,不妨阐述一下。下面有两个佐证:
(1)
在MyTimerTask中锁定的区域rectDirty,如果直接传到下面的画方块代码中canvas.drawRect(rectDirty,
paint);,会发现一个比较有趣的现象:首先会看到屏幕背景变成全蓝(不是全黑了),然后屏幕背景变成全白,此后屏幕背景颜色不再变化(一直是白
的),方块除了第一个蓝块没有的话,其它的正常。这个现象是符合上面猜想的第一帧、第二帧现象,目前没有弄清楚的是,为什么背景整个颜色会被改变。把锁定
的区域大小再new一个同样大小、起始坐标相同的Rect来画方块就没有问题。
(2)
hellogv大牛写的一个例子:http://blog.csdn.net/hellogv/archive/2010/11/03
/5985090.aspx
,由于之前发现这个例子是部分刷新的,所以重点学了一下。在我遇到第一、二帧那个问题时,回头再看这个例子,思考它为什么没有这个问题。其实里面恰好第
一、二帧什么都没有画而是直接显示面板(即unlockCanvasAndPost(canvas) ),不知道这是特意这样做的,还是有其它原因。
阅读(2929) | 评论(0) | 转发(0) |