二十八、使用SurfaceView實現(xiàn)動畫

雖然前面介紹了使用自定義View來進(jìn)行繪圖,但View的繪圖機(jī)制存在如下兩個缺陷:

View缺乏雙緩存機(jī)制
當(dāng)程序需要更新View上的圖像時,程序必須重繪View上顯示的整張圖片。

由于View存在上面兩個缺陷,所以通過自定義View來實現(xiàn)繪圖、尤其是游戲中的繪圖時性能并不好。Android提供一個SurfaceView來代替View,在游戲繪圖方面,SurfaceView比View更出色,因此一般推薦SurfaceView。

SurfaceView的繪圖機(jī)制

SurfaceView一般會與SurfaceHolder結(jié)合使用,SurfaceHolder用于向與之關(guān)聯(lián)的 SurfaceVew上繪圖,調(diào)用 SurfaceView的 getholder()方法即可獲取SurfaceView關(guān)聯(lián)的SurfaceHolder。

Surface Holder提供了如下方法來獲取 Canvas對象。

Canvas lockCanvas():鎖定整個SurfaceView對象,獲取該 Surface上的 Cvanvas。
Canvas lockcanvas(Rect dirty):鎖定 Surfaceview上Rect劃分的區(qū)域,獲取該Surface上的 Canvas。

當(dāng)對同一個SurfaceView調(diào)用上面兩個方法時,兩個方法所返回的是同一個Canvas對象。但當(dāng)程序調(diào)用第二個方法獲取指定區(qū)域的Canvas時, SurfaceView將只對Rect所“圈”出來的區(qū)域進(jìn)行更新,通過這種方式可以提高畫面的更新速度。

當(dāng)通過lockcanvas()獲取指定了 SurfaceView上的Canvas之后,接下來程序就可以調(diào)用Canvas進(jìn)行繪圖了,Canvas繪圖完成后通過如下方法來釋放繪圖、提交所繪制的圖形:

unlockCanvasAndPost(canvas);

需要指出的是,當(dāng)調(diào)用 SurfaceHolder的unlockCanvasAndPost方法之后,該方法之前所繪制的圖形還處于緩沖之中,下一次 lockCanvas方法鎖定的區(qū)域可能會“遮擋”它。

下面的程序示范了SurfaceView的繪圖機(jī)制。

    //SurfaceHolder負(fù)責(zé)維護(hù)SurfaceView上繪制的內(nèi)容
    private SurfaceHolder holder;

    private Paint paint;

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity18);
        paint = new Paint();
        SurfaceView surfaceView = findViewById(R.id.show);
        //初始化SurfaceHolder對象
        holder = surfaceView.getHolder();
        holder.addCallback(new SurfaceHolder.Callback() {

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
            }

            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {
                //鎖定整個SurfaceView
                Canvas canvas = holder.lockCanvas();
                //繪制背景
                Bitmap back = BitmapFactory.decodeResource(Activity18.this.getResources(),
                        R.drawable.switcher5);
                //繪制背景
                canvas.drawBitmap(back, 0, 0, null);
                //繪制完成,釋放畫布,提交修改
                holder.unlockCanvasAndPost(canvas);
                //重新鎖一次,避免被下次lockCanvas遮擋
                holder.lockCanvas(new Rect(0,0,0,0));
                holder.unlockCanvasAndPost(canvas);
            }
            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
            }
        });
        //為Surface的觸摸事件綁定監(jiān)聽器
        surfaceView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent event) {
                //只處理按下事件
                if(event.getAction() == MotionEvent.ACTION_DOWN){
                    int cx = (int) event.getX();
                    int cy = (int) event.getY();
                    //鎖定SurfaceView的局部區(qū)域,只更新局部內(nèi)容
                    Canvas canvas = holder.lockCanvas(new Rect(cx - 50, cy - 50,
                            cx + 50, cy + 50));
                    //保存canvas的當(dāng)前狀態(tài)
                    canvas.save();
                    //旋轉(zhuǎn)畫布
                    canvas.rotate(30, cx, cy);
                    paint.setColor(Color.RED);
                    //繪制紅色方塊
                    canvas.drawRect(cx - 40, cy - 40, cx, cy, paint);
                    //恢復(fù)Canvas之前的保存狀態(tài)
                    canvas.restore();
                    paint.setColor(Color.GREEN);
                    //繪制綠色方塊
                    canvas.drawRect(cx, cy, cx + 40, cy + 40, paint);
                    //繪制完成,釋放畫布,提交修改
                    holder.unlockCanvasAndPost(canvas);
                }
                return false;
            }
        });
    }

