【Android_View】ImageView源碼簡析筆記(三)

ImageView源碼簡析


ImageView的繪製–onDraw()

【Android_View】ImageView源碼簡析筆記(二)一文中,我們簡要回顧了ImageView的測量方法即onMeasure().
我們知道,View的繪製基本上有個【三步論】:測量—佈局—繪製。
對於【佈局】,很明顯,這適應於ViewGroup,即父佈局對子View進行佈局操作,確定各個子View的佈局位置。而這次我們探討的ImageView是直接繼承於VIew的,那麼在這就不需考慮相關的佈局問題。接下來我們一起看看ImageView的繪製—onDraw()吧!


那麼老規矩,我們先看onDraw()方法的源代碼:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //Drawable對象爲空,不作任何操作
        if (mDrawable == null) {
            return; 
        }
        //Drawable對象的寬高爲0,不作任何操作
        if (mDrawableWidth == 0 || mDrawableHeight == 0) {
            return;      
        }

        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
        //Drawable的轉換矩陣爲空 且 圖像緊鄰座標軸與周圍沒有邊距
        //===> 直接繪製
            mDrawable.draw(canvas);
        } else {
       //返回Canvas私有堆棧上矩陣/剪輯狀態的數量。
       //這等於#save()調用 - #restore()調用。
            final int saveCount = canvas.getSaveCount();
            //保存畫布狀態
            canvas.save();

            if (mCropToPadding) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;
                //canvas生成矩形裁剪區域
                canvas.clipRect(scrollX + mPaddingLeft, 
                scrollY + mPaddingTop,
                scrollX + mRight - mLeft - mPaddingRight,
                scrollY + mBottom - mTop - mPaddingBottom);
            }
            //canvas移動
            canvas.translate(mPaddingLeft, mPaddingTop);

            if (mDrawMatrix != null) {
                //矩陣轉換
                canvas.concat(mDrawMatrix);
            }
            //在canvas上繪製mDrawable
            mDrawable.draw(canvas);
            //canvas恢復狀態
            canvas.restoreToCount(saveCount);
        }
    }

首先來看clipRect()方法。

public boolean clipRect(float left, float top, float right, float bottom) {
        return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
    }

這是Canvas類中的方法,我們都知道canvas就是View體系中的畫布,所有的圖形圖像都要在這上面顯示。
clipRect()看名稱是”裁剪”矩形區域的。而實際上,見名之意,這個方法主要是用來設定canvas的顯示區域的。
四個形參分別代表—矩形裁剪區左、上、右、下座標的位置。
當生成的裁剪區域不爲空時,返回true。
特別注意,這個剪切,是不影響【原有的】【已經繪製好的】圖形的。


再來看translate()方法:

public void translate(float dx, float dy) {
        native_translate(mNativeCanvasWrapper, dx, dy);
    }

很明顯,根據前綴我們可以看出,native_translate()這個方法是沒有java方法體的。根據註釋:

Preconcat the current matrix with the specified translation
     * @param dx The distance to translate in X
     * @param dy The distance to translate in Y

可知,這是用對【當前矩陣】進行指定的轉換。

Preconcat

上面這個單詞,在實際查詢中並沒有找到與之匹配的直接到含義。
在Stack Overflow中找到一個答案供大家參考:
Preconcat含義
上面說到:這只是定義的一種矩陣操作。當然與之對應的還有一種操作叫做:【postConcat】。
還有一篇比較詳細的解讀,也來自Stack Overflow。附上地址,供大家參考:參考解釋
不過,簡單點看:【preconcat】和【postConcat】實際代表着兩種不同的矩陣操作。
舉個栗子:M.preConcat(other) 與M.postConcat(other)
【preconcat】 意味着 M’ = M * other
【postConcat】意味着 M’ = other * M


好了,再來看canvas.concat(mDrawMatrix);

 public void concat(@Nullable Matrix matrix) {
        if (matrix != null) native_concat(mNativeCanvasWrapper, matrix.native_instance);
    }

原方法的註釋爲:

Preconcat the current matrix with the specified matrix. If the specified matrix is null, this method does nothing.

很明顯,就是用指定的矩陣(the specified matrix)【Preconcat操作】當前的矩陣。還有:若指定的矩陣爲空,則不進行任何操作。
在onDraw()方法中,

canvas.concat(mDrawMatrix);

的含義是:用onMeasure()方法設定好的圖像轉換矩陣mDrawMatrix來操作canvas已達到完成相應的顯示效果。有關mDrawMatrix的相應操作與賦值,請參考上篇文章所描述的內容


通過源碼我們可以看到,執行完了相應的矩陣操作後,緊接着就是繪製:

mDrawable.draw(canvas);

draw()方法的源碼如下:

public abstract void draw(@NonNull Canvas canvas);

這是Drawable類中的方法。可以看到,因爲有”abstract”我們知道這是一個抽象方法,當中是不存在方法體的。
再看註釋:

Draw in its bounds (set via setBounds) respecting optional effects such as alpha (set via setAlpha) and color filter (set via setColorFilter).

* @param canvas The canvas to draw into

這個方法是運用【透明度】【顏色濾鏡】等效果在【邊界】內繪製相關內容。
實際上,Drawable類中有一個

setBounds(int left, int top, int right, int bottom)

方法來設定繪製的邊界(實質上是一個矩形)。
在制定完繪製的邊界後,就調用Drawable.draw()在剛纔指定的邊界區域之內繪製圖像。


再來看最後一個方法:restoreToCount(int saveCount);

public void restoreToCount(int saveCount) {
        boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated();
        native_restoreToCount(mNativeCanvasWrapper, saveCount, throwOnUnderflow);
    }

很明顯,這個方法和之前的canvas.save()方法相輔相成。

  • canvas.save()保存了當前畫布的狀態後,
  • 我們又在canvas繪製了內容,
  • 待繪製結束後,隨即調用canvas.restoreToCount(int saveCount)來恢復canvas的狀態。

這樣看來,canvas.restoreToCount(int saveCount)與canvas.save()都是在保存canvas的狀態,怎麼沒什麼區別呢?
沒關係,那肯定是我們想的不夠細緻。
看到restoreToCount()方法中還有一個int 形參saveCount了麼??
實際上,在一次操作中我們可以多次保存canvas的狀態,舉個栗子:

 canvas.translate(500,500); 
 // 第一次保存Canvas的狀態 ===> 狀態1
 int save1 = canvas.save(); 
 canvas.scale(2, 2);
 // 第二次保存Canvas的狀態 ===> 狀態2
 int save2 = canvas.save(); 
 canvas.translate(100,100); 
 // 第三次保存Canvas的狀態 ===> 狀態3
 int save3 = canvas.save(); 
 canvas.restore(); // 返回最近保存的save狀態,即狀態3
 canvas.restoreToCount(save1);//返回到指定的狀態,此處爲狀態1

看完上述栗子之後,相信大家對此已經比較清楚了。


Ok。到這裏,我們就簡單的看完了ImageView的onDraw()的方法了。
這就是ImageView 最基本的【繪製】過程。要想透徹的理解ImageView,僅僅知道”三步論”可能還遠遠不夠。沒關係,接下來我們在一起看看ImageView還有什麼神祕的內容,畢竟源碼可是有1588行啊。

接下來,繼續努力!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章