本文原文發(fā)表于vcmo博客 by Jie
眾所周知ImageView可以通過src和Background兩種方式設置顯示資源。和大家一起通過源碼來了解兩種屬性的繪制流程有何不同。
熱身案例
先上兩段小代碼:
<ImageView
android:layout_width="200dp"
android:layout_height="300dp"
android:background="@mipmap/ic_launcher"/>
<ImageView
android:layout_width="200dp"
android:layout_height="300dp"
android:src="@mipmap/ic_launcher"/>
android:background="@mipmap/ic_launcher"
android:src="@mipmap/ic_launcher"
例子中壳影,可以看到ImageView通過設置Background會將圖片資源拉伸至控件的寬高讶迁。通過src設置并不會有這樣的現(xiàn)象羞芍。這是為什么呢?下面就同大家一起在源碼中找到答案糖驴。
ImageView 是View的直接子類洪唐,我們先從ImageView的源碼入手跨琳。
src與background兩種屬性設置的方法入手
setImageResource()方法入手src屬性
在ImageView的源碼中我們很容易能找到設置ImageResource的方法:
方法很短
- 首先將兩個全局變量的值賦給了局部變量。
- 調(diào)用了updateDrawable(null)桐罕,更新Drawable脉让,并將null作為參數(shù)桂敛。對,是用來初始化的溅潜。
- 將我們傳入的resId賦值給全局變量mResource术唬。
- 調(diào)用resolveUri();名稱的意思是解析資源。
- 根據(jù)新的寬高是否等于舊的值滚澜,判斷是否請求重新計算布局粗仓。
- invalidate(),重繪
通過上面方法,找到了resolveUri()方法,看看里面做了什么设捐。
private void resolveUri() {
···
Drawable d = null;
if (mResource != 0) {
try {
d = mContext.getDrawable(mResource);
} catch (Exception e) {
Log.w("ImageView", "Unable to find resource: " + mResource, e);
// Don't try again.
mUri = null;
}
}
···
updateDrawable(d);
}
代碼不短借浊,我們只關(guān)注相關(guān)的部分÷苷校可以看到在這個方法里面
- 通過mContext.getDrawable(mResource)獲取到了mResource解析來的Drawable對象實例蚂斤。
- mResource就是上面方法第三步賦值我們傳入的resId來的。
- 拿到Drawable對象d槐沼,在方法最后通過updateDrawable(d)方法更新Drawable資源曙蒸。(沒錯這個方法在上面也用到了,傳入的參數(shù)是null初始化)岗钩。
接著查看updateDrawable()方法:
private void updateDrawable(Drawable d) {
···
mDrawable = d;
if (d != null) {
d.setCallback(this);
d.setLayoutDirection(getLayoutDirection());
if (d.isStateful()) {
d.setState(getDrawableState());
}
d.setVisible(getVisibility() == VISIBLE, true);
d.setLevel(mLevel);
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
applyImageTint();
applyColorMod();
configureBounds();
}
}
同樣我們看重點:
- 將d賦值全局變量mDrawable.
- 通過d.getIntrinsicWidth()拿到drawable的width賦值全局變量mDrawableWidth
- 通過d.getIntrinsicHeight()拿到drawable的height賦值全局變來那個mDrawableHeight
- 調(diào)用configureBounds()方法配置邊界纽窟。
OK,去看configureBounds()方法:
private void configureBounds() {
···
int dwidth = mDrawableWidth;
int dheight = mDrawableHeight;
int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
int vheight = getHeight() - mPaddingTop - mPaddingBottom;
if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;
} else {
mDrawable.setBounds(0, 0, dwidth, dheight);
···
}
}
代碼較多兼吓,但是并不復雜臂港,我們只看與我們討論相關(guān)的部分(中間面省略的部分是與ScaleType屬相相關(guān)的代碼,下次單獨拿出來分析)
- 計算出view的內(nèi)部寬高并賦值局部變量。
- 判斷drawable的寬高是否沒有意義视搏,或者是設置ScaleType為FIX_XY趋艘,就通過mDrawable.setBounds()方法將mDrawable的邊界范圍設置為View的內(nèi)部大小。
- 否則凶朗,將mDrawable的邊界范圍設置為drawable資源的寬高。(所以src默認不會拉伸圖片)
最后就是在ondraw()方法中將mDrawable繪制出來显拳。到此整個的src屬性的設置到繪制的流程已經(jīng)清楚棚愤。
接著我們來看Background屬性是如何作用于View的。
setBackgroundResource()方法入手Background屬性
在ImageView的源碼中我們通過搜索發(fā)現(xiàn)并沒有Background相關(guān)的設置方法杂数。在它的父類—View中宛畦,可以找到我們想要的代碼。
public void setBackgroundResource(@DrawableRes int resid) {
if (resid != 0 && resid == mBackgroundResource) {
return;
}
Drawable d = null;
if (resid != 0) {
d = mContext.getDrawable(resid);
}
setBackground(d);
mBackgroundResource = resid;
}
代碼簡短揍移,同樣先根據(jù)resid拿到drawable對象次和,然后調(diào)用了setBackground()方法。最終調(diào)用了setBackgroundDrawable()方法那伐。在這個方法最終將drawable賦值給了一個全局變量mBackground踏施,在這個方法中并沒有發(fā)現(xiàn)設置drawable的bounds相關(guān)的方法石蔗,那么我們接著往后走,到繪制層畅形。View的onDraw()方法是空的养距,我們知道onDraw()方法其實是由View的draw()方法調(diào)用的,在draw()方法中是進行View的基本繪制日熬,onDraw()是擴展視圖自己繪制的方法棍厌。
直奔draw()方法
很開心,google的工程師給我們寫了很清晰的注釋竖席。第一步就是繪制background耘纱,只調(diào)用了一個方法drawBackground()。乘勝追擊毕荐。
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
···
}
看到了束析,調(diào)用了一個setBackGroundBounds()方法,追
void setBackgroundBounds() {
if (mBackgroundSizeChanged && mBackground != null) {
mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
}
簡短精悍的代碼东跪,mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop)畸陡,設置了mBackground這個drawable的邊界。就是View的寬高虽填。
- 此處是直接使用mRight - mleft 和 mBottom - mTop,也就是說忽略padding的存在丁恭。
- 在src屬性中,使用到容器的寬高是這樣獲得的
int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
int vheight = getHeight() - mPaddingTop - mPaddingBottom;
padding是起作用的斋日。通過兩個簡單的例子看看效果牲览。
總結(jié)
通過源碼的閱讀,清楚了ImageView 與 Background的區(qū)別與聯(lián)系以及繪制的流程:
- 都是Drawable資源的繪制恶守,所以他們內(nèi)容是可以互通的第献。
- background是View的屬性,在View層繪制(所以Background稱為背景兔港,因為它在自定義View的onDraw()方法被調(diào)用前就已經(jīng)被調(diào)用繪制了)庸毫,所有的控件都具有該屬性,src是ImageView中定義的屬性衫樊,在ImageView的onDraw方法中才被繪制飒赃。只有ImageView即其子類才有此屬性。
- background是縮放填充式的繪制科侈,src可以通過ScaleType設置不同的縮放效果载佳。
- padding屬性對background無效,但對ImageView的src是有效的臀栈。
第一次寫技術(shù)類的文章蔫慧,如有錯誤還望多多指正。歡迎分享交流权薯。
郵箱: liuj4563@163.com
原創(chuàng)博文 轉(zhuǎn)載請注明出處