如圖所示的第一次繪制的圖形被第二次的canvas“遮擋”了;第三次 lockCanvas時又可能“遮擋”第二次 lockCanvas的區(qū)域,但不可能“遮擋”第一次 lockCanvas的區(qū)域;如果第二次 lockCanvas“遮擋”的區(qū)域又被第三次lockcanvas所“遮擋”,那么原來第一次 drawCanvas所繪制的圖形可能“顯露”出來。

基于SurfaceView示波器

SurfaceView與普通View還有一個重要的區(qū)別:View的繪圖必須在當(dāng)前UI線程中進(jìn)行---這也是前面程序需要更新View組件時總要采用 Handler處理的原因;但SurfaceView就不會存在這個問題,因此 SurfaceView的繪圖是由 SurfaceHolder來完成的。

對于View組件,如果程序需要花較長的時間來更新繪圖,那么主UI線程將會被阻塞,無法響應(yīng)用戶的任何動作;而SurfaceViewHolder則會啟用新的線程去更新 Surface View的繪制,因此不會阻塞主UI線程。

一般來說,如果程序或游戲界面的動畫元素較多,而且很多都需要通過定時器來控制這些動畫元素的移動,就可以考慮使用SurfaceView,而不是View。例如下面我們使用Surface View開發(fā)一個示波器程序,該程序?qū)鶕?jù)用戶單擊的按鈕在屏幕上自動繪制正弦波或余弦波。

該程序的界面布局很簡單,界面布局中包含兩個按鈕和一個SurfaceView,程序每次繪制時只需要繪制(更新)當(dāng)前點的波形,前面已經(jīng)繪制的波形無須更新,這就利用了Surfaceholder的lockCanvas(Rect r)方法。

    private SurfaceHolder holder;

    private Paint paint;

    final int HEIGHT = 700;

    final int WIDTH = 700;

    final int X_OFFSET = 5;

    private int cx = X_OFFSET;
    //實際的Y軸的位置
    int centerY = HEIGHT / 2;

    Timer timer = new Timer();

    TimerTask task = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity19);
        final SurfaceView surface = findViewById(R.id.show);
        //初始化SurfaceHolder對象
        holder = surface.getHolder();
        paint = new Paint();
        paint.setColor(Color.RED);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(5);
        Button sin = findViewById(R.id.sin);
        Button cos = findViewById(R.id.cos);
        sin.setOnClickListener(this);
        cos.setOnClickListener(this);
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {
            }

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                drawBack(holder);
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
                timer.cancel();
            }
        });
    }

    @Override
    public void onClick(final View view) {
        drawBack(holder);
        cx = X_OFFSET;
        if(task != null){
            task.cancel();
        }
        task = new TimerTask() {
            @Override
            public void run() {
                int cy = view.getId() == R.id.sin ? centerY -
                        (int)(200 *  Math.sin((cx - 5) * 2 * Math.PI / 150))
                        : centerY - (int)(200 * Math.cos((cx - 5) * 2 * Math.PI / 150));
                Canvas canvas = holder.lockCanvas(new Rect(cx, cy - 2, cx + 2, cy + 2));
                canvas.drawPoint(cx, cy, paint);
                cx += 2;
                if(cx > WIDTH){
                    task.cancel();
                    task = null;
                }
                holder.unlockCanvasAndPost(canvas);
            }
        };
        timer.schedule(task, 0, 30);
    }

    private void drawBack(SurfaceHolder holder){
        Canvas canvas = holder.lockCanvas();
        canvas.drawColor(Color.WHITE);
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(2);
        paint.setAntiAlias(true);
        //繪制坐標(biāo)
        canvas.drawLine(X_OFFSET, centerY, WIDTH, centerY, paint);
        canvas.drawLine(X_OFFSET, 40, X_OFFSET, HEIGHT, paint);
        holder.unlockCanvasAndPost(canvas);
        holder.lockCanvas(new Rect(0,0,0,0));
        holder.unlockCanvasAndPost(canvas);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容