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

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

1.viewandroid

 

view在api中的结构
git

Java.lang.Objectcanvas

Android.view.Viewapi

 

直接子类:安全

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

 

间接子类:ide

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刷新屏幕的几种方法。post

 

 

          第一种: 在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 plain copy
 
 
  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 plain copy
 
 
  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

一、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的,而且能够定义这些装载内容的特性。

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

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

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

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

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

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

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

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

因此当更新操做说花的时间较长,并且数据量较大的话,通常采用SurfaceView方式更新屏幕,而少用View。

四、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);

    }

}