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) 更新指定矩形区域
- public void onDraw(Canvas canvas){
- DosomeThing();
- invalidate();
- }
第二种:使用postInvalidate();方法来刷新屏幕 ,调用后它会用handler通知UI线程重绘屏幕,我们可以 new Thread(this).start(); 开启一个游戏的主线程 然后在主线程中通过调用postInvalidate();方法来刷新屏幕。postInvalidate();方法 调用后 系统会帮我们调用onDraw方法 ,它是在我们自己的线程中调用 通过调用它可以通知UI线程刷新屏幕 。由此可见它是主动调用UI线程的。所以建议使用postInvalidate()方法通知UI线程来刷新整个屏幕。
postInvalidate(left, top, right, bottom) 方法 通过UI线程来刷新规定矩形区域。
- @Override
- public void run() {
- while (mIsRunning) {
- try {
- Thread.sleep(100);
- postInvalidate();
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
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 因为它的效果是最好了 当然它也是最占内存的。
- mBufferBitmap = Bitmap.createBitmap(mScreenWidth,mScreenHeight,Config.ARGB_8888);
创建一个缓冲的画布,将内容绘制在缓冲区mBufferBitmap中
- Canvas mCanvas = new Canvas();
- mCanvas.setBitmap(mBufferBitmap);
最后一次性的把缓冲区mBufferBitmap绘制在屏幕上,怎么样 简单吧 呵呵。
- @Override
- protected void onDraw(Canvas canvas) {
- /**这里先把所有须要绘制的资源绘制到mBufferBitmap上**/
- /**绘制地图**/
- DrawMap(mCanvas,mPaint,mBitmap);
- /**绘制动画**/
- RenderAnimation(mCanvas);
- /**更新动画**/
- UpdateAnimation();
- if(isBorderCollision) {
- DrawCollision(mCanvas,"与边界发生碰撞");
- }
- if(isAcotrCollision) {
- DrawCollision(mCanvas,"与实体层发生碰撞");
- }
- if(isPersonCollision) {
- DrawCollision(mCanvas,"与NPC发生碰撞");
- }
- /**最后通过canvas一次性的把mBufferBitmap绘制到屏幕上**/
- canvas.drawBitmap(mBufferBitmap, 0,0, mPaint);
- super.onDraw(canvas);
- }
由此可见view属于被动刷新, 因为我们做的任何刷新的操作实际上都是通知UI线程去刷新。所以在做一些只有通过玩家操作以后才会刷新屏幕的游戏 并非自动刷新的游戏 可以使用view来操作。
2.SurfaceView
从API中可以看出SurfaceView属于View的子类 它是专门为制作游戏而产生的,它的功能非常强大,最重要的是它支持OpenGL ES库,2D和3D的效果都可以实现。创建SurfaceView的时候需要实现SurfaceHolder.Callback接口,它可以用来监听SurfaceView的状态,SurfaceView的改变 SurfaceView的创建 SurfaceView 销毁 我们可以在相应的方法中做一些比如初始化的操作 或者 清空的操作等等。
使用SurfaceView构建游戏框架它的绘制原理是绘制前先锁定画布 然后等都绘制结束以后 在对画布进行解锁 最后在把画布内容显示到屏幕上。
代码中是如何实现SurfaceView
首先需要实现 Callback 接口 与Runnable接口
- public class AnimView extends SurfaceView implements Callback,Runnable
获取当前mSurfaceHolder 并且把它加到CallBack回调函数中
- SurfaceHolder mSurfaceHolder = getHolder();
- mSurfaceHolder.addCallback(this);
通过callBack接口监听SurfaceView的状态, 在它被创建的时候开启游戏的主线程,结束的时候销毁。这里说一下在View的构造函数中是拿不到view有关的任何信息的,因为它还没有构建好。 所以通过这个监听我们可以在surfaceCreated()中拿到当前view的属性 比如view的宽高 等等,所以callBack接口还是非常有用处的。
- @Override
- public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,
- int arg3) {
- // surfaceView的大小发生改变的时候
- }
- @Override
- public void surfaceCreated(SurfaceHolder arg0) {
- /**启动游戏主线程**/
- mIsRunning = true;
- mThread = new Thread(this);
- mThread.start();
- }
- @Override
- public void surfaceDestroyed(SurfaceHolder arg0) {
- // surfaceView销毁的时候
- mIsRunning = false;
- }
在游戏主线程循环中在绘制开始 先拿到画布canvas 并使用mSurfaceHolder.lockCanvas()锁定画布,等绘制结束以后 使用mSurfaceHolder.unlockCanvasAndPost(mCanvas)解锁画布, 解锁画布以后画布上的内容才会显示到屏幕上。
- @Override
- public void run() {
- while (mIsRunning) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- //在这里加上线程安全锁
- synchronized (mSurfaceHolder) {
- /**拿到当前画布 然后锁定**/
- mCanvas =mSurfaceHolder.lockCanvas();
- Draw();
- /**绘制结束后解锁显示在屏幕上**/
- mSurfaceHolder.unlockCanvasAndPost(mCanvas);
- }
- }
- }
由此可见SurfaceView 属于主动刷新 ,重绘过程完全是在我们自己的线程中完成 , 由于游戏中肯定会执行各种绚丽的动画效果如果使用被动刷新的View就有可能就会阻塞UI线程,所以SurfaceView 更适合做游戏。
效果图
最近有朋友反映说运行起来有点卡 我解释一下, 卡的主要原因是我的地图文件太大了,当然还有模拟器不给力的原因。我每绘制一块地图就须要使用裁剪原图,频繁的切割如此大的图片肯定会造成卡顿的情况。同学们在制作的时候将没用的地图块去掉,保留只需要的地图块这样会流畅很多喔 。
优化游戏主线程循环
同学们先看看这段代码,Draw()方法绘制结束让线程等待100毫秒在进入下一次循环。其实这样更新游戏循环是很不科学的,原因是Draw()方法每一次更新所耗费的时间是不确定的。举个例子 比如第一次循环Draw() 耗费了1000毫秒 加上线程等待100毫秒 整个循环耗时1100毫秒,第二次循环Draw() 耗时2000毫秒 加上线程等待时间100毫秒 整个循环时间就是2100毫秒。很明显这样就会造成游戏运行刷新时间时快时慢,所以说它是很不科学的。
- public void run() {
- while (mIsRunning) {
- //在这里加上线程安全锁
- synchronized (mSurfaceHolder) {
- /**拿到当前画布 然后锁定**/
- mCanvas =mSurfaceHolder.lockCanvas();
- Draw();
- /**绘制结束后解锁显示在屏幕上**/
- mSurfaceHolder.unlockCanvasAndPost(mCanvas);
- }
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
在贴一段科学的控游戏制循环代码,每次循环游戏主线程 在Draw()方法前后计算出Draw()方法所消耗的时间,然后在判断是否达到我们规定的刷新屏幕时间,下例是以30帧刷新一次屏幕,如果满足则继续下次循环如果不满足使用Thread.yield(); 让游戏主线程去等待 并计算当前等待时间直到等待时间满足30帧为止在继续下一次循环刷新游戏屏幕。
这里说一下Thread.yield(): 与Thread.sleep(long millis):的区别,Thread.yield(): 是暂停当前正在执行的线程对象 ,并去执行其他线程。Thread.sleep(long millis):则是使当前线程暂停参数中所指定的毫秒数然后在继续执行线程。
- /**每30帧刷新一次屏幕**/
- public static final int TIME_IN_FRAME = 30;
- @Override
- public void run() {
- while (mIsRunning) {
- /**取得更新游戏之前的时间**/
- long startTime = System.currentTimeMillis();
- /**在这里加上线程安全锁**/
- synchronized (mSurfaceHolder) {
- /**拿到当前画布 然后锁定**/
- mCanvas =mSurfaceHolder.lockCanvas();
- Draw();
- /**绘制结束后解锁显示在屏幕上**/
- mSurfaceHolder.unlockCanvasAndPost(mCanvas);
- }
- /**取得更新游戏结束的时间**/
- long endTime = System.currentTimeMillis();
- /**计算出游戏一次更新的毫秒数**/
- int diffTime = (int)(endTime - startTime);
- /**确保每次更新时间为30帧**/
- while(diffTime <=TIME_IN_FRAME) {
- diffTime = (int)(System.currentTimeMillis() - startTime);
- /**线程等待**/
- Thread.yield();
- }
- }
- }
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程序
/* |
效果图:
打开下面的代码,测试堵塞主UI线程(长按屏幕5秒以上)就会出现如下的图。
synchronized (this) { |
注意:
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线程被堵塞了。
/* |
view, surfaceView, invalidate, postInvalidate, 刷新屏幕的更多相关文章
- 解决:View调用invalidate()后不刷新onDraw()
近来学android图片处理,按照例子来,自定义一个View,之后在Activity里面手动调用该View的invalidate()后,一直无法刷新onDraw() 上网搜了一下,有两种解决办法: 一 ...
- android: View, SurfaceView, GLSurfaceView, TextureView 区别与联系
区别与联系 View: 显示视图,内置画布,提供了图形绘制函数.触屏事件.按键事件函数等,必须在UI主线程内更新画面,速度较慢: SurfaceView: 基于view视图进行拓展的视图类,更适合2D ...
- Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法
http://www.cnblogs.com/SkyD/archive/2010/11/08/1871423.html Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法 Surfa ...
- SurfaceView绘图时刷新问题,尝试各种办法无法解决,请教高手
/** * */ 源码:http://pan.baidu.com/s/1i3FtdZZ 画图时最左面,第一帧总是出现一个黑条,其它的帧没有问题package com.macrosoft.testewa ...
- android: requestLayout(), invalidate(), postInvalidate() 方法区别
一.invalidate和postInvalidate 这两个方法都是在重绘当前控件的时候调用的.invalidate在UI线程中调用,postInvalidate在非UI线程中调用.因为androi ...
- 能够在子线程绘画的View SurfaceView
转载请注明出处:王亟亟的大牛之路 近期两天都没有写文章,一方面是自己在看书.一方面不知道写什么,本来昨天想写Glide或者RxAndroid的东西结果公司的"狗屎"网怎么都刷不好G ...
- Android之View / SurfaceView / GLSurfaceView
Android游戏当中主要的除了控制类外就是显示类View.SurfaceView是从View基类中派生出来的显示类.android游戏开发中常用的三种视图是:view.SurfaceView和GLS ...
- 获取View的高度宽度,屏幕参数,状态栏高度
基础 各区域示例,注意绿色,紫色,橙色区域 注意: 在onCreate只是对象的初始创建过程,这时并没有draw,这时view.getHeight返回0,可在onStart里用getHeight等. ...
- android view surfaceView GLSurfaceView
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha 表面视图 SurfaceView 是 视图 的子类, 刷新界面速度比 视图 块, 因为它 ...
随机推荐
- WPF RichTextBox 控件常用方法和属性
以下内容转自 http://blog.csdn.net/yulongguiziyao/article/details/25330551. 1. 取得已被选中的内容: (1)使用 RichTextBox ...
- Shadow Mapping 的原理与实践(二)
3) 定义并生成Shadow Map纹理 texture2D Lamp0ShadowMapColor : RENDERCOLORTARGET < float2 ViewPortRatio = { ...
- Azure编程笔记(1):序列化复杂类型的TableEntity字段
内容提要 在使用MicrosoftAzure的CloudTable存储数据时,我们先要把数据定义成TableEntity的子类.假设TableEntity中包括复杂类型(比方容器类型如List等.或者 ...
- 我的学习笔记_Windows_HOOK编程 2009-12-03 11:19
一.什么是HOOK? "hook"这个单词的意思是"钩子","Windows Hook"是Windows消息处理机制的一个重要扩展,程序猿能 ...
- python类型转换、数值操作(转)
最近学习python语言,碰到数据类型间的转换问题.看到一篇文章总结的挺详细,收藏之备用. 类型转换 代码 Code highlighting produced by Actipro CodeHigh ...
- Perl 小知识之多行匹配
如果想匹配多行文本(即文本中有换行符)中的内容,比如: aaa ... bbb ... 要匹配aaa和bbb之间的内容,我们可以使用 /aaa.*bbb/s 其中/s修饰符可以让.匹配任何字符,包括换 ...
- MySQL数据库InnoDB存储引擎中的锁机制
MySQL数据库InnoDB存储引擎中的锁机制 http://www.uml.org.cn/sjjm/201205302.asp 00 – 基本概念 当并发事务同时访问一个资源的时候,有可能 ...
- IPC——消息队列
Linux进程间通信——使用消息队列 下面来说说如何用不用消息队列来进行进程间的通信,消息队列与命名管道有很多相似之处.有关命名管道的更多内容可以参阅我的另一篇文章:Linux进程间通信——使用命名管 ...
- Sleep v.s. sleep
Sleep函数_百度百科 http://baike.baidu.com/link?url=EmOxsG_du8HdLbv_9nDVlahpZOQ6DgkoLKooQInorAqIl_CEhCvBzTC ...
- Tuple类型
Tuple类型类似的体现了C#中的匿名类型 var person=new { Name="Eric"; Age=18: } 调用: Console.writeline( perso ...