SurfaceView入門(mén)

本章目錄

  • 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代碼模板

  1. 創(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() {

    }
}
  1. 初始化幾個(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);
    }
  1. 開(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);
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骨坑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子柬采,更是在濱河造成了極大的恐慌欢唾,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粉捻,死亡現(xiàn)場(chǎng)離奇詭異礁遣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)肩刃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)祟霍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)杏头,“玉大人,你說(shuō)我怎么就攤上這事沸呐〈纪酰” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵崭添,是天一觀的道長(zhǎng)厦画。 經(jīng)常有香客問(wèn)我,道長(zhǎng)滥朱,這世上最難降的妖魔是什么根暑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮徙邻,結(jié)果婚禮上排嫌,老公的妹妹穿的比我還像新娘。我一直安慰自己缰犁,他們只是感情好淳地,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著帅容,像睡著了一般颇象。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上并徘,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天遣钳,我揣著相機(jī)與錄音,去河邊找鬼麦乞。 笑死蕴茴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的姐直。 我是一名探鬼主播倦淀,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼声畏!你這毒婦竟也來(lái)了撞叽?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤插龄,失蹤者是張志新(化名)和其女友劉穎愿棋,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體辫狼,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡初斑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年辛润,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了膨处。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片见秤。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖真椿,靈堂內(nèi)的尸體忽然破棺而出鹃答,到底是詐尸還是另有隱情,我是刑警寧澤突硝,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布测摔,位于F島的核電站,受9級(jí)特大地震影響解恰,放射性物質(zhì)發(fā)生泄漏锋八。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一护盈、第九天 我趴在偏房一處隱蔽的房頂上張望挟纱。 院中可真熱鬧,春花似錦腐宋、人聲如沸紊服。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)欺嗤。三九已至,卻和暖如春卫枝,著一層夾襖步出監(jiān)牢的瞬間煎饼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工校赤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腺占,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓痒谴,卻偏偏與公主長(zhǎng)得像衰伯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子积蔚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容