Android Drawable完全解析(一):Drawable源碼分析(下)

Android Drawable完全解析(一):Drawable源碼分析(上)
Android Drawable完全解析(一):Drawable源碼分析(中)
Android Drawable完全解析(一):Drawable源碼分析(下)

昨天下班前,分析了View實例將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,實質(zhì)是調(diào)用View.setBackgroundDrawable,上篇文章已分析。

下面就這幾種方式逐一分析:
首先上原圖:


voice.png

3.3.1:android:background="@mipmap/voice"

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

實際效果:


background.png

可見直接使用android:background,圖片作為背景完全鋪滿ImageView尺寸,會根據(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.itdecent.cn/p/2213c62e4738)
        setBackgroundDrawable(background);
    }
}

可見:
在xml中直接設(shè)置android:background="@mipmap/voice"實質(zhì)是通過調(diào)用View.setBackgroundDrawable(Drawable background)將圖片繪制到屏幕上!

View.setBackgroundDrawable(Drawable background)在上一篇文章:Android Drawable完全解析(一):Drawable源碼分析(中)有過分析!
為什么背景圖會鋪滿整個ImageView,是因為在View繪制過程中,將背景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"
            />

實際效果:


src.png

可見直接使用android:src,默認(rèn)情況下圖片會根據(jù)ImageView的尺寸在保留自身寬高比例下進行縮放,最后在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實例中圖像邊界 與 ImageView邊界間的縮放關(guān)系
        initImageView();
        final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
        //將xml中使用android:src="@mipmap/voice"設(shè)置的圖片生成Drawable實例
        final Drawable d = a.getDrawable(R.styleable.ImageView_src);
        if (d != null) {
            //將src生成的Drawable實例設(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對 通過src生成的Drawable實例進行著色
        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) {
            ****
            //對src生成的Drawable實例設(shè)置一系列屬性
            updateDrawable(drawable);
            ****
            //最后調(diào)用invalidate()觸發(fā)draw
            invalidate();
        }
    }
    private void updateDrawable(Drawable d) {
        ****
        //將ImageView實例之前關(guān)聯(lián)的Drawable實例的動畫監(jiān)聽移除,
        //并停止其已經(jīng)在執(zhí)行的動畫,解除其所有事件
        if (mDrawable != null) {
            mDrawable.setCallback(null);
            unscheduleDrawable(mDrawable);
            if (isAttachedToWindow()) {
                mDrawable.setVisible(false, false);
            }
        }
        //將mDrawable賦值為通過src屬性生成的Drawable實例
        mDrawable = d;
        if (d != null) {
            //為通過src生成的Drawable實例設(shè)置動畫監(jiān)聽為ImageView實例自身;
            //并設(shè)置其布局方向,狀態(tài)數(shù)組,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();
            //根據(jù)當(dāng)前ImageView實例的ColorStateList對其進行著色
            applyImageTint();
            //未執(zhí)行實質(zhì)代碼
            applyColorMod();
            //設(shè)置Drawable實例的繪制范圍不變,并根據(jù)ImageView實例內(nèi)容區(qū)域和
            //Drawable實例原始繪制范圍,確定Drawable實例在實際繪制時候的縮放。
            configureBounds();
        } else {
            mDrawableWidth = mDrawableHeight = -1;
        }
    }
    private void applyImageTint() {
        ****
                //根據(jù)當(dāng)前ImageView的ColorStateList對 通過src生成的Drawable實例進行著色
                mDrawable.setTintList(mDrawableTintList);
        ****
    }
    private void applyColorMod() {
        //對應(yīng)通過 src生成的Drawble實例來說,ImageView并未調(diào)用setColorFilter
        //mColorMod也為默認(rèn)的false值,所以下面代碼實質(zhì)未執(zhí)行
        if (mDrawable != null && mColorMod) {
            mDrawable = mDrawable.mutate();
            //如果當(dāng)前ImageView實例調(diào)用過setColorFilter,
            //則對 通過src生成的Drawable實例設(shè)置相同的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實例的內(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è)定為ImageView實例的內(nèi)容區(qū)域
            mDrawable.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
        } else {
            //對應(yīng)我們例子中,未設(shè)置android:scaleType情況下,
            //通過src生成的Drawable實例的繪制范圍就是其原始范圍
            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實例內(nèi)容區(qū)域的范圍和Drawable實例實際寬高來設(shè)置mDrawMatrix
                mTempSrc.set(0, 0, dwidth, dheight);
                mTempDst.set(0, 0, vwidth, vheight);
                mDrawMatrix = mMatrix;
                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
            }
        }
    }
}

ImageView實例生成后,肯定還是執(zhí)行onDraw方法將自身繪制到屏幕上,繼續(xù)追蹤代碼:

public class ImageView extends View {
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        ****
        //在上面分析過 mDrawMatrix不為null
        //mDrawMatrix的屬性根據(jù)ImageView實例內(nèi)容區(qū)域的范圍和Drawable實例實際寬高來配置
        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
            //如果矩陣mDrawMatrix為空,且ImageView的上下padding值都為0
            //則直接將Drawable實例繪制到畫布上
            mDrawable.draw(canvas);
        } else {
            ****
            //我們例子中,矩陣mDrawMatrix不為空,則將其設(shè)置到ImageView的畫布上
            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }
            //然后在畫布上面繪制Drawable實例
            mDrawable.draw(canvas);
            canvas.restoreToCount(saveCount);
        }
    }
}

至此,
android:src="@mipmap/voice"整個流程就分析完了,流程總結(jié)如下:

在ImageView構(gòu)造函數(shù)中:

1:設(shè)置縮放類型默認(rèn)為 ScaleType.FIT_CENTER(圖像居中等比例縮放)
2:在ImageView構(gòu)造函數(shù)中,解析xml中android:src屬性獲取Drawable實例;
3:為生成的Drawable實例設(shè)置一系列屬性:

  • 設(shè)置動畫監(jiān)聽為ImageView實例自身:d.setCallback(this);
  • 設(shè)置布局方向和ImageView實例一致:d.setLayoutDirection(getLayoutDirection());
  • 設(shè)置狀態(tài)數(shù)組和ImageView實例一致:d.setState(getDrawableState());
  • 設(shè)置動畫是否可見和 ImageView可見性一致:d.setVisible(getWindowVisibility() == VISIBLE && isShown(), true);
  • 設(shè)置動畫當(dāng)前Level值和ImageView的mLevel值一致:d.setLevel(mLevel);
  • 根據(jù)當(dāng)前ImageView實例的ColorStateList對其進行著色:applyImageTint();
  • 設(shè)置繪制范圍為原始繪制范圍setBounds 且 根據(jù)ImageView、Drawable實例的范圍 和 縮放類型 來設(shè)置Matrix mDrawMatrix(用于onDraw):configureBounds();

4:如果我們在xml中還設(shè)置了縮放類型,著色,著色模式,透明度,
則為mScaleType重新賦值,并為生成的Drawable實例逐一設(shè)置著色,著色模式,透明度

在ImageView的onDraw方法中:

1:如果矩陣mDrawMatrix為空,且ImageView的上下padding值都為0,則直接將Drawable實例繪制到畫布上
2:其余情況下:

  • 如果矩陣mDrawMatrix不為空,則將其設(shè)置到ImageView的畫布上;
  • 然后在畫布上面繪制Drawable實例

本質(zhì)上還是執(zhí)行了Drawable.draw(@NonNull Canvas canvas)將src生成的Drawable實例繪制到ImageView實例所在的畫布

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會引發(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實例
                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ù),
        //調(diào)用updateDrawable,上面已經(jīng)分析過此方法
        updateDrawable(d);
    }

setImageResource(@DrawableRes int resId)整個流程就分析完了,流程總結(jié)如下:

  • 1:首先執(zhí)行updateDrawable(null),將已經(jīng)繪制完畢的mDrawable動畫停止,移除所有事件及動畫監(jiān)聽,并重置為null
  • 2:用Resource實例通過resId獲取Drawable實例,作為參數(shù)執(zhí)行updateDrawable
  • 3:ImageView實例執(zhí)行重繪,詳見之前onDraw的分析

至此,Drawable在ImageView中的繪制流程就分析完畢了!Drawable源碼分析也告一段落,如有錯誤或者翻譯問題請各位大神留言!

'Android Drawable完全解析' 系列未完待續(xù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容