Android Drawable完全解析(一):Drawable源碼分析(上)
Android Drawable完全解析(一):Drawable源碼分析(中)
Android Drawable完全解析(一):Drawable源碼分析(下)
昨天下班前至会,分析了View實(shí)例將Drawable作為背景繪制到屏幕上面的流程奶是,今天繼續(xù)分析Drawable在ImageView中的繪制流程!
3:Drawable繪制流程
3.3:Drawable在ImageView中的繪制流程
ImageView使用Drawable的方式大體以下幾種:
- 在xml中直接設(shè)置android:background="@mipmap/voice"
<ImageView
android:layout_width="200dp"
android:layout_height="100dp"
android:background="@mipmap/voice"
/> - 在xml中直接設(shè)置android:src="@mipmap/voice"
<ImageView
android:layout_width="200dp"
android:layout_height="100dp"
android:src="@mipmap/voice"
/> - Java代碼中調(diào)用 setImageResource(@DrawableRes int resId)
- Java代碼中調(diào)用 setImageDrawable(@Nullable Drawable drawable)
- Java代碼中調(diào)用 setBackgroundDrawable,實(shí)質(zhì)是調(diào)用View.setBackgroundDrawable,上篇文章已分析弦牡。
下面就這幾種方式逐一分析:
首先上原圖:
3.3.1:android:background="@mipmap/voice"
<ImageView
android:layout_width="200dp"
android:layout_height="100dp"
android:background="@mipmap/voice"
/>
實(shí)際效果:
可見直接使用android:background友驮,圖片作為背景完全鋪滿ImageView尺寸,會(huì)根據(jù)ImageView的范圍縮放驾锰。
既然在xml中布局ImageView卸留,那么肯定是調(diào)用ImageView(Context context, @Nullable AttributeSet attrs),看一下關(guān)鍵代碼:
public class ImageView extends View {
public ImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
****
public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
//調(diào)用View的構(gòu)造函數(shù)
super(context, attrs, defStyleAttr, defStyleRes);
****
}
}
一路追蹤下去:
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
****
Drawable background = null;
****
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
//獲取在xml中設(shè)置的android:background="@mipmap/voice"
case com.android.internal.R.styleable.View_background:
background = a.getDrawable(attr);
break;
*****
}
}
****
if (background != null) {
setBackground(background);
}
****
}
public void setBackground(Drawable background) {
//[Android Drawable完全解析(一):Drawable源碼分析(中)](http://www.reibang.com/p/2213c62e4738)
setBackgroundDrawable(background);
}
}
可見:
在xml中直接設(shè)置android:background="@mipmap/voice"實(shí)質(zhì)是通過調(diào)用View.setBackgroundDrawable(Drawable background)將圖片繪制到屏幕上椭豫!
View.setBackgroundDrawable(Drawable background)在上一篇文章:Android Drawable完全解析(一):Drawable源碼分析(中)有過分析耻瑟!
為什么背景圖會(huì)鋪滿整個(gè)ImageView,是因?yàn)樵赩iew繪制過程中赏酥,將背景Drawable的繪制范圍設(shè)置為和View的尺寸一致:
void setBackgroundBounds() {
if (mBackgroundSizeChanged && mBackground != null) {
mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
}
3.3.2:android:src="@mipmap/voice"
<ImageView
android:layout_width="200dp"
android:layout_height="100dp"
android:src="@mipmap/voice"
/>
實(shí)際效果:
可見直接使用android:src喳整,默認(rèn)情況下圖片會(huì)根據(jù)ImageView的尺寸在保留自身寬高比例下進(jìn)行縮放,最后在ImageView的中心顯示裸扶。
既然在xml中布局ImageView框都,那么肯定是調(diào)用ImageView(Context context, @Nullable AttributeSet attrs),同樣看一下關(guān)鍵代碼:
public class ImageView extends View {
public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
//super上面分析過了呵晨,繪制的是android:background="@mipmap/voice"
super(context, attrs, defStyleAttr, defStyleRes);
//主要設(shè)置了 ImageView實(shí)例中圖像邊界 與 ImageView邊界間的縮放關(guān)系
initImageView();
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
//將xml中使用android:src="@mipmap/voice"設(shè)置的圖片生成Drawable實(shí)例
final Drawable d = a.getDrawable(R.styleable.ImageView_src);
if (d != null) {
//將src生成的Drawable實(shí)例設(shè)置為ImageView的內(nèi)容
setImageDrawable(d);
}
****
//在我們的例子中魏保,沒有設(shè)置scaleType屬性,則index = -1摸屠;
final int index = a.getInt(R.styleable.ImageView_scaleType, -1);
if (index >= 0) {
//在我們例子中谓罗,index = -1,下面代碼不執(zhí)行
setScaleType(sScaleTypeArray[index]);
}
//解析在xml中設(shè)置的tint和tintMode屬性值
if (a.hasValue(R.styleable.ImageView_tint)) {
mDrawableTintList = a.getColorStateList(R.styleable.ImageView_tint);
mHasDrawableTint = true;
// Prior to L, this attribute would always set a color filter with
// blending mode SRC_ATOP. Preserve that default behavior.
mDrawableTintMode = PorterDuff.Mode.SRC_ATOP;
mHasDrawableTintMode = true;
}
if (a.hasValue(R.styleable.ImageView_tintMode)) {
mDrawableTintMode = Drawable.parseTintMode(a.getInt(
R.styleable.ImageView_tintMode, -1), mDrawableTintMode);
mHasDrawableTintMode = true;
}
//根據(jù)當(dāng)前ImageView的ColorStateList對(duì) 通過src生成的Drawable實(shí)例進(jìn)行著色
applyImageTint();
final int alpha = a.getInt(R.styleable.ImageView_drawableAlpha, 255);
//設(shè)置透明度
if (alpha != 255) {
setImageAlpha(alpha);
}
mCropToPadding = a.getBoolean(
R.styleable.ImageView_cropToPadding, false);
a.recycle();
//need inflate syntax/reader for matrix
}
private void initImageView() {
****
//設(shè)置mScaleType = ScaleType.FIT_CENTER;可見ImageView中
//mScaleType默認(rèn)就是ScaleType.FIT_CENTER
mScaleType = ScaleType.FIT_CENTER;
****
}
public void setImageDrawable(@Nullable Drawable drawable) {
if (mDrawable != drawable) {
****
//對(duì)src生成的Drawable實(shí)例設(shè)置一系列屬性
updateDrawable(drawable);
****
//最后調(diào)用invalidate()觸發(fā)draw
invalidate();
}
}
private void updateDrawable(Drawable d) {
****
//將ImageView實(shí)例之前關(guān)聯(lián)的Drawable實(shí)例的動(dòng)畫監(jiān)聽移除季二,
//并停止其已經(jīng)在執(zhí)行的動(dòng)畫妥衣,解除其所有事件
if (mDrawable != null) {
mDrawable.setCallback(null);
unscheduleDrawable(mDrawable);
if (isAttachedToWindow()) {
mDrawable.setVisible(false, false);
}
}
//將mDrawable賦值為通過src屬性生成的Drawable實(shí)例
mDrawable = d;
if (d != null) {
//為通過src生成的Drawable實(shí)例設(shè)置動(dòng)畫監(jiān)聽為ImageView實(shí)例自身;
//并設(shè)置其布局方向戒傻,狀態(tài)數(shù)組,Drawable動(dòng)畫是否開啟蜂筹,Drawable的level值需纳。
d.setCallback(this);
d.setLayoutDirection(getLayoutDirection());
if (d.isStateful()) {
d.setState(getDrawableState());
}
if (isAttachedToWindow()) {
d.setVisible(getWindowVisibility() == VISIBLE && isShown(), true);
}
d.setLevel(mLevel);
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
//根據(jù)當(dāng)前ImageView實(shí)例的ColorStateList對(duì)其進(jìn)行著色
applyImageTint();
//未執(zhí)行實(shí)質(zhì)代碼
applyColorMod();
//設(shè)置Drawable實(shí)例的繪制范圍不變,并根據(jù)ImageView實(shí)例內(nèi)容區(qū)域和
//Drawable實(shí)例原始繪制范圍艺挪,確定Drawable實(shí)例在實(shí)際繪制時(shí)候的縮放不翩。
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}
private void applyImageTint() {
****
//根據(jù)當(dāng)前ImageView的ColorStateList對(duì) 通過src生成的Drawable實(shí)例進(jìn)行著色
mDrawable.setTintList(mDrawableTintList);
****
}
private void applyColorMod() {
//對(duì)應(yīng)通過 src生成的Drawble實(shí)例來說,ImageView并未調(diào)用setColorFilter
//mColorMod也為默認(rèn)的false值麻裳,所以下面代碼實(shí)質(zhì)未執(zhí)行
if (mDrawable != null && mColorMod) {
mDrawable = mDrawable.mutate();
//如果當(dāng)前ImageView實(shí)例調(diào)用過setColorFilter口蝠,
//則對(duì) 通過src生成的Drawable實(shí)例設(shè)置相同的ColorFilter
if (mHasColorFilter) {
mDrawable.setColorFilter(mColorFilter);
}
mDrawable.setXfermode(mXfermode);
mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
}
}
private void configureBounds() {
//通過src生成的Drawable實(shí)例 原始寬高
final int dwidth = mDrawableWidth;
final int dheight = mDrawableHeight;
//ImageView實(shí)例的內(nèi)容區(qū)域?qū)捀?去除了padding值)
final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
final int vheight = getHeight() - mPaddingTop - mPaddingBottom;
****
if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
//當(dāng)ImageView設(shè)置過android:scaleType="fitXY" 或setScaleType(ScaleType.FIT_XY),
//則將此Drawable實(shí)例的繪制范圍設(shè)定為ImageView實(shí)例的內(nèi)容區(qū)域
mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;
} else {
//對(duì)應(yīng)我們例子中津坑,未設(shè)置android:scaleType情況下妙蔗,
//通過src生成的Drawable實(shí)例的繪制范圍就是其原始范圍
mDrawable.setBounds(0, 0, dwidth, dheight);
//下面代碼設(shè)置了mDrawMatrix的屬性
//在initImageView()方法中已知:
//ImageView中mScaleType默認(rèn)就是ScaleType.FIT_CENTER
if (ScaleType.MATRIX == mScaleType) {
****
} else if (fits) {
****
} else if (ScaleType.CENTER == mScaleType) {
****
} else if (ScaleType.CENTER_CROP == mScaleType) {
****
} else if (ScaleType.CENTER_INSIDE == mScaleType) {
****
} else {
//ImageView中mScaleType默認(rèn)就是ScaleType.FIT_CENTER
//則根據(jù)ImageView實(shí)例內(nèi)容區(qū)域的范圍和Drawable實(shí)例實(shí)際寬高來設(shè)置mDrawMatrix
mTempSrc.set(0, 0, dwidth, dheight);
mTempDst.set(0, 0, vwidth, vheight);
mDrawMatrix = mMatrix;
mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
}
}
}
}
ImageView實(shí)例生成后,肯定還是執(zhí)行onDraw方法將自身繪制到屏幕上疆瑰,繼續(xù)追蹤代碼:
public class ImageView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
****
//在上面分析過 mDrawMatrix不為null
//mDrawMatrix的屬性根據(jù)ImageView實(shí)例內(nèi)容區(qū)域的范圍和Drawable實(shí)例實(shí)際寬高來配置
if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
//如果矩陣mDrawMatrix為空眉反,且ImageView的上下padding值都為0
//則直接將Drawable實(shí)例繪制到畫布上
mDrawable.draw(canvas);
} else {
****
//我們例子中昙啄,矩陣mDrawMatrix不為空,則將其設(shè)置到ImageView的畫布上
if (mDrawMatrix != null) {
canvas.concat(mDrawMatrix);
}
//然后在畫布上面繪制Drawable實(shí)例
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
}
至此寸五,
android:src="@mipmap/voice"整個(gè)流程就分析完了梳凛,流程總結(jié)如下:
在ImageView構(gòu)造函數(shù)中:
1:設(shè)置縮放類型默認(rèn)為 ScaleType.FIT_CENTER(圖像居中等比例縮放)
2:在ImageView構(gòu)造函數(shù)中,解析xml中android:src屬性獲取Drawable實(shí)例梳杏;
3:為生成的Drawable實(shí)例設(shè)置一系列屬性:
- 設(shè)置動(dòng)畫監(jiān)聽為ImageView實(shí)例自身:d.setCallback(this);
- 設(shè)置布局方向和ImageView實(shí)例一致:d.setLayoutDirection(getLayoutDirection())韧拒;
- 設(shè)置狀態(tài)數(shù)組和ImageView實(shí)例一致:d.setState(getDrawableState());
- 設(shè)置動(dòng)畫是否可見和 ImageView可見性一致:d.setVisible(getWindowVisibility() == VISIBLE && isShown(), true);
- 設(shè)置動(dòng)畫當(dāng)前Level值和ImageView的mLevel值一致:d.setLevel(mLevel);
- 根據(jù)當(dāng)前ImageView實(shí)例的ColorStateList對(duì)其進(jìn)行著色:applyImageTint();
- 設(shè)置繪制范圍為原始繪制范圍setBounds 且 根據(jù)ImageView十性、Drawable實(shí)例的范圍 和 縮放類型 來設(shè)置Matrix mDrawMatrix(用于onDraw):configureBounds();
4:如果我們?cè)趚ml中還設(shè)置了縮放類型叛溢,著色,著色模式烁试,透明度雇初,
則為mScaleType重新賦值,并為生成的Drawable實(shí)例逐一設(shè)置著色减响,著色模式靖诗,透明度
在ImageView的onDraw方法中:
1:如果矩陣mDrawMatrix為空,且ImageView的上下padding值都為0支示,則直接將Drawable實(shí)例繪制到畫布上
2:其余情況下:
- 如果矩陣mDrawMatrix不為空刊橘,則將其設(shè)置到ImageView的畫布上;
- 然后在畫布上面繪制Drawable實(shí)例
本質(zhì)上還是執(zhí)行了Drawable.draw(@NonNull Canvas canvas)將src生成的Drawable實(shí)例繪制到ImageView實(shí)例所在的畫布
3.3.3:setImageDrawable(@Nullable Drawable drawable)
setImageDrawable在上面分析過程中出現(xiàn)過
public void setImageDrawable(@Nullable Drawable drawable) {
if (mDrawable != drawable) {
mResource = 0;
mUri = null;
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(drawable);
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
//invalidate會(huì)引發(fā)重繪颂鸿,調(diào)用onDraw方法促绵,直接看上面onDraw的流程分析即可
invalidate();
}
}
3.3.4:setImageResource(@DrawableRes int resId)
public void setImageResource(@DrawableRes int resId) {
****
//在updateDrawable(Drawable d)中:mDrawable = d;
//此處將mDrawable重置為null
updateDrawable(null);
//為mResource賦值為傳入的資源ID,mUri重置為null
mResource = resId;
mUri = null;
resolveUri();
****
//引發(fā)重繪
invalidate();
}
private void resolveUri() {
//updateDrawable(null)已經(jīng)將mDrawable重置為null
if (mDrawable != null) {
return;
}
if (getResources() == null) {
return;
}
Drawable d = null;
//在中setImageResource(@DrawableRes int resId)已知:mResource = resId;
if (mResource != 0) {
//通過setImageResource傳入的resId通常不為0嘴纺,執(zhí)行如下:
try {
//通過傳入的圖片資源ID獲取Drawable實(shí)例
d = mContext.getDrawable(mResource);
} catch (Exception e) {
Log.w(LOG_TAG, "Unable to find resource: " + mResource, e);
// Don't try again.
mUri = null;
}
} else if (mUri != null) {
d = getDrawableFromUri(mUri);
if (d == null) {
Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
// Don't try again.
mUri = null;
}
} else {
return;
}
//將通過傳入的圖片資源ID生成的Drawable實(shí)例作為參數(shù)败晴,
//調(diào)用updateDrawable,上面已經(jīng)分析過此方法
updateDrawable(d);
}
setImageResource(@DrawableRes int resId)整個(gè)流程就分析完了栽渴,流程總結(jié)如下:
- 1:首先執(zhí)行updateDrawable(null)尖坤,將已經(jīng)繪制完畢的mDrawable動(dòng)畫停止,移除所有事件及動(dòng)畫監(jiān)聽闲擦,并重置為null
- 2:用Resource實(shí)例通過resId獲取Drawable實(shí)例慢味,作為參數(shù)執(zhí)行updateDrawable
- 3:ImageView實(shí)例執(zhí)行重繪,詳見之前onDraw的分析
至此墅冷,Drawable在ImageView中的繪制流程就分析完畢了纯路!Drawable源碼分析也告一段落,如有錯(cuò)誤或者翻譯問題請(qǐng)各位大神留言寞忿!
'Android Drawable完全解析' 系列未完待續(xù)...