Android Drawable徹底解析(一):Drawable源碼分析(下)

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,上篇文章已分析。

下面就這幾種方式逐一分析: 首先上原圖: voice.png ####3.3.1:android:background="@mipmap/voice"app

<ImageView
            android:layout_width="200dp"
            android:layout_height="100dp"
            android:background="@mipmap/voice"
            />

實際效果: background.pngide

可見直接使用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"
            />

實際效果: src.pngui

可見直接使用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徹底解析' 系列未完待續...

相關文章
相關標籤/搜索