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中找到一個答案供大家參考:
上面說到:這只是定義的一種矩陣操作。當然與之對應的還有一種操作叫做:【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行啊。