view, surfaceView, invalidate, postInvalidate, 刷新屏幕

http://blog.csdn.net/linghu_java/article/details/9985489

1.view

view在api中的结构

Java.lang.Object

Android.view.View

 

直接子类:

AnalogClock, ImageView, KeyboardView, ProgressBar, SurfaceView, TextVie, ViewGroup, ViewStub 

 

间接子类:

AbsListView, AbsSeekBar, AbsSpinner, AbsoluteLayout, AdapterView<T extends Adapter>, AppWidgetHostView, AutoCompleteTextView, Button, CheckBox, CheckedTextView, Chronometer, CompoundButton, DatePicker, DialerFilter, DigitalClock,EditView, ExpandableListView, ExtractEditText, FrameLayout, GLSurfaceView, Gallery, GestureOverlayView, GridView, HorizontalScrollView, ImageButton, ImageSwitcher, LinearLayout, ListView, MediaController, MultiAutoCompleteTextView, QuickContactBadge, RadioButton, RadioGroup, RatingBar, RelativeLayout, ScrollView, SeekBar, SlidingDrawer, Spinner, TabHost, TabWidget, TableLayout, TableRow, TextSwitcher, TimePicker, ToggleButton, TwoLineListItem, VideoView, ViewAnimator, ViewFlipper, ViewSwitcher, WebView, ZoomButton, ZoomControls

 

         由此可见View类属于Android开发绘制中的显示老大,任何与绘制有关系的控件都是它的子类。在这篇文章中我主要讲View 与SurFaceView 使用线程刷新屏幕绘制方面的知识。开发中如何去选择使用View还是SurFaceView。我相信读过我前几篇博客的朋友应该知道我在刷新屏幕的时候使用invalidate()方法来重绘,下面我详细的说明一下Andooid刷新屏幕的几种方法。

 

 

          第一种: 在onDraw方法最后调用invalidate()方法,它会通知UI线程重绘 这样 View会重新调用onDraw方法,实现刷新屏幕。 这样写看起来代码非常简洁漂亮,但是它也同时存在一个很大的问题,它和游戏主线程是分开的 它违背了单线程模式,这样操作绘制的话是很不安全的,举个例子 比如程序先进在Activity1中 使用invalidate()方法来重绘, 然后我跳到了Activity2这时候Activity1已经finash()掉 可是Activity1中 的invalidate() 的线程还在程序中,Android的虚拟机不可能主动杀死正在运行中的线程所以这样操作是非常危险的。因为它是在UI线程中被动掉用的所以很不安全。

invalidate()  更新整个屏幕区域

invalidate(Rect rect) 更新Rect区域

invalidate(l, t, r, b) 更新指定矩形区域

 
  1. public void onDraw(Canvas canvas){    
  2.         DosomeThing();    
  3.         invalidate();    
  4. }    

第二种:使用postInvalidate();方法来刷新屏幕 ,调用后它会用handler通知UI线程重绘屏幕,我们可以 new  Thread(this).start(); 开启一个游戏的主线程 然后在主线程中通过调用postInvalidate();方法来刷新屏幕。postInvalidate();方法 调用后 系统会帮我们调用onDraw方法 ,它是在我们自己的线程中调用 通过调用它可以通知UI线程刷新屏幕 。由此可见它是主动调用UI线程的。所以建议使用postInvalidate()方法通知UI线程来刷新整个屏幕。

postInvalidate(left, top, right, bottom) 方法 通过UI线程来刷新规定矩形区域。

 
  1. @Override  
  2. public void run() {  
  3.     while (mIsRunning) {  
  4.     try {  
  5.         Thread.sleep(100);  
  6.                    postInvalidate();  
  7.     } catch (InterruptedException e) {  
  8.         // TODO Auto-generated catch block  
  9.         e.printStackTrace();  
  10.     }  
  11.     }  
  12. }  

