Android SurfaceView入門學習


學習資料:

  • Android 開發群英傳

搜索學習資料時,搜到了羅升陽老師的Android視圖SurfaceView的實現原理分析,老羅老師寫的一系列博客,一年前開始學習Android時看不懂,現在依然看不懂,感覺涉及到的知識面太廣並且非常深入,還得需要積累很多知識后才能看得懂

在包建強老師的博客中看到,說老羅的博客不是給開發App的人寫的,是給開發Rom的人寫的。哈哈,為看不懂找個心安理得的理由,推薦包老師的系列博客

寫給Android App開發人員看的Android底層知識


1. SurfaceView

 

View通過刷新來重繪視圖,Android系統通過發出VSYNC信號來進行屏幕的重繪,刷新的時間間隔為16ms

在一些需要頻繁刷新,執行很多邏輯操作的時候,超過了16ms,就會導致卡頓

SurfaceView繼承之View,但擁有獨立的繪制表面,即它不與其宿主窗口共享同一個繪圖表面,可以單獨在一個線程進行繪制,並不會占用主線程的資源。這樣,繪制就會比較高效,游戲,視頻播放,還有最近熱門的直播,都可以用SurfaceView

SurfaceView有兩個子類GLSurfaceViewVideoView


SurfaceViewView的區別:

  1. View主要適用於主動更新的情況下,而SurfaceView主要適用於被動更新,例如頻繁地刷新
  2. View在主線程中對畫面進行刷新,而SurfaceView通常會通過一個子線程來進行頁面的刷新
  3. View在繪圖時沒有使用雙緩沖機制,而SufaceView在底層實現機制中就已經實現了雙緩沖機制

如果自定義View需要頻繁刷新,或者刷新時數據處理量比較大,就 可以考慮使用SurfaceView來取代View了


2. SurfaceView的使用模板


SurfaceView使用過程有一套模板代碼,大部分的SurfaceView都可以套用

3步走套路:

  1. 創建SurfaceView
  2. 初始化SurfaceView
  3. 使用SurfaceView

2.1 創建SurfaceView

 

創建一個自定義的SurfaceViewL,繼承之SurfaceView,並實現兩個接口SurfaceHolder.CallBackRunnable

代碼:

public class SurfaceViewL extends SurfaceView implements SurfaceHolder.Callback,Runnable{ public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void surfaceCreated(SurfaceHolder holder) {//創建 } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//改變 } @Override public void surfaceDestroyed(SurfaceHolder holder) {//銷毀 } @Override public void run() { } }

SurfaceHolder.CallBack有3個方法,分別在SurfaceView創建,改變,銷毀時進行回調

SurfaceHolder.CallBack還有一個子Callback2接口,里面添加了一個surfaceRedrawNeeded (SurfaceHolder holder)方法

當需要重繪SurfaceView中的內容時,可以使用這個接口。目前還不了解具體的使用場景


2.2 初始化SurfaceView

在自定義的SurfaceView中,通常需要3個成員變量

  1. SurfaceHolder mSurfaceHolder 可以控制SurfaceView的大小,格式,可以監控或者改變SurfaceView
  2. Canvas mCanvas 畫布
  3. boolean isDrawing 子線程標志位,用來控制子線程

在構造方法中,對SurfaceHolder mSurfaceHolder進行初始化

public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mSurfaceHolder = getHolder();//得到SurfaceHolder對象 mSurfaceHolder.addCallback(this);//注冊SurfaceHolder setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true);//保持屏幕長亮 }
  • setFocusable(true) 能否獲得焦點
  • setFocusableInTouchMode(true) 能否通過觸摸獲得焦點

這兩個方法都是View類的方法,可以看看setFocusable與setFocusableInTouchMode差異以及clickable


2.3 使用SurfaceView


利用在2.2拿到的mSurfaceHolder對象,通過lockCanvas()方法獲得當前的Canvas

注意:
lockCanvas()獲取到的Canvas對象還是上次的Canvas對象,並不是一個新的對象。之前的繪圖都將被保留,如果需要擦除,可以在繪制之前通過drawColor()方法來進行清屏

繪制要充分利用SurfaceView的三個回調方法,在surfaceCreate()方法中開啟子線程進行繪制。在子線程中,使用一個while(isDrawing)循環來不停地繪制。具體的繪制過程,由lockCanvas()方法進行繪制,並通過unlockCanvasAndPost(mCanvas)進行畫布內容的提交


2.4 完整的模板代碼

 

public class SurfaceViewL extends SurfaceView implements SurfaceHolder.Callback, Runnable { // SurfaceHolder private SurfaceHolder mSurfaceHolder; // 畫布 private Canvas mCanvas; // 子線程標志位 private boolean isDrawing; public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); } @Override public void surfaceCreated(SurfaceHolder holder) {//創建 isDrawing = true; new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//改變 } @Override public void surfaceDestroyed(SurfaceHolder holder) {//銷毀 isDrawing = false; } @Override public void run() { while (isDrawing){ drawing(); } } private void drawing() { try { mCanvas = mSurfaceHolder.lockCanvas(); //這里進行內容的繪制 ... }finally { if (mCanvas != null){ mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } } }

mSurfaceHolder.unlockCanvasAndPost(mCanvas)將這行代碼放入finally代碼塊中,目的是為了確保內容都能夠被提交


3. 簡單使用


效果還是一個簡易的畫圖板


哈哈,惡搞一下

 

 

 

 

 

 

 

public class SurfaceViewL extends SurfaceView implements SurfaceHolder.Callback, Runnable { // SurfaceHolder private SurfaceHolder mSurfaceHolder; // 畫布 private Canvas mCanvas; // 子線程標志位 private boolean isDrawing; // 畫筆 Paint mPaint; // 路徑 Path mPath; private float mLastX, mLastY;//上次的坐標 public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 初始化 */ private void init() { //初始化 SurfaceHolder mSurfaceHolder mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); //畫筆 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaint.setStrokeWidth(10f); mPaint.setColor(Color.parseColor("#FF4081")); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeCap(Paint.Cap.ROUND); //路徑 mPath = new Path(); } @Override public void surfaceCreated(SurfaceHolder holder) {//創建 isDrawing = true; Log.e("surfaceCreated","--"+isDrawing); //繪制線程 new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//改變 } @Override public void surfaceDestroyed(SurfaceHolder holder) {//銷毀 isDrawing = false; Log.e("surfaceDestroyed","--"+isDrawing); } @Override public void run() { while (isDrawing){ drawing(); } } /** * 繪制 */ private void drawing() { try { mCanvas = mSurfaceHolder.lockCanvas(); mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath,mPaint); } finally { if (mCanvas != null) { mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastX = x; mLastY = y; mPath.moveTo(mLastX, mLastY); break; case MotionEvent.ACTION_MOVE: float dx = Math.abs(x - mLastX); float dy = Math.abs(y - mLastY); if (dx >= 3 || dy >= 3) { mPath.quadTo(mLastX, mLastY, (mLastX + x) / 2, (mLastY + y) / 2); } mLastX = x; mLastY = y; break; case MotionEvent.ACTION_UP: break; } return true; } /** * 測量 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int wSpecMode = MeasureSpec.getMode(widthMeasureSpec); int wSpecSize = MeasureSpec.getSize(widthMeasureSpec); int hSpecMode = MeasureSpec.getMode(heightMeasureSpec); int hSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(300, 300); } else if (wSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(300, hSpecSize); } else if (hSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(wSpecSize, 300); } } }

代碼主要是從徐醫生的Android群英傳中學到的。

由於while (isDrawing)是個死循環,drawing方法一直在執行,就導致一直在繪制,徐醫生給了一個優化的方案,對run()方法進行了修改

@Override public void run() { Log.e("drawing","--"+111111); long start = System.currentTimeMillis(); while (isDrawing) { drawing(); } long end = System.currentTimeMillis(); if (end- start < 100){ try { Log.e("drawing","--"+22222); Thread.sleep(100-(end-start)); } catch (InterruptedException e) { e.printStackTrace(); } } }

但對於優化后run()方法有些疑問,Thread.sleep(100-(end-start))感覺這行代碼並不執行到,因為isDrawing = true,是個死循環,只有surfaceDestroyed(SurfaceHolder holder)方法執行時,isDrawing = false,之后才可以執行到sleep()方法

根據個人的理解,修改了代碼:

public void run() { while (isDrawing) { Log.e("drawing","--"+111111); long start = System.currentTimeMillis(); drawing(); long end = System.currentTimeMillis(); if (end- start < 100){ try { Log.e("drawing","--"+22222); Thread.sleep(100-(end-start)); } catch (InterruptedException e) { e.printStackTrace(); } } } }

這樣修改后,雖然sleep()方法能執行到,但繪制過程有種不跟手的感覺,個人感覺並不用優化,在run()方法中


4. 自己想的優化

 


既然不想時時刻在繪制,那就只有在手指在屏幕滑動時,進行繪制

修改代碼:

// 修改 1 @Override public void surfaceCreated(SurfaceHolder holder) {//創建 drawing(); } //修改onTouchEvent(MotionEvent event)方法 //修改 2 case MotionEvent.ACTION_DOWN: isDrawing = true ;//每次開始將標記設置為ture new Thread(this).start();//開啟線程 mLastX = x; mLastY = y; mPath.moveTo(mLastX, mLastY); break; //修改3 case MotionEvent.ACTION_UP: isDrawing = false;//每次結束將標記設置為false break; //修改4 run()方法 @Override public void run() { while (isDrawing){ drawing(); } }

手指落下,將標記設置為true,並開啟線程
手指離開,將標記設置為false,循環結束后,線程也就停止


5. 最后

關於4的優化,哪位同學有好的建議,請留言

又是周末,周末愉快

本人很菜,有錯誤請指出

共勉 : )



作者:英勇青銅5
鏈接:http://www.jianshu.com/p/15060fc9ef18
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM