1.view
view在api中的结构
java.lang.Object
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类属于Anroid开发绘制中的显示老大,任何与绘制有关系的控件都是它的子类。这篇文章主要讲View 与SurFaceView 使用线程刷新屏幕绘制方面的知识。开发中如何去选择使用View还是SurFaceView。
下面详细的说明一下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线程来刷新规定矩形区域。
5 @Override
6 public void run() {
7 while (mIsRunning) {
8 try {
9 Thread.sleep(100);
10 postInvalidate();
11 } catch (InterruptedException e) {
12 // TODO Auto-generated catch block
13 e.printStackTrace();
14 }
15 }
16 }
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 因为它的效果是最好了 当然它也是最占内存的。
17 mBufferBitmap = Bitmap.createBitmap(mScreenWidth,mScreenHeight,Config.ARGB_8888);
创建一个缓冲的画布,将内容绘制在缓冲区mBufferBitmap中
18 Canvas mCanvas = new Canvas();
19 mCanvas.setBitmap(mBufferBitmap);
最后一次性的把缓冲区mBufferBitmap绘制在屏幕上,怎么样 简单吧 呵呵。
20 @Override
21 protected void onDraw(Canvas canvas) {
22 /**这里先把所有须要绘制的资源绘制到mBufferBitmap上**/
23 /**绘制地图**/
24 DrawMap(mCanvas,mPaint,mBitmap);
25 /**绘制动画**/
26 RenderAnimation(mCanvas);
27 /**更新动画**/
28 UpdateAnimation();
29
30
31 if(isBorderCollision) {
32 DrawCollision(mCanvas,"与边界发生碰撞");
33 }
34
35 if(isAcotrCollision) {
36 DrawCollision(mCanvas,"与实体层发生碰撞");
37 }
38 if(isPersonCollision) {
39 DrawCollision(mCanvas,"与NPC发生碰撞");
40 }
41
42 /**最后通过canvas一次性的把mBufferBitmap绘制到屏幕上**/
43 canvas.drawBitmap(mBufferBitmap, 0,0, mPaint);
44 super.onDraw(canvas);
45 }
由此可见view属于被动刷新, 因为我们做的任何刷新的操作实际上都是通知UI线程去刷新。所以在做一些只有通过玩家操作以后才会刷新屏幕的游戏 并非自动刷新的游戏 可以使用view来操作。
2.SurfaceView
从API中可以看出SurfaceView属于View的子类 它是专门为制作游戏而产生的,它的功能非常强大,最重要的是它支持OpenGL ES库,2D和3D的效果都可以实现。创建SurfaceView的时候需要实现SurfaceHolder.Callback接口,它可以用来监听SurfaceView的状态,SurfaceView的改变 SurfaceView的创建 SurfaceView 销毁 我们可以在相应的方法中做一些比如初始化的操作 或者 清空的操作等等。
使用SurfaceView构建游戏框架它的绘制原理是绘制前先锁定画布 然后等都绘制结束以后 在对画布进行解锁 最后在把画布内容显示到屏幕上。
代码中是如何实现SurfaceView
首先需要实现 Callback 接口 与Runnable接口
46 public class AnimView extends SurfaceView implements Callback,Runnable
获取当前mSurfaceHolder 并且把它加到CallBack回调函数中
47 SurfaceHolder mSurfaceHolder = getHolder();
48 mSurfaceHolder.addCallback(this);
通过callBack接口监听SurfaceView的状态, 在它被创建的时候开启游戏的主线程,结束的时候销毁。这里说一下在View的构造函数中是拿不到view有关的任何信息的,因为它还没有构建好。 所以通过这个监听我们可以在surfaceCreated()中拿到当前view的属性 比如view的宽高 等等,所以callBack接口还是非常有用处的。
49 @Override
50 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,
51 int arg3) {
52 // surfaceView的大小发生改变的时候
53
54 }
55
56 @Override
57 public void surfaceCreated(SurfaceHolder arg0) {
58 /**启动游戏主线程**/
59 mIsRunning = true;
60 mThread = new Thread(this);
61 mThread.start();
62 }
63
64 @Override
65 public void surfaceDestroyed(SurfaceHolder arg0) {
66 // surfaceView销毁的时候
67 mIsRunning = false;
68 }
在游戏主线程循环中在绘制开始 先拿到画布canvas 并使用mSurfaceHolder.lockCanvas()锁定画布,等绘制结束以后 使用mSurfaceHolder.unlockCanvasAndPost(mCanvas)解锁画布, 解锁画布以后画布上的内容才会显示到屏幕上。
69 @Override
70 public void run() {
71 while (mIsRunning) {
72 try {
73 Thread.sleep(100);
74 } catch (InterruptedException e) {
75 // TODO Auto-generated catch block
76 e.printStackTrace();
77 }
78
79 //在这里加上线程安全锁
80 synchronized (mSurfaceHolder) {
81 /**拿到当前画布 然后锁定**/
82 mCanvas =mSurfaceHolder.lockCanvas();
83 &nbs