View中用到的双缓冲技术

        重绘的原理是 程序根据时间来刷新屏幕 如果有一帧图形还没有完全绘制结束 程序就开始刷新屏幕这样就会造成瞬间屏幕闪烁 画面很不美观,所以双缓冲的技术就诞生了。它存在的目的就是解决屏幕闪烁的问题,下面我说说在自定义View中如何实现双缓冲。

首先我们需要创建一张屏幕大小的缓冲图片,我说一下第三个参数 ARGB 分别代表的是 透明度   红色   绿色     蓝色

Bitmap.Config  ARGB_4444              ARGB  分别占四位  
Bitmap.Config  ARGB_8888              ARGB  分别占八位 
Bitmap.Config  RGB_565                没有透明度(A)   R占5位   G 占6位   B占5位    

一般情况下我们使用ARGB_8888 因为它的效果是最好了 当然它也是最占内存的。

  1. mBufferBitmap = Bitmap.createBitmap(mScreenWidth,mScreenHeight,Config.ARGB_8888);  


创建一个缓冲的画布,将内容绘制在缓冲区mBufferBitmap中

 
  1. Canvas mCanvas = new Canvas();  
  2. mCanvas.setBitmap(mBufferBitmap);  

最后一次性的把缓冲区mBufferBitmap绘制在屏幕上,怎么样 简单吧 呵呵。

 
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     /**这里先把所有须要绘制的资源绘制到mBufferBitmap上**/  
  4.     /**绘制地图**/  
  5.     DrawMap(mCanvas,mPaint,mBitmap);  
  6.     /**绘制动画**/  
  7.     RenderAnimation(mCanvas);  
  8.     /**更新动画**/  
  9.     UpdateAnimation();  
  10.       
  11.       
  12.     if(isBorderCollision) {  
  13.     DrawCollision(mCanvas,"与边界发生碰撞");  
  14.     }  
  15.       
  16.     if(isAcotrCollision) {  
  17.     DrawCollision(mCanvas,"与实体层发生碰撞");  
  18.     }  
  19.     if(isPersonCollision) {  
  20.     DrawCollision(mCanvas,"与NPC发生碰撞");  
  21.     }  
  22.       
  23.     /**最后通过canvas一次性的把mBufferBitmap绘制到屏幕上**/  
  24.     canvas.drawBitmap(mBufferBitmap, 0,0, mPaint);  
  25.     super.onDraw(canvas);  
  26. }  

         由此可见view属于被动刷新, 因为我们做的任何刷新的操作实际上都是通知UI线程去刷新。所以在做一些只有通过玩家操作以后才会刷新屏幕的游戏 并非自动刷新的游戏 可以使用view来操作。 

2.SurfaceView 

        从API中可以看出SurfaceView属于View的子类 它是专门为制作游戏而产生的,它的功能非常强大,最重要的是它支持OpenGL ES库,2D和3D的效果都可以实现。创建SurfaceView的时候需要实现SurfaceHolder.Callback接口,它可以用来监听SurfaceView的状态,SurfaceView的改变 SurfaceView的创建 SurfaceView 销毁  我们可以在相应的方法中做一些比如初始化的操作 或者 清空的操作等等。

       使用SurfaceView构建游戏框架它的绘制原理是绘制前先锁定画布 然后等都绘制结束以后 在对画布进行解锁 最后在把画布内容显示到屏幕上。     

代码中是如何实现SurfaceView

首先需要实现 Callback 接口 与Runnable接口 

 
  1. public class AnimView extends SurfaceView implements Callback,Runnable  

获取当前mSurfaceHolder 并且把它加到CallBack回调函数中

 
  1. SurfaceHolder  mSurfaceHolder = getHolder();  
  2. mSurfaceHolder.addCallback(this);  

        通过callBack接口监听SurfaceView的状态, 在它被创建的时候开启游戏的主线程,结束的时候销毁。这里说一下在View的构造函数中是拿不到view有关的任何信息的,因为它还没有构建好。 所以通过这个监听我们可以在surfaceCreated()中拿到当前view的属性 比如view的宽高 等等,所以callBack接口还是非常有用处的。

