音視訊入門系列之繪製圖片三種方式

蒼溟發表於2018-08-28

本文由玉剛說寫作平臺提供寫作贊助,版權歸玉剛說微信公眾號所有
原作者:蒼溟
版權宣告:未經玉剛說許可,不得以任何形式轉載

在 Android 音視訊開發學習思路 裡面,我們寫到了,想要逐步入門音視訊開發,就需要一步步的去學習整理,並積累。本文是音視訊開發積累的第一篇。 對應的要學習的內容是:在 Android 平臺繪製一張圖片,使用3 種不同的 API,ImageView、SurfaceView、自定義 View。

ImageView 繪製圖片

這個想必做過Android開發的都知道如何去繪製了。很簡單:

//  ImageView 載入幾種來源 
//(1) drawable/mipmap 中通過 R.drawabe.xxx 載入圖片資源
//(2) assests或者sdcard的路徑的資源
// 這裡我以 assests 代表來載入資源
ImageView customImageView = findViewById(R.id.img_middle);
customImageView.setImageBitmap(Util.getImageFromAssetsFile(this"prettygirl.png"));
// Util.getImageFromAssetsFile(this, "prettygirl.png")
public static Bitmap getImageFromAssetsFile(Context context, String fileName) {
    Bitmap image = null;
    AssetManager am = context.getResources().getAssets();
    try {
        InputStream is = am.open(fileName);
        image = BitmapFactory.decodeStream(is);
        is.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return image;
}
複製程式碼

很輕鬆,在介面上清晰的看到養眼的美女:

avatar
avatar

自定義 View 繪製圖片

有些時候我們需要載入圖片需要自定義View來載入圖片,通過上邊載入圖片的方式加上我自己自定義View 經驗,我們很快能寫出下邊通過自定義View的程式碼來載入圖片。

/**
 * 自定義View 顯示圖片
 */

public class CustomImageView extends View {
    private Bitmap mBitmap;
    private Paint mPaint = new Paint();

    ···

    private void init() {
        initPaint();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        //設定抗鋸齒
        mPaint.setAntiAlias(true);
    }

    public void setBitmap(Bitmap bitmap) {
        mBitmap = bitmap;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mBitmap == null) {
            return;
        }
        canvas.drawBitmap(mBitmap, 00, mPaint);
    }
}
複製程式碼

SurfaceView 繪製圖片

這個比 ImageView 繪製圖片稍微複雜一點點,接下來呢,說說自己對它的理解

定義

SurfaceView是View的一個特殊子類,它的目的是另外提供一個執行緒進行繪製操作。

它的特性是:可以在主執行緒之外的執行緒中向螢幕繪圖上。這樣可以避免畫圖任務繁重的時候造成主執行緒阻塞,從而提高了程式的反應速度。

實現

首先繼承SurfaceView並實現SurfaceHolder.Callback介面,使用介面的原因:因為使用SurfaceView 有一個原則,所有的繪圖工作必須得在Surface 被建立之後才能開始(Surface—表面,這個概念在 圖形程式設計中常常被提到。基本上我們可以把它當作視訊記憶體的一個對映,寫入到Surface 的內容。

可以被直接複製到視訊記憶體從而顯示出來,這使得顯示速度會非常快),而在Surface 被銷燬之前必須結束。所以Callback 中的surfaceCreated 和surfaceDestroyed 就成了繪圖處理程式碼的邊界。

需要重寫的方法如下:

//在surface的大小發生改變時激發、
public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
//在建立時激發,一般在這裡呼叫畫圖的執行緒。
public void surfaceCreated(SurfaceHolder holder){}
//銷燬時激發,一般在這裡將畫圖的執行緒停止、釋放。
public void surfaceDestroyed(SurfaceHolder holder) {}
複製程式碼

使用SurfaceView大概流程:

  1. 繼承SurfaceView並實現SurfaceHolder.Callback介面

  2. SurfaceView.getHolder()獲得SurfaceHolder物件

  3. SurfaceHolder.addCallback(callback)新增回撥函式

  4. SurfaceHolder.lockCanvas()獲得Canvas物件並鎖定畫布

  5. Canvas繪畫

  6. SurfaceHolder.unlockCanvasAndPost(Canvas canvas)結束鎖定畫圖,並提交改變,將圖形顯示。

這裡用到了一個類SurfaceHolder,可以把它當成surface的控制器,就好比我們常用的MVC 設計模式,用來操縱Surface。處理它的Canvas上畫的效果和動畫,控制表面、大小、畫素等。下面我說幾個需要注意的方法:

// 給SurfaceView當前的持有者一個回撥物件。
abstract void addCallback(SurfaceHolder.Callback callback);
// 鎖定畫布,一般在鎖定後就可以通過其返回的畫布物件Canvas,在其上面畫圖等操作了。
abstract Canvas lockCanvas();
// 鎖定畫布的某個區域進行畫圖等..因為畫完圖後,會呼叫下面的unlockCanvasAndPost來改變顯示內容。
// 相對部分記憶體要求比較高的遊戲來說,可以不用重畫dirty外的其它區域的畫素,可以提高速度。
abstract Canvas lockCanvas(Rect dirty);
// 結束鎖定畫圖,並提交改變。
abstract void unlockCanvasAndPost(Canvas canvas);
複製程式碼

注意:每次利用SurfaceHolder獲得畫布時,前一次的內容將會保留。

我這裡貼一下重要部分的程式碼:

/**
 * 自定義SurfaceView 繪製圖片
 */

public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.CallbackRunnable {

   ····

    private void init() {
        mHolder = getHolder();
        mHolder.addCallback(this);
        mPaint = new Paint();
        //獲取焦點
        setFocusable(true);
        setFocusableInTouchMode(true);
        //設定常量
        setKeepScreenOn(true);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRunning = true;
        mThread = new Thread(this);
        mThread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        isRunning = false;
    }

    @Override
    public void run() {
        //迴圈繪製
        while (isRunning) {
            draw();
        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            if (mCanvas != null) {
                mCanvas.drawBitmap(Util.getImageFromAssetsFile(mContext, PICTURE_NAME), 00, mPaint);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mCanvas != null) {
                //釋放canvas
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }

    }
}
複製程式碼

原始碼地址(image包名下):https://github.com/StudyLifeTime/basicvideotutorial

總結

最後總結一下學習這三種載入方式,自定義View 和 ImageView 載入方式大同小異,就是自定義View注意畫筆的抗鋸齒操作,然後繪製圖片如果很浪費資源的情況下推薦使用 SurfaceView ,畢竟是在工作執行緒裡繪製不會影響到主執行緒的阻塞問題。

相關文章