網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
一、引入:
Android提供了View來進(jìn)行繪圖處理,在大部分情況下,View都能滿足繪圖需求。大家都知道View是通過刷新來重繪視圖,Android系統(tǒng)通過發(fā)出VSYNC信號(hào)來進(jìn)行屏幕的重繪,刷新的間隔時(shí)間為16ms。如果在16ms內(nèi)View完成了你所需要執(zhí)行的所有操作,那么用戶在視覺上,就不會(huì)產(chǎn)生卡頓的感覺;反之,如果操作的邏輯過多時(shí),就會(huì)掉幀從而使得用戶感覺到卡頓。特別的需要頻繁刷新的界面上,如游戲(60FPS以上),就會(huì)不斷阻塞主線程,從而導(dǎo)致界面卡頓。而Android提供了SurfaceView來解決這種情況。
二、SurfaceView和View的不同之處
View | SurfaceView |
適用于主動(dòng)更新 | 適用于被動(dòng)刷新 |
在主線程中進(jìn)行畫面更新 | 通常通過一個(gè)子線程來進(jìn)行畫面更新 |
繪圖中沒有使用雙緩沖機(jī)制 | 在底層實(shí)現(xiàn)中就實(shí)現(xiàn)了雙緩沖機(jī)制 |
比較了上面的不同之處,顯然可以發(fā)現(xiàn),如果一個(gè)View需要頻繁的刷新,或者在刷新時(shí)數(shù)據(jù)處理量大(可能引起卡頓),可以考慮使用SurfaceView來替代View。
三、SurfaceView的基本使用
SurfaceView在使用的過程中,有一套模板代碼,對(duì)于大部分的SurfaceView繪圖操作而言都可以套用,因此SurfaceView在使用過程中并不難。
其中值得注意的幾個(gè)點(diǎn):。
兩個(gè)接口
SurfaceHolder.CallBack
Runnable
第一個(gè)接口中需要實(shí)現(xiàn)的方法分別對(duì)應(yīng)于SurfaceView的生命周期,即創(chuàng)建、改變和銷毀。具體代碼如下:
//Surface的生命周期 @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { }
而第二接口需要實(shí)現(xiàn)run方法,用于在子線程中進(jìn)行draw操作。
由于SurfaceView的基本操作比較簡(jiǎn)單,這邊就直接給出了它的一個(gè)模板代碼
package com.pignet.surfaceviewdemo; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; /** * Created by DB on 2017/6/9. */ public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback,Runnable{ private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing; //構(gòu)造方法 public SurfaceViewTemplate(Context context) { super(context); initView(); } public SurfaceViewTemplate(Context context, AttributeSet attrs) { super(context, attrs); } public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } private void initView() { mHolder=getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing=true; new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing=false; } @Override public void run() { while (mIsDrawing){ draw(); //通過線程休眠以控制刷新速度 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } private void draw() { try { mCanvas=mHolder.lockCanvas(); //初始化畫布并在畫布上畫一些東西 }catch (Exception e){ }finally { //判斷畫布是否為空,從而避免黑屏情況 if(mCanvas!=null){ mHolder.unlockCanvasAndPost(mCanvas); } } } }
下面結(jié)合一個(gè)具體的示例,展現(xiàn)SurfaceView在繪圖中的效果(繪圖板,即通過監(jiān)聽觸摸事件完成內(nèi)容的繪制)。
package com.pignet.surfaceviewdemo; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; /** * Created by DB on 2017/6/9. */ public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback,Runnable { private static final String TAG="SurfaceView"; //SurfaceHolder private SurfaceHolder mHolder; //用于繪圖的Canvas private Canvas mCanvas; //子線程標(biāo)志位 private boolean mIsDrawing; //畫筆 private Paint mPaint; //路徑 private Path mPath; public SurfaceViewTemplate(Context context) { super(context); initView(); } public SurfaceViewTemplate(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } private void initView() { mHolder = getHolder(); //添加回調(diào) mHolder.addCallback(this); mPath=new Path(); //初始化畫筆 mPaint=new Paint(); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(6); mPaint.setAntiAlias(true); mPaint.setColor(Color.RED); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); } //Surface的生命周期 @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing=true; new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing=false; } @Override public void run() { long start =System.currentTimeMillis(); while(mIsDrawing){ draw(); long end = System.currentTimeMillis(); if(end-start<100){ try{ Thread.sleep(100-end+start); } catch (InterruptedException e) { e.printStackTrace(); } } } } private void draw() { try{ //鎖定畫布并返回畫布對(duì)象 mCanvas=mHolder.lockCanvas(); //接下去就是在畫布上進(jìn)行一下draw mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath,mPaint); }catch (Exception e){ }finally { //當(dāng)畫布內(nèi)容不為空時(shí),才post,避免出現(xiàn)黑屏的情況。 if(mCanvas!=null) mHolder.unlockCanvasAndPost(mCanvas); } } /** * 繪制觸摸滑動(dòng)路徑 * @param event MotionEvent * @return true */ @Override public boolean onTouchEvent(MotionEvent event) { int x=(int) event.getX(); int y= (int) event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.d(TAG, "onTouchEvent: down"); mPath.moveTo(x,y); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "onTouchEvent: move"); mPath.lineTo(x,y); break; case MotionEvent.ACTION_UP: Log.d(TAG, "onTouchEvent: up"); break; } return true; } /** * 清屏 * @return true */ public boolean reDraw(){ mPath.reset(); return true; } }
效果圖:
四、tips:
SurfaceView和View一大不同就是SurfaceView是被動(dòng)刷新的,但我們可以控制刷新的幀率,而View并且通過invalidate方法通知系統(tǒng)來主動(dòng)刷新界面的,但是View的刷新是依賴于系統(tǒng)的VSYSC信號(hào)的,其幀率并不受控制,而且因?yàn)閁I線程中的其他一些操作會(huì)導(dǎo)致掉幀卡頓。而對(duì)于SurfaceView而言,它是在子線程中繪制圖形,根據(jù)這一特性即可控制其顯示幀率,通過簡(jiǎn)單地設(shè)置休眠時(shí)間,即可,并且由于在子線程中,一般不會(huì)引起UI卡頓。
Thread.sleep(50);即可以控制1s內(nèi)刷新20次
SurfaceView的雙緩沖機(jī)制:即對(duì)于每一個(gè)SurfaceView對(duì)象而言,有兩個(gè)獨(dú)立的graphic buffer。在Android SurfaceView的雙緩沖機(jī)制中是這樣實(shí)現(xiàn)的:
在Buffer A中繪制內(nèi)容,然后讓屏幕顯示Buffer A;在下一個(gè)循環(huán)中,在Buffer B中繪制內(nèi)容,然后讓屏幕顯示Buffer B,如此往復(fù)。而由于這個(gè)雙緩沖機(jī)制的存在,可能會(huì)引起閃屏現(xiàn)象,。在第一個(gè)"lockCanvas-drawCanvas-unlockCanvasAndPost "循環(huán)中,更新的是buffer A的內(nèi)容;到下一個(gè)"lockCanvas-drawCanvas-unlockCanvasAndPost"循環(huán)中,更新的是buffer B的內(nèi)容。 如果buffer A與buffer B中某個(gè)buffer內(nèi)容為空,當(dāng)屏幕輪流顯示它們時(shí),就會(huì)出現(xiàn)畫面黑屏閃爍現(xiàn)象。
解決方法
出現(xiàn)黑屏是因?yàn)閎uffer A與buffer B中一者內(nèi)容為空,而且為空的一方還被post到了屏幕。于是有兩種解決思路:
1.不讓空buffer出現(xiàn):每次向一個(gè)buffer寫完內(nèi)容并post之后,順便用這個(gè)buffer的內(nèi)容填充另一個(gè)buffer。這樣能保證兩個(gè) buffer的內(nèi)容是同步的,缺點(diǎn)是做了無用功,耗費(fèi)性能。
2.不post空buffer到屏幕:當(dāng)準(zhǔn)備更新內(nèi)容時(shí),先判斷內(nèi)容是否為空,只有非空時(shí)才啟動(dòng)"lockCanvas-drawCanvas-unlockCanvasAndPost"這個(gè)流程。(上述模板和示例中即采用了這個(gè)方法)
原文鏈接:https://www.cnblogs.com/zhangyingai/p/7087371.html
相關(guān)推薦
- 2022-08-18 R語(yǔ)言使用cgdsr包獲取TCGA數(shù)據(jù)示例詳解_R語(yǔ)言
- 2023-03-01 Python中的getter與setter及deleter使用示例講解_python
- 2023-12-26 Caused by: org.apache.ibatis.binding.BindingExcept
- 2022-09-08 pandas刪除部分?jǐn)?shù)據(jù)后重新生成索引的實(shí)現(xiàn)_python
- 2024-02-16 springboot開啟mybatis二級(jí)緩存
- 2023-01-17 SqlServer事務(wù)語(yǔ)法及使用方法詳解_MsSql
- 2022-08-20 Docker容器修改端口映射的實(shí)現(xiàn)_docker
- 2022-06-11 shell編程中for循環(huán)語(yǔ)句的實(shí)現(xiàn)過程及案例_linux shell
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支