[java] view plaincopy
 
 
  1. @Override  
  2. public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,  
  3.     int arg3) {  
  4.     // surfaceView的大小发生改变的时候  
  5.       
  6. }  
  7.   
  8. @Override  
  9. public void surfaceCreated(SurfaceHolder arg0) {  
  10.     /**启动游戏主线程**/  
  11.     mIsRunning = true;  
  12.     mThread = new Thread(this);  
  13.     mThread.start();  
  14. }  
  15.   
  16. @Override  
  17. public void surfaceDestroyed(SurfaceHolder arg0) {  
  18.  // surfaceView销毁的时候  
  19.     mIsRunning = false;  
  20. }  

 

在游戏主线程循环中在绘制开始 先拿到画布canvas 并使用mSurfaceHolder.lockCanvas()锁定画布,等绘制结束以后 使用mSurfaceHolder.unlockCanvasAndPost(mCanvas)解锁画布,  解锁画布以后画布上的内容才会显示到屏幕上。

[java] view plaincopy
 
 
  1. @Override  
  2. public void run() {  
  3.     while (mIsRunning) {  
  4.     try {  
  5.         Thread.sleep(100);  
  6.     } catch (InterruptedException e) {  
  7.         // TODO Auto-generated catch block  
  8.         e.printStackTrace();  
  9.     }  
  10.       
  11.     //在这里加上线程安全锁  
  12.     synchronized (mSurfaceHolder) {  
  13.         /**拿到当前画布 然后锁定**/  
  14.         mCanvas =mSurfaceHolder.lockCanvas();    
  15.         Draw();  
  16.         /**绘制结束后解锁显示在屏幕上**/  
  17.         mSurfaceHolder.unlockCanvasAndPost(mCanvas);  
  18.     }  
  19.     }  
  20. }  

由此可见SurfaceView 属于主动刷新 ,重绘过程完全是在我们自己的线程中完成 , 由于游戏中肯定会执行各种绚丽的动画效果如果使用被动刷新的View就有可能就会阻塞UI线程,所以SurfaceView 更适合做游戏。

效果图

 

 

 

        最近有朋友反映说运行起来有点卡 我解释一下,  卡的主要原因是我的地图文件太大了,当然还有模拟器不给力的原因。我每绘制一块地图就须要使用裁剪原图,频繁的切割如此大的图片肯定会造成卡顿的情况。同学们在制作的时候将没用的地图块去掉,保留只需要的地图块这样会流畅很多喔 。

 

 

优化游戏主线程循环

 

        同学们先看看这段代码,Draw()方法绘制结束让线程等待100毫秒在进入下一次循环。其实这样更新游戏循环是很不科学的,原因是Draw()方法每一次更新所耗费的时间是不确定的。举个例子 比如第一次循环Draw() 耗费了1000毫秒 加上线程等待100毫秒 整个循环耗时1100毫秒,第二次循环Draw() 耗时2000毫秒 加上线程等待时间100毫秒 整个循环时间就是2100毫秒。很明显这样就会造成游戏运行刷新时间时快时慢,所以说它是很不科学的。

 

 
  1. public void run() {  
  2.     while (mIsRunning) {  
  3.     //在这里加上线程安全锁  
  4.     synchronized (mSurfaceHolder) {  
  5.         /**拿到当前画布 然后锁定**/  
  6.         mCanvas =mSurfaceHolder.lockCanvas();    
  7.         Draw();  
  8.         /**绘制结束后解锁显示在屏幕上**/  
  9.         mSurfaceHolder.unlockCanvasAndPost(mCanvas);  
  10.     }  
  11.     try {  
  12.         Thread.sleep(100);  
  13.     } catch (InterruptedException e) {  
  14.         e.printStackTrace();  
  15.     }  
  16.     }  
  17. }  



       在贴一段科学的控游戏制循环代码,每次循环游戏主线程 在Draw()方法前后计算出Draw()方法所消耗的时间,然后在判断是否达到我们规定的刷新屏幕时间,下例是以30帧刷新一次屏幕,如果满足则继续下次循环如果不满足使用Thread.yield(); 让游戏主线程去等待 并计算当前等待时间直到等待时间满足30帧为止在继续下一次循环刷新游戏屏幕。

 

这里说一下Thread.yield(): 与Thread.sleep(long millis):的区别,Thread.yield(): 是暂停当前正在执行的线程对象 ,并去执行其他线程。Thread.sleep(long millis):则是使当前线程暂停参数中所指定的毫秒数然后在继续执行线程。

 

 
  1.        /**每30帧刷新一次屏幕**/  
  2.        public static final int TIME_IN_FRAME = 30;  
  3. @Override  
  4. public void run() {  
  5.     while (mIsRunning) {  
  6.       
  7.     /**取得更新游戏之前的时间**/  
  8.     long startTime = System.currentTimeMillis();  
  9.       
  10.     /**在这里加上线程安全锁**/  
  11.     synchronized (mSurfaceHolder) {  
  12.         /**拿到当前画布 然后锁定**/  
  13.         mCanvas =mSurfaceHolder.lockCanvas();    
  14.         Draw();  
  15.         /**绘制结束后解锁显示在屏幕上**/  
  16.         mSurfaceHolder.unlockCanvasAndPost(mCanvas);  
  17.     }  
  18.       
  19.     /**取得更新游戏结束的时间**/  
  20.     long endTime = System.currentTimeMillis();  
  21.       
  22.     /**计算出游戏一次更新的毫秒数**/  
  23.     int diffTime  = (int)(endTime - startTime);  
  24.       
  25.     /**确保每次更新时间为30帧**/  
  26.     while(diffTime <=TIME_IN_FRAME) {  
  27.         diffTime = (int)(System.currentTimeMillis() - startTime);  
  28.         /**线程等待**/  
  29.         Thread.yield();  
  30.     }  
  31.       
  32.     }  
  33. }  
 Android显示系统之View与SurfaceView更新屏幕的区别
http://www.uml.org.cn/mobiledev/201209102.asp

1、View

View

extends Object

implements Drawable.Callback KeyEvent.Callback AccessibilityEventSource

java.lang.Object

android.view.View

  • Known Direct Subclasses(直接子类,SurfaceView是View的子类)

AnalogClock,ImageView,KeyboardView,MediaRouteButton,ProgressBar,Space,SurfaceView,TextView,TextureView,ViewGroup,ViewStu

  • Known Indirect Subclasses(间接子类)

AbsListView,AbsSeekBar,AbsSpinner,AbsoluteLayout,AdapterView<T extends Adapter>,AdapterViewAnimator,AdapterViewFlipper,AppWidgetHostView,AutoCompleteTextView, Button, CalendarView, CheckBox, CheckedTextView, Chronometer, and 53 others.

Class Overview

This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class forwidgets, which are used to create interactive UI components (buttons, text fields, etc.). TheViewGroup subclass is the base class forlayouts, which are invisible containers that hold other Views (or other ViewGroups) and define their layout properties.

View类为用户界面提供了最基础的组件,View类组件负责更换屏幕与处理事件。同时,View类也是widgets类的基础类,widgets类可以创建基础的UI组件,如Bottons、Textview等等。View类的其中一个直接子类ViewGroup是layous的基础类,layous是用来装载View或者其他的ViewGrous的,并且可以定义这些装载内容的特性。

2、 从上述的Overview可知,SurfaceView是继承于View类的,(GLSurfaceView是继承于SurfaceView的)。

Android更新屏幕主要有两种方式,继承SurfaceView实现SurfaceHolder.callback接口来实现屏幕的更新。

或者直接继承View类,复写OnDraw方法实现更新屏幕。

事实上,两种是用本质的区别的。

3、View与SurfaceView更新屏幕的区别

对于SurfaceView更新屏幕,是在非UI线程(主线程)中更新的。而对于View,则是在UI的主线程中更新画面。

那在UI的主线程中更新画面很容易造成主线程的堵塞,造成程序的长时间无响应,当主UI线程超过5秒钟没有响应用户的操作,Android系统会提示是否关闭应用程序。

当使用SurfaceView 来更新画面的话,就不必担心堵塞主UI线程这个问题了。但是这也带来了另外一个问题,线程的同步性。

所以当更新操作说花的时间较长,而且数据量较大的话,一般采用SurfaceView方式更新屏幕,而少用View。

4、Demo程序

/*
 * author: conowen
 * e-mail: conowen@hotmail.com
 * date  :  2012.8.8
 */
package com.conowen.viewtestdemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;

public class MyView extends View {

	private int counter;

	public MyView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		super.onDraw(canvas);
/*		synchronized (this) {
			try {
				wait(10 * 1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
*/
		// 设定Canvas对象的背景颜色
		canvas.drawColor(Color.YELLOW - counter);

		// 创建画笔
		Paint p = new Paint();
		// 设置画笔颜色
		p.setColor(Color.RED);
		// 设置文字大小
		p.setTextSize(40);
		// 消除锯齿
		p.setFlags(Paint.ANTI_ALIAS_FLAG);

		// 在canvas上绘制rect
		canvas.drawArc(new RectF(100, 50, 400, 350), 0, counter, true, p);
		if (counter == 400) {
			counter = 0;
		}

		canvas.drawText("counter = " + (counter++), 500, 200, p);
		// 重绘, 再一次执行onDraw 程序
		invalidate();

	}

}

效果图:

打开下面的代码,测试堵塞主UI线程(长按屏幕5秒以上)就会出现如下的图。

synchronized (this) {
			try {
				wait(10 * 1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

注意:

onDraw方法是运行于主UI线程中的,如果你在onDraw中执行invalidate()方法去更新屏幕,是可以的。但是你既要继承View而且要不希望堵塞主UI线程的话,可以另外新建线程,然后在线程中执行postInvalidate()方法去更新屏幕。也就是说invalidate()方法只能在主UI线程中被调用,postInvalidate()方法只能在非主UI线程中被调用。否则会出现如下error

08-08 15:33:34.587: E/AndroidRuntime(4995): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

这两个方法只是再次调用onDraw方法而已。

Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future. This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().

如下面的代码所示。这样的话,就不必担心主UI线程被堵塞了。

/*
 * author: conowen
 * e-mail: conowen@hotmail.com
 * date  :  2012.8.4
 */
package com.conowen.viewtestdemo;

import java.util.Timer;
import java.util.TimerTask;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;

public class MyView extends View {

    private int counter;
    private boolean isNewThread;
    private RectF rectf;
    private Paint p;
    private Timer timer;

    public MyView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        isNewThread = true;
        rectf = new RectF(100, 50, 400, 350);
        p = new Paint();
        timer = new Timer();
    }

    public void newThread() {

        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                postInvalidate();
                
            }
        }, 0, 100);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        if (isNewThread) {
            newThread();
            isNewThread = false;
        }
        // 设定Canvas对象的背景颜色
        canvas.drawColor(Color.YELLOW - counter);

        // 设置画笔颜色
        p.setColor(Color.RED);
        // 设置文字大小
        p.setTextSize(40);
        // 消除锯齿
        p.setFlags(Paint.ANTI_ALIAS_FLAG);

        // 在canvas上绘制rect
        canvas.drawArc(rectf, 0, counter, true, p);
        if (counter == 400) {
            counter = 0;
        }

        canvas.drawText("counter = " + (counter++), 500, 200, p);

    }

} 
 
原文地址:https://www.cnblogs.com/jukan/p/6052035.html