Android Drawable徹底解析(一):Drawable源碼分析(上) Android Drawable徹底解析(一):Drawable源碼分析(中) Android Drawable徹底解析(一):Drawable源碼分析(下)android
昨天下班前,分析了View實例將Drawable做爲背景繪製到屏幕上面的流程,今天繼續分析Drawable在ImageView中的繪製流程!canvas
####3:Drawable繪製流程 ####3.3:Drawable在ImageView中的繪製流程 ImageView使用Drawable的方式大致如下幾種:數組
- 在xml中直接設置android:background="@mipmap/voice" <ImageView android:layout_width="200dp" android:layout_height="100dp" android:background="@mipmap/voice" />
- 在xml中直接設置android:src="@mipmap/voice" <ImageView android:layout_width="200dp" android:layout_height="100dp" android:src="@mipmap/voice" />
- Java代碼中調用 setImageResource(@DrawableRes int resId)
- Java代碼中調用 setImageDrawable(@Nullable Drawable drawable)
- Java代碼中調用 setBackgroundDrawable,實質是調用View.setBackgroundDrawable,上篇文章已分析。
下面就這幾種方式逐一分析: 首先上原圖: ####3.3.1:android:background="@mipmap/voice"app
<ImageView android:layout_width="200dp" android:layout_height="100dp" android:background="@mipmap/voice" />
實際效果: ide
可見直接使用android:background,圖片做爲背景徹底鋪滿ImageView尺寸,會根據ImageView的範圍縮放。 既然在xml中佈局ImageView,那麼確定是調用ImageView(Context context, @Nullable AttributeSet attrs),看一下關鍵代碼:函數
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) { //調用View的構造函數 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中設置的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.jianshu.com/p/2213c62e4738) setBackgroundDrawable(background); } }
可見: 在xml中直接設置android:background="@mipmap/voice"實質是經過調用View.setBackgroundDrawable(Drawable background)將圖片繪製到屏幕上!源碼分析
View.setBackgroundDrawable(Drawable background)在上一篇文章:Android Drawable徹底解析(一):Drawable源碼分析(中)有過度析! 爲何背景圖會鋪滿整個ImageView,是由於在View繪製過程當中,將背景Drawable的繪製範圍設置爲和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" />
實際效果: ui
可見直接使用android:src,默認狀況下圖片會根據ImageView的尺寸在保留自身寬高比例下進行縮放,最後在ImageView的中心顯示。 既然在xml中佈局ImageView,那麼確定是調用ImageView(Context context, @Nullable AttributeSet attrs),一樣看一下關鍵代碼:
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); //主要設置了 ImageView實例中圖像邊界 與 ImageView邊界間的縮放關係 initImageView(); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.ImageView, defStyleAttr, defStyleRes); //將xml中使用android:src="@mipmap/voice"設置的圖片生成Drawable實例 final Drawable d = a.getDrawable(R.styleable.ImageView_src); if (d != null) { //將src生成的Drawable實例設置爲ImageView的內容 setImageDrawable(d); } **** //在咱們的例子中,沒有設置scaleType屬性,則index = -1; final int index = a.getInt(R.styleable.ImageView_scaleType, -1); if (index >= 0) { //在咱們例子中,index = -1,下面代碼不執行 setScaleType(sScaleTypeArray[index]); } //解析在xml中設置的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; } //根據當前ImageView的ColorStateList對 經過src生成的Drawable實例進行着色 applyImageTint(); final int alpha = a.getInt(R.styleable.ImageView_drawableAlpha, 255); //設置透明度 if (alpha != 255) { setImageAlpha(alpha); } mCropToPadding = a.getBoolean( R.styleable.ImageView_cropToPadding, false); a.recycle(); //need inflate syntax/reader for matrix } private void initImageView() { **** //設置mScaleType = ScaleType.FIT_CENTER;可見ImageView中 //mScaleType默認就是ScaleType.FIT_CENTER mScaleType = ScaleType.FIT_CENTER; **** } public void setImageDrawable(@Nullable Drawable drawable) { if (mDrawable != drawable) { **** //對src生成的Drawable實例設置一系列屬性 updateDrawable(drawable); **** //最後調用invalidate()觸發draw invalidate(); } } private void updateDrawable(Drawable d) { **** //將ImageView實例以前關聯的Drawable實例的動畫監聽移除, //並中止其已經在執行的動畫,解除其全部事件 if (mDrawable != null) { mDrawable.setCallback(null); unscheduleDrawable(mDrawable); if (isAttachedToWindow()) { mDrawable.setVisible(false, false); } } //將mDrawable賦值爲經過src屬性生成的Drawable實例 mDrawable = d; if (d != null) { //爲經過src生成的Drawable實例設置動畫監聽爲ImageView實例自身; //並設置其佈局方向,狀態數組,Drawable動畫是否開啓,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(); //根據當前ImageView實例的ColorStateList對其進行着色 applyImageTint(); //未執行實質代碼 applyColorMod(); //設置Drawable實例的繪製範圍不變,並根據ImageView實例內容區域和 //Drawable實例原始繪製範圍,肯定Drawable實例在實際繪製時候的縮放。 configureBounds(); } else { mDrawableWidth = mDrawableHeight = -1; } } private void applyImageTint() { **** //根據當前ImageView的ColorStateList對 經過src生成的Drawable實例進行着色 mDrawable.setTintList(mDrawableTintList); **** } private void applyColorMod() { //對應經過 src生成的Drawble實例來講,ImageView並未調用setColorFilter //mColorMod也爲默認的false值,因此下面代碼實質未執行 if (mDrawable != null && mColorMod) { mDrawable = mDrawable.mutate(); //若是當前ImageView實例調用過setColorFilter, //則對 經過src生成的Drawable實例設置相同的ColorFilter if (mHasColorFilter) { mDrawable.setColorFilter(mColorFilter); } mDrawable.setXfermode(mXfermode); mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8); } } private void configureBounds() { //經過src生成的Drawable實例 原始寬高 final int dwidth = mDrawableWidth; final int dheight = mDrawableHeight; //ImageView實例的內容區域寬高(去除了padding值) final int vwidth = getWidth() - mPaddingLeft - mPaddingRight; final int vheight = getHeight() - mPaddingTop - mPaddingBottom; **** if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) { //當ImageView設置過android:scaleType="fitXY" 或setScaleType(ScaleType.FIT_XY), //則將此Drawable實例的繪製範圍設定爲ImageView實例的內容區域 mDrawable.setBounds(0, 0, vwidth, vheight); mDrawMatrix = null; } else { //對應咱們例子中,未設置android:scaleType狀況下, //經過src生成的Drawable實例的繪製範圍就是其原始範圍 mDrawable.setBounds(0, 0, dwidth, dheight); //下面代碼設置了mDrawMatrix的屬性 //在initImageView()方法中已知: //ImageView中mScaleType默認就是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默認就是ScaleType.FIT_CENTER //則根據ImageView實例內容區域的範圍和Drawable實例實際寬高來設置mDrawMatrix mTempSrc.set(0, 0, dwidth, dheight); mTempDst.set(0, 0, vwidth, vheight); mDrawMatrix = mMatrix; mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType)); } } } } ImageView實例生成後,確定仍是執行onDraw方法將自身繪製到屏幕上,繼續追蹤代碼: public class ImageView extends View { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); **** //在上面分析過 mDrawMatrix不爲null //mDrawMatrix的屬性根據ImageView實例內容區域的範圍和Drawable實例實際寬高來配置 if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) { //若是矩陣mDrawMatrix爲空,且ImageView的上下padding值都爲0 //則直接將Drawable實例繪製到畫布上 mDrawable.draw(canvas); } else { **** //咱們例子中,矩陣mDrawMatrix不爲空,則將其設置到ImageView的畫布上 if (mDrawMatrix != null) { canvas.concat(mDrawMatrix); } //而後在畫布上面繪製Drawable實例 mDrawable.draw(canvas); canvas.restoreToCount(saveCount); } } }
至此, android:src="@mipmap/voice"整個流程就分析完了,流程總結以下:
在ImageView構造函數中:
1:設置縮放類型默認爲 ScaleType.FIT_CENTER(圖像居中等比例縮放) 2:在ImageView構造函數中,解析xml中android:src屬性獲取Drawable實例; 3:爲生成的Drawable實例設置一系列屬性:
- 設置動畫監聽爲ImageView實例自身:d.setCallback(this);
- 設置佈局方向和ImageView實例一致:d.setLayoutDirection(getLayoutDirection());
- 設置狀態數組和ImageView實例一致:d.setState(getDrawableState());
- 設置動畫是否可見和 ImageView可見性一致:d.setVisible(getWindowVisibility() == VISIBLE && isShown(), true);
- 設置動畫當前Level值和ImageView的mLevel值一致:d.setLevel(mLevel);
- 根據當前ImageView實例的ColorStateList對其進行着色:applyImageTint();
- 設置繪製範圍爲原始繪製範圍setBounds 且 根據ImageView、Drawable實例的範圍 和 縮放類型 來設置Matrix mDrawMatrix(用於onDraw):configureBounds();
4:若是咱們在xml中還設置了縮放類型,着色,着色模式,透明度, 則爲mScaleType從新賦值,併爲生成的Drawable實例逐一設置着色,着色模式,透明度
在ImageView的onDraw方法中:
1:若是矩陣mDrawMatrix爲空,且ImageView的上下padding值都爲0,則直接將Drawable實例繪製到畫布上 2:其他狀況下:
- 若是矩陣mDrawMatrix不爲空,則將其設置到ImageView的畫布上;
- 而後在畫布上面繪製Drawable實例
本質上仍是執行了Drawable.draw(@NonNull Canvas canvas)將src生成的Drawable實例繪製到ImageView實例所在的畫布
####3.3.3:setImageDrawable(@Nullable Drawable drawable) setImageDrawable在上面分析過程當中出現過
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會引起重繪,調用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(); **** //引起重繪 invalidate(); } private void resolveUri() { //updateDrawable(null)已經將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,執行以下: try { //經過傳入的圖片資源ID獲取Drawable實例 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實例做爲參數, //調用updateDrawable,上面已經分析過此方法 updateDrawable(d); }
setImageResource(@DrawableRes int resId)整個流程就分析完了,流程總結以下:
- 1:首先執行updateDrawable(null),將已經繪製完畢的mDrawable動畫中止,移除全部事件及動畫監聽,並重置爲null
- 2:用Resource實例經過resId獲取Drawable實例,做爲參數執行updateDrawable
- 3:ImageView實例執行重繪,詳見以前onDraw的分析
至此,Drawable在ImageView中的繪製流程就分析完畢了!Drawable源碼分析也告一段落,若有錯誤或者翻譯問題請各位大神留言!
'Android Drawable徹底解析' 系列未完待續...