Android系統(tǒng)view與SurfaceView的基本使用及區(qū)別分析
一、引入:
Android提供了View來進(jìn)行繪圖處理,在大部分情況下,View都能滿足繪圖需求。大家都知道View是通過刷新來重繪視圖,Android系統(tǒng)通過發(fā)出VSYNC信號來進(jìn)行屏幕的重繪,刷新的間隔時(shí)間為16ms。如果在16ms內(nèi)View完成了你所需要執(zhí)行的所有操作,那么用戶在視覺上,就不會產(chǎn)生卡頓的感覺;反之,如果操作的邏輯過多時(shí),就會掉幀從而使得用戶感覺到卡頓。特別的需要頻繁刷新的界面上,如游戲(60FPS以上),就會不斷阻塞主線程,從而導(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在使用的過程中,有一套模板代碼,對于大部分的SurfaceView繪圖操作而言都可以套用,因此SurfaceView在使用過程中并不難。
其中值得注意的幾個(gè)點(diǎn):。
兩個(gè)接口
SurfaceHolder.CallBack
Runnable
第一個(gè)接口中需要實(shí)現(xiàn)的方法分別對應(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的基本操作比較簡單,這邊就直接給出了它的一個(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{
//鎖定畫布并返回畫布對象
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信號的,其幀率并不受控制,而且因?yàn)閁I線程中的其他一些操作會導(dǎo)致掉幀卡頓。而對于SurfaceView而言,它是在子線程中繪制圖形,根據(jù)這一特性即可控制其顯示幀率,通過簡單地設(shè)置休眠時(shí)間,即可,并且由于在子線程中,一般不會引起UI卡頓。
Thread.sleep(50);即可以控制1s內(nèi)刷新20次
SurfaceView的雙緩沖機(jī)制:即對于每一個(gè)SurfaceView對象而言,有兩個(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ī)制的存在,可能會引起閃屏現(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í),就會出現(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è)方法)
以上就是Android系統(tǒng)view與SurfaceView的基本使用及區(qū)別分析的詳細(xì)內(nèi)容,更多關(guān)于Android view與SurfaceView使用區(qū)別的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 優(yōu)化之卡頓優(yōu)化的實(shí)現(xiàn)
這篇文章主要介紹了Android 優(yōu)化之卡頓優(yōu)化的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-07-07
解決Android啟動(dòng)APP的一瞬間系統(tǒng)欄會變成藍(lán)色問題
這篇文章主要介紹了解決Android啟動(dòng)APP的一瞬間系統(tǒng)欄會變成藍(lán)色問題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06
Android中DrawerLayout實(shí)現(xiàn)側(cè)滑菜單效果
這篇文章主要為大家詳細(xì)介紹了Android中DrawerLayout實(shí)現(xiàn)側(cè)滑菜單效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
Android中ACTION_CANCEL的觸發(fā)機(jī)制與滑出子view的情況
這篇文章主要介紹了Android中ACTION_CANCEL的觸發(fā)機(jī)制與滑出子view的情況,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
Android編程實(shí)用技術(shù)小結(jié)
這篇文章主要介紹了Android編程實(shí)用技術(shù),實(shí)例匯總了開機(jī)啟動(dòng)receiver、service使用、AlarmManager發(fā)送廣播及停止AlarmManager等相關(guān)技巧,需要的朋友可以參考下2016-01-01
Android Http實(shí)現(xiàn)文件的上傳和下載
這篇文章主要為大家詳細(xì)介紹了Android Http實(shí)現(xiàn)文件的上傳和下載,感興趣的小伙伴們可以參考一下2016-08-08
Android中DrawerLayout+ViewPager滑動(dòng)沖突的解決方法
這篇文章主要為大家詳細(xì)介紹了Android中DrawerLayout+ViewPager滑動(dòng)沖突的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Android 使用版本控制工具時(shí)添加忽略文件的方式(詳解)
下面小編就為大家?guī)硪黄狝ndroid 使用版本控制工具時(shí)添加忽略文件的方式(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-01-01

