本章目錄
- Part One:SurfaceView概述
- Part Two:SurfaceView代碼模板
- Part Three:SurfaceView案例
Part One:SurfaceView概述
通常情況下肴颊,與用戶交互的View和響應(yīng)時(shí)間都是在主線程,也就是UI線程去處理的泡一。而網(wǎng)絡(luò)訪問(wèn)之類(lèi)的耗時(shí)操作放在子線程中執(zhí)行厉膀,避免阻塞主線程溶耘。而耗時(shí)操作獲取到的數(shù)據(jù)想要更新UI,要么使用接口回調(diào)站蝠,要么用Handler之類(lèi)的發(fā)消息汰具。最終,所有更新UI的操作都要在主線程中執(zhí)行菱魔。
像我們一直在講的案例里留荔,我們使用了自定義View去繪制一些動(dòng)畫(huà),都是在主線程頻繁的刷新澜倦,此時(shí)會(huì)造成一個(gè)問(wèn)題:
以下摘自《安卓群英傳》
Android提供了View進(jìn)行繪圖處理聚蝶,View可以滿足大部分的繪圖需求,但在某些時(shí)候也會(huì)心有余而力不足藻治。我們知道碘勉,View通過(guò)刷新來(lái)重繪視圖,Android 系統(tǒng)通過(guò)發(fā)出VSYNC信號(hào)來(lái)進(jìn)行屏幕的重繪桩卵,刷新的時(shí)間間隔為16ms验靡。如果在16ms內(nèi)View完成了你所需要執(zhí)行的所有操作,那么用戶在視覺(jué)上就不會(huì)產(chǎn)生卡頓的感覺(jué)雏节;而如果執(zhí)行的操作邏輯太多胜嗓,特別是需要頻繁刷新的界面上,例如游戲界面钩乍,就會(huì)不斷阻塞主線程辞州,從而導(dǎo)致畫(huà)面卡頓。很多情況下寥粹,在自定義View的log中會(huì)看到如下的警告:
“Skipped 47 frames! The application may be doing too much work on its main thread.”
為了避免這一問(wèn)題的產(chǎn)生变过,Android系統(tǒng)提供了SurfaceView組件。
View和SurfaceView的區(qū)別:
- View主要使用于被動(dòng)更新涝涤,比如下棋游戲媚狰,間隔時(shí)間比較長(zhǎng);而SurfaceView主要適用于主動(dòng)更新阔拳,比如視圖本身頻繁刷新界面哈雏。
- View必須在UI的主線程中更新畫(huà)面,而surfaceView是在一個(gè)新起的單獨(dú)線程中可以重新繪制畫(huà)面。
- View 在繪圖時(shí)沒(méi)有使用雙緩沖機(jī)制裳瘪,而 SurfaceView 在底層實(shí)現(xiàn)機(jī)制中就已經(jīng)實(shí)現(xiàn)了雙緩沖機(jī)制土浸。(注:雙緩沖是2D游戲開(kāi)發(fā)中常用的一種機(jī)制)。
Part Two:SurfaceView代碼模板
- 創(chuàng)建一個(gè)自定義HeartSurfaceView繼承自SurfaceView彭羹,寫(xiě)好相應(yīng)的構(gòu)造方法黄伊;并且實(shí)現(xiàn)SurfaceHolder.Callback(在底層的Surface狀態(tài)發(fā)生變化的時(shí)候通知SurfaceView)和Runnable(子線程中要執(zhí)行的操作)接口,重寫(xiě)幾個(gè)方法派殷。
package com.terana.mycustomview.customview;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class HeartSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable{
public HeartSurfaceView(Context context) {
this(context, null);
}
public HeartSurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initVariable();
}
/**
* 初始化操作
*/
private void initVariable() {
}
/**
* 當(dāng)Surface第一次創(chuàng)建后會(huì)立即調(diào)用該函數(shù)还最。一般情況下都是在另外的線程來(lái)繪制界面,
* 所以不要在這個(gè)函數(shù)中繪制Surface毡惜。
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {}
/**
*當(dāng)Surface的狀態(tài)(大小和格式)發(fā)生變化的時(shí)候會(huì)調(diào)用該函數(shù)拓轻,
* 在surfaceCreated調(diào)用后該函數(shù)至少會(huì)被調(diào)用一次。
* 類(lèi)似于自定義View的onSizeChanged
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
/**
* 當(dāng)Surface被摧毀前會(huì)調(diào)用該函數(shù)经伙,該函數(shù)被調(diào)用后就不能繼續(xù)使用Surface了扶叉,
* 一般在該函數(shù)中來(lái)清理使用的資源。
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {}
/**
* 子線程中要執(zhí)行的業(yè)務(wù)
*/
@Override
public void run() {
}
}
- 初始化幾個(gè)通用屬性帕膜,為將來(lái)繪制作準(zhǔn)備枣氧。除了下面寫(xiě)的之外,可根據(jù)具體業(yè)務(wù)自行添加屬性垮刹。
private SurfaceHolder surfaceHolder;//用于獲取Surface
private Canvas canvas;//繪圖所使用的Canvas
private boolean isDrawing;//用于判斷子線程是否正在繪制中达吞,true是正在繪制,false是繪制完成
public HeartSurfaceView(Context context) {
this(context, null);
}
public HeartSurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initVariable();
}
/**
* 初始化操作
*/
private void initVariable() {
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);//注冊(cè)回調(diào)
setZOrderOnTop(true);//設(shè)置畫(huà)布 背景透明
surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
}
- 開(kāi)始繪制
首先荒典,用SurfaceHolder的Canvas lockCanvas()方法或者Canvas lockCanvas(Rect dirty)方法獲取Canvas對(duì)象酪劫。如果不需要緩存,用lockCanvas()或者lockCanvas(null)獲取對(duì)象即可寺董;如果只需要重繪變化的部分覆糟,可以調(diào)用lockCanvas(Rect dirty)函數(shù)來(lái)指定一個(gè)dirty區(qū)域,這樣該區(qū)域外的內(nèi)容會(huì)緩存起來(lái)螃征。
然后搪桂,在子線程中使用Canvas對(duì)象進(jìn)行繪制透敌,具體邏輯等同于自定義View的onDraw方法盯滚。
最后,調(diào)用unlockCanvasAndPost(Canvas canvas)提交酗电,通知系統(tǒng)Surface已經(jīng)繪制完成魄藕,這樣系統(tǒng)會(huì)把繪制完的內(nèi)容顯示出來(lái)。因?yàn)樵谡{(diào)用lockCanvas函數(shù)獲取Canvas后撵术,SurfaceView會(huì)獲取Surface的一個(gè)同步鎖直到調(diào)用unlockCanvasAndPost(Canvas canvas)函數(shù)才釋放該鎖背率,這種同步機(jī)制保證在Surface繪制過(guò)程中不會(huì)被改變(被摧毀、修改)。
package com.terana.mycustomview.customview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class HeartSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder surfaceHolder;//用于獲取Surface
private Canvas canvas;//繪圖所使用的Canvas
private boolean isDrawing;//用于判斷子線程是否正在繪制中寝姿,true是正在繪制交排,false是繪制完成
public HeartSurfaceView(Context context) {
this(context, null);
}
public HeartSurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initVariable();
}
/**
* 初始化操作
*/
private void initVariable() {
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);//注冊(cè)回調(diào)
setZOrderOnTop(true);//設(shè)置畫(huà)布 背景透明
surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
}
/**
* 當(dāng)Surface第一次創(chuàng)建后會(huì)立即調(diào)用該函數(shù)。一般情況下都是在另外的線程來(lái)繪制界面饵筑,
* 所以不要在這個(gè)函數(shù)中繪制Surface埃篓。
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
//開(kāi)始繪制
isDrawing = true;
}
/**
* 當(dāng)Surface的狀態(tài)(大小和格式)發(fā)生變化的時(shí)候會(huì)調(diào)用該函數(shù),
* 在surfaceCreated調(diào)用后該函數(shù)至少會(huì)被調(diào)用一次根资。
* 類(lèi)似于自定義View的onSizeChanged
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
new Thread(this).start();
}
/**
* 當(dāng)Surface被摧毀前會(huì)調(diào)用該函數(shù)架专,該函數(shù)被調(diào)用后就不能繼續(xù)使用Surface了,
* 一般在該函數(shù)中來(lái)清理使用的資源玄帕。
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//結(jié)束繪制
isDrawing = false;
}
/**
* 子線程中要執(zhí)行的業(yè)務(wù)
*/
@Override
public void run() {
while (isDrawing) {
drawOnCanvas();
}
}
//具體的繪制邏輯
private void drawOnCanvas() {
//用try{}catch{}finally{}包裹起來(lái)部脚,保證無(wú)論發(fā)生何種意外,都能講Canvas提交
try {
canvas = surfaceHolder.lockCanvas();//獲得Canvas對(duì)象
if (canvas != null) {
//繪制背景
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//TODO 繪圖操作
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (canvas != null){
surfaceHolder.unlockCanvasAndPost(canvas); }
}
}
}
之后裤纹,在TODO部分進(jìn)行具體的繪制即可委刘。
Part Three:SurfaceView案例
package com.terana.mycustomview.customview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.terana.mycustomview.R;
import java.util.ArrayList;
import java.util.List;
public class HeartSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder surfaceHolder;//用于獲取Surface
private Canvas canvas;//繪圖所使用的Canvas
private boolean isDrawing;//用于判斷子線程是否正在繪制中,true是正在繪制服傍,false是繪制完成
private int offsetX;//偏移量钱雷,為了讓桃心處于屏幕正中央
private int offsetY;
private Bitmap bitmapFlower;
private RectF rectF;
public HeartSurfaceView(Context context) {
this(context, null);
}
public HeartSurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initVariable();
}
/**
* 初始化操作
*/
private void initVariable() {
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);//注冊(cè)回調(diào)
setZOrderOnTop(true);//設(shè)置畫(huà)布 背景透明
surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
bitmapFlower = BitmapFactory.decodeResource(getResources(), R.drawable.flower);
rectF = new RectF();
}
/**
* 當(dāng)Surface第一次創(chuàng)建后會(huì)立即調(diào)用該函數(shù)。一般情況下都是在另外的線程來(lái)繪制界面吹零,
* 所以不要在這個(gè)函數(shù)中繪制Surface罩抗。
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
//開(kāi)始繪制
isDrawing = true;
}
/**
* 當(dāng)Surface的狀態(tài)(大小和格式)發(fā)生變化的時(shí)候會(huì)調(diào)用該函數(shù),
* 在surfaceCreated調(diào)用后該函數(shù)至少會(huì)被調(diào)用一次灿椅。
* 類(lèi)似于自定義View的onSizeChanged
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
offsetX = width / 2;
offsetY = height / 2 - 88;
new Thread(this).start();
}
/**
* 當(dāng)Surface被摧毀前會(huì)調(diào)用該函數(shù)套蒂,該函數(shù)被調(diào)用后就不能繼續(xù)使用Surface了,
* 一般在該函數(shù)中來(lái)清理使用的資源茫蛹。
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//結(jié)束繪制
isDrawing = false;
}
/**
* 子線程中要執(zhí)行的業(yè)務(wù)
*/
@Override
public void run() {
float angle = 10;
while (true) {
Point p = getHeartPoint(angle);
//循環(huán)比較新的坐標(biāo)位置是否可以創(chuàng)建花朵操刀,為了防止花朵太密集
for (int i = 0; i < heartPoints.size(); i++) {
Point bp = heartPoints.get(i);
float distance = (float) Math.sqrt(Math.pow(p.x - bp.x, 2) + Math.pow(p.y - bp.y, 2));
if (distance < 48) {
isDrawing = false;
break;
}
}
heartPoints.add(p);
if (angle >= 30) {
break;
} else {
angle = angle + 0.2f;
}
drawOnCanvas();
}
}
//具體的繪制邏輯
private void drawOnCanvas() {
//用try{}catch{}finally{}包裹起來(lái),保證無(wú)論發(fā)生何種意外婴洼,都能講Canvas提交
try {
canvas = surfaceHolder.lockCanvas();//獲得Canvas對(duì)象
if (canvas != null) {
//繪制背景
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//TODO 繪圖操作
for (int i = 0; i < heartPoints.size(); i++) {
rectF.left = heartPoints.get(i).x - 24;
rectF.top = heartPoints.get(i).y - 24;
rectF.bottom = rectF.top + 48;
rectF.right = rectF.left + 48;
Paint p = new Paint();
p.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#389deb"), PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmapFlower, null, rectF, p);
}
Thread.sleep(20);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
private List<Point> heartPoints = new ArrayList<>();
/**
* 桃心線位置
*/
public Point getHeartPoint(float angle) {
float t = (float) (angle / Math.PI);
float x = (float) (32 * (16 * Math.pow(Math.sin(t), 3)));
float y = (float) (-32 * (13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t)));
return new Point(offsetX + (int) x, offsetY + (int) y);
}
}