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) 更新指定矩形區域
- 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) {
-
- 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) {
-
-
- DrawMap(mCanvas,mPaint,mBitmap);
-
- RenderAnimation(mCanvas);
-
- UpdateAnimation();
-
-
- if(isBorderCollision) {
- DrawCollision(mCanvas,"與邊界發生碰撞");
- }
-
- if(isAcotrCollision) {
- DrawCollision(mCanvas,"與實體層發生碰撞");
- }
- if(isPersonCollision) {
- DrawCollision(mCanvas,"與NPC發生碰撞");
- }
-
-
- 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) {
-
-
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder arg0) {
-
- mIsRunning = true;
- mThread = new Thread(this);
- mThread.start();
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder arg0) {
-
- mIsRunning = false;
- }
在遊戲主線程循環中在繪製開始 先拿到畫布canvas 並使用mSurfaceHolder.lockCanvas()鎖定畫布,等繪製結束之後 使用mSurfaceHolder.unlockCanvasAndPost(mCanvas)解鎖畫布, 解鎖畫布之後畫布上的內容纔會顯示到屏幕上。
- @Override
- public void run() {
- while (mIsRunning) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
-
- 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):則是使當前線程暫停參數中所指定的毫秒數而後在繼續執行線程。
-
- 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);
-
-
- while(diffTime <=TIME_IN_FRAME) {
- diffTime = (int)(System.currentTimeMillis() - startTime);
-
- Thread.yield();
- }
-
- }
- }
一、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);
}
} |