其實你不懂:Drawable着色(tint)的兼容方案 源碼解析

原文地址:其實你不懂:Drawable着色(tint)的兼容方案 源碼解析android

前兩天寫一個自定義控件,使用Drawable變色來展現EditText的不一樣狀態,涉及到了DrawableCompat這個類,今天着重分析一下它。app

####1:Drawable變色的通用代碼ide

//1:經過圖片資源文件生成Drawable實例
Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher).mutate();
//2:先調用DrawableCompat的wrap方法
drawable = DrawableCompat.wrap(drawable);
//3:再調用DrawableCompat的setTint方法,爲Drawable實例進行着色
DrawableCompat.setTint(drawable, Color.RED);

這裏涉及幾個方法:函數

  • Drawable.mutate()
  • DrawableCompat.wrap(@NonNull Drawable drawable)
  • DrawableCompat.setTint(@NonNull Drawable drawable, @ColorInt int tint)

下面看一下這幾個方法的源碼,Drawable.mutate()稍後分析,先看一下DrawableCompat中的wrap和setTint這兩個方法。 ####2:DrawableCompat.wrap(@NonNull Drawable drawable)性能

public static Drawable wrap(@NonNull Drawable drawable) {
  return IMPL.wrap(drawable);
}

從源碼可見,wrap方法內部是return IMPL.wrap(drawable),那這個IMPL是?優化

static final DrawableImpl IMPL;
    /**
     * Interface for the full API.
     */
    interface DrawableImpl {
        void jumpToCurrentState(Drawable drawable);
        void setAutoMirrored(Drawable drawable, boolean mirrored);
        boolean isAutoMirrored(Drawable drawable);
        void setHotspot(Drawable drawable, float x, float y);
        void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom);
        void setTint(Drawable drawable, int tint);
        void setTintList(Drawable drawable, ColorStateList tint);
        void setTintMode(Drawable drawable, PorterDuff.Mode tintMode);
        Drawable wrap(Drawable drawable);
        boolean setLayoutDirection(Drawable drawable, int layoutDirection);
        int getLayoutDirection(Drawable drawable);
        int getAlpha(Drawable drawable);
        void applyTheme(Drawable drawable, Resources.Theme t);
        boolean canApplyTheme(Drawable drawable);
        ColorFilter getColorFilter(Drawable drawable);
        void clearColorFilter(Drawable drawable);
        void inflate(Drawable drawable, Resources res, XmlPullParser parser, AttributeSet attrs,
                     Resources.Theme t) throws IOException, XmlPullParserException;
    }
    static {
        final int version = android.os.Build.VERSION.SDK_INT;
        if (version >= 23) {
            IMPL = new MDrawableImpl();
        } else if (version >= 21) {
            IMPL = new LollipopDrawableImpl();
        } else if (version >= 19) {
            IMPL = new KitKatDrawableImpl();
        } else if (version >= 17) {
            IMPL = new JellybeanMr1DrawableImpl();
        } else if (version >= 11) {
            IMPL = new HoneycombDrawableImpl();
        } else {
            IMPL = new BaseDrawableImpl();
        }
    }

可見,在不一樣的SDK版本下,IMPL對應DrawableImpl的不一樣子類實例。下面分別看一下這幾個實現類對wrap方法的實質執行代碼。動畫

  • MDrawableImpl public Drawable wrap(Drawable drawable) { // No need to wrap on M+ //未對Drawable實例作任何處理,直接返回 return drawable; } 可見在SDK版本>= 23(MDrawableImpl)狀況下:DrawableCompat.wrap(@NonNull Drawable drawable)直接返回了原始的Drawable實例
  • LollipopDrawableImpl public Drawable wrap(Drawable drawable) { return DrawableCompatLollipop.wrapForTinting(drawable); } 繼續跟蹤DrawableCompatLollipop.wrapForTinting: public static Drawable wrapForTinting(final Drawable drawable) { if (!(drawable instanceof TintAwareDrawable)) { //當前傳入的Drawable實例並不屬於TintAwareDrawable return new DrawableWrapperLollipop(drawable); } return drawable; } 繼續跟蹤DrawableWrapperLollipop: class DrawableWrapperLollipop extends DrawableWrapperKitKat { DrawableWrapperLollipop(Drawable drawable) { super(drawable); } *** } 繼續跟蹤DrawableWrapperKitKat: class DrawableWrapperKitKat extends DrawableWrapperHoneycomb { DrawableWrapperKitKat(Drawable drawable) { super(drawable); } *** } 繼續跟蹤DrawableWrapperHoneycomb: class DrawableWrapperHoneycomb extends DrawableWrapperGingerbread { DrawableWrapperHoneycomb(Drawable drawable) { super(drawable); } *** } 繼續跟蹤DrawableWrapperGingerbread:
static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
    private int mCurrentColor;
    private PorterDuff.Mode mCurrentMode;
    private boolean mColorFilterSet;
    DrawableWrapperState mState;  //mState默認是null
    private boolean mMutated;
    Drawable mDrawable;

    DrawableWrapperGingerbread(@Nullable Drawable dr) {
        mState = mutateConstantState();
        // Now set the drawable...
        setWrappedDrawable(dr);
    }
  • mState = mutateConstantState();
mutateConstantState()一路追蹤到底:
    DrawableWrapperState mutateConstantState() {
        //返回一個DrawableWrapperStateBase實例
        //mState默認是null
        return new DrawableWrapperStateBase(mState, null);
    }
    private static class DrawableWrapperStateBase extends DrawableWrapperState {
        //調用父類 DrawableWrapperState 的構造函數
        //orig就是DrawableWrapperGingerbread中的mState,默認是null
        DrawableWrapperStateBase(
                @Nullable DrawableWrapperState orig, @Nullable Resources res) {
            super(orig, res);
        }
        @Override
        public Drawable newDrawable(@Nullable Resources res) {
            return new DrawableWrapperGingerbread(this, res);
        }
    }
    protected static abstract class DrawableWrapperState extends Drawable.ConstantState {
        int mChangingConfigurations;
        Drawable.ConstantState mDrawableState;
        ColorStateList mTint = null;
        PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
        //orig就是DrawableWrapperGingerbread中的mState,默認是null
        DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) {
            //由於orig是null,因此mChangingConfigurations,mDrawableState,
            //mTint,mTintMode都是DrawableWrapperState中的默認值
            if (orig != null) {
                mChangingConfigurations = orig.mChangingConfigurations;
                mDrawableState = orig.mDrawableState;
                mTint = orig.mTint;
                mTintMode = orig.mTintMode;
            }
        }
}

代碼一路跟下來可見:mState = mutateConstantState(),mState被賦值爲一個新的DrawableWrapperState實例其中:mState(DrawableWrapperState)中,下面成員變量的值都是默認值: int mChangingConfigurations; Drawable.ConstantState mDrawableState; ColorStateList mTint = null; PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;ui

  • setWrappedDrawable(dr);
setWrappedDrawable(Drawable dr)一路追蹤到底:
    public final void setWrappedDrawable(Drawable dr) {
        if (mDrawable != null) {
            mDrawable.setCallback(null);
        }
        mDrawable = dr;
        if (dr != null) {
            dr.setCallback(this);
            // Only call setters for data that's stored in the base Drawable.
            setVisible(dr.isVisible(), true);
            setState(dr.getState());
            setLevel(dr.getLevel());
            setBounds(dr.getBounds());
            //mState不爲null:爲一個新的DrawableWrapperState實例
            if (mState != null) {
                //爲mState的mDrawableState賦值爲Drawable原始實例
                //關聯的ConstantState
                mState.mDrawableState = dr.getConstantState();
            }
        }
        invalidateSelf();
    }

這裏涉及到DrawableWrapperGingerbread中的幾個方法: setVisible(boolean visible, boolean restart) @Override public boolean setVisible(boolean visible, boolean restart) { //Drawable中的setVisible,用於控制Drawable實例是否執行動畫,對於AnimationDrawable實例會產生效果,控制是否執行動畫 return super.setVisible(visible, restart) || mDrawable.setVisible(visible, restart); } setState(final int[] stateSet)this

@Override
    public boolean setState(final int[] stateSet) {
        boolean handled = mDrawable.setState(stateSet);
        handled = updateTint(stateSet) || handled;
        return handled;
    }
    private boolean updateTint(int[] state) {
        //isCompatTintEnabled()這裏直接返回了true
        if (!isCompatTintEnabled()) {
            // If compat tinting is not enabled, fail fast
            return false;
        }
        //mState.mTint是默認值:null
        final ColorStateList tintList = mState.mTint;
        //mState.mTintMode是默認值:DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN
        final PorterDuff.Mode tintMode = mState.mTintMode;
        if (tintList != null && tintMode != null) {
            //tintList爲null,因此不會執行下面代碼
            final int color = tintList.getColorForState(state, tintList.getDefaultColor());
            if (!mColorFilterSet || color != mCurrentColor || tintMode != mCurrentMode) {
                setColorFilter(color, tintMode);
                mCurrentColor = color;
                mCurrentMode = tintMode;
                mColorFilterSet = true;
                return true;
            }
        } else {
            //tintList爲null
            mColorFilterSet = false;
            //執行的是其父類Drawable的clearColorFilter()
            clearColorFilter();
        }
        return false;
    }
    protected boolean isCompatTintEnabled() {
        // It's enabled by default on Gingerbread
        //這裏直接返回了true
        return true;
    }
Drawable的clearColorFilter方法:移除了當前Drawable實例關聯的ColorFilter
    public void clearColorFilter() {
        setColorFilter(null);
    }

可見:setState(dr.getState())這一步直接移除了Drawable實例關聯的ColorFilter. **setLevel直接使用的是其父類Drawable中的方法setLevel(@IntRange(from=0,to=10000) int level) **.net

Drawable的setLevel方法:
    public final boolean setLevel(@IntRange(from=0,to=10000) int level) {
        //在這裏,由於mLevel就是以前DrawableWrapperGingerbread構造函數中的Drawable dr的level值,
        //而level=dr.getLevel()返回的也是Drawable dr的level值,mLevel == level,
        //因此下面的代碼並不會執行
        if (mLevel != level) {
            mLevel = level;
            return onLevelChange(level);
        }
        return false;
    }

可見:setLevel(dr.getLevel())這一步並未產生實質影響,未執行處理邏輯。 setBounds直接使用的是其父類Drawable中的方法setBounds(@NonNull Rect bounds)

/**
     * Specify a bounding rectangle for the Drawable. This is where the drawable
     * will draw when its draw() method is called.
     */
    public void setBounds(@NonNull Rect bounds) {
        setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
    }

可見:setBounds(dr.getBounds())這一步爲新產生的DrawableWrapperGingerbread實例設置其繪製範圍與原始Drawable實例一致。

可見在SDK版本>= 21(LollipopDrawableImpl)狀況下:DrawableCompat.wrap(@NonNull Drawable drawable)返回了Drawable的子類DrawableWrapperGingerbread的一個新實例。 且在updateTint方法中移除了該新實例關聯過的ColorFilter,設置了該新實例的繪製範圍和原始Drawable實例相同

  • KitKatDrawableImpl 跟蹤終點同LollipopDrawableImpl
在KitKatDrawableImpl狀況下,wrap(Drawable drawable)一路跟蹤到底:
@Override
public Drawable wrap(Drawable drawable) {
    return DrawableCompatKitKat.wrapForTinting(drawable);
}
**
繼承關係跟蹤到最後仍是DrawableWrapperGingerbread,和LollipopDrawableImpl相同:
DrawableWrapperGingerbread(@Nullable Drawable dr) {
    mState = mutateConstantState();
    // Now set the drawable...
    setWrappedDrawable(dr);
}
  • JellybeanMr1DrawableImpl 跟蹤終點同LollipopDrawableImpl
  • HoneycombDrawableImpl 跟蹤終點同LollipopDrawableImpl
  • BaseDrawableImpl 跟蹤終點同LollipopDrawableImpl

綜上可見: 1:在SDK版本>= 23(MDrawableImpl)狀況下:DrawableCompat.wrap(@NonNull Drawable drawable)直接返回了原始的Drawable實例; 2:其他狀況下,DrawableCompat.wrap(@NonNull Drawable drawable)返回了Drawable的子類DrawableWrapperGingerbread的一個新實例,且在updateTint方法中移除了該新實例關聯過的ColorFilter,設置了該新實例的繪製範圍和原始Drawable實例相同; ####3:DrawableCompat.setTint(@NonNull Drawable drawable, @ColorInt int tint)

public static void setTint(@NonNull Drawable drawable, @ColorInt int tint) {
        IMPL.setTint(drawable, tint);
    }

以前分析wrap方法時候已經看到IMPL在不一樣SDK版本下有不一樣的實現,仍是逐一查看:

  • MDrawableImpl
setTint一路跟蹤:
@Override
public void setTint(Drawable drawable, int tint) {
    DrawableCompatLollipop.setTint(drawable, tint);
}
public static void setTint(Drawable drawable, int tint) {
    //執行的是Drawable原生的setTint方法
    drawable.setTint(tint);
}
  • LollipopDrawableImpl
setTint一路跟蹤:
@Override
public void setTint(Drawable drawable, int tint) {
    //同MDrawableImpl
    DrawableCompatLollipop.setTint(drawable, tint);
}
  • KitKatDrawableImpl
setTint一路跟蹤:
@Override
public void setTint(Drawable drawable, int tint) {
    DrawableCompatBase.setTint(drawable, tint);
}
public static void setTint(Drawable drawable, int tint) {
    if (drawable instanceof TintAwareDrawable) {
        ((TintAwareDrawable) drawable).setTint(tint);
    }
}
public interface TintAwareDrawable {
    void setTint(@ColorInt int tint);
    void setTintList(ColorStateList tint);
    void setTintMode(PorterDuff.Mode tintMode);
}

在上面分析DrawableCompat.wrap方法時候,已知其返回結果爲DrawableWrapperGingerbread新實例,看一下DrawableWrapperGingerbread類的聲明:class DrawableWrapperGingerbread extends Drawable implements Drawable.Callback, DrawableWrapper, TintAwareDrawable 因而可知,setTint實質執行的仍是DrawableWrapperGingerbread的setTint方法,繼續跟蹤:

setTint一路追蹤:
    @Override
    public void setTint(int tint) {
        setTintList(ColorStateList.valueOf(tint));
    }
    @Override
    public void setTintList(ColorStateList tint) {
        //1:在上面分析DrawableCompat.wrap時候,mState的值以下:
        //mState(DrawableWrapperState)中,下面成員變量的值都是默認值:
        //int mChangingConfigurations;
        //Drawable.ConstantState mDrawableState;
        //ColorStateList mTint = null;
        //PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;

        //2:在DrawableCompat.setTint時候,mState.mTint再也不爲空值
        mState.mTint = tint;
        updateTint(getState());
    }

    //以前DrawableCompat.wrap已經執行過一次updateTint,
    //如今DrawableCompat.setTint第二次執行!!
    private boolean updateTint(int[] state) {
        //isCompatTintEnabled()返回true
        if (!isCompatTintEnabled()) {
            // If compat tinting is not enabled, fail fast
            return false;
        }
        //此時mState.mTint已經在setTintList中賦值不爲null
        final ColorStateList tintList = mState.mTint;
        //mState.mTintMode依然爲默認值不爲null
        final PorterDuff.Mode tintMode = mState.mTintMode;
        if (tintList != null && tintMode != null) {
            //二者都不爲空,於是執行if條件下代碼

            //獲取當前狀態下對應的顏色
            final int color = tintList.getColorForState(state, tintList.getDefaultColor());
            //mColorFilterSet默認是false
            //color即爲setTint時候傳入的顏色
            //mCurrentColor默認值是0
            //tintMode是mState中的mTintMode=DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN
            //mCurrentMode默認值是null
            if (!mColorFilterSet || color != mCurrentColor || tintMode != mCurrentMode) {
                //對Drawable實例產生着色的,本質上仍是執行了Drawable中的setColorFilter方法。
                setColorFilter(color, tintMode);
                mCurrentColor = color;
                mCurrentMode = tintMode;
                mColorFilterSet = true;
                return true;
            }
        } else {
            mColorFilterSet = false;
            clearColorFilter();
        }
        return false;
    }
  • JellybeanMr1DrawableImpl 跟蹤終點同KitKatDrawableImpl
  • HoneycombDrawableImpl 跟蹤終點同KitKatDrawableImpl
  • BaseDrawableImpl 跟蹤終點同KitKatDrawableImpl

綜上可見: 1:在SDK版本>= 21(MDrawableImpl和LollipopDrawableImpl)狀況下:DrawableCompat.setTint(@NonNull Drawable drawable, @ColorInt int tint)執行的是Drawable原生的setTint方法; 2:其他狀況下,DrawableCompat.setTint(@NonNull Drawable drawable, @ColorInt int tint)本質上仍是執行了Drawable中的setColorFilter方法;

####4:原生Drawable.setTint(@ColorInt int tintColor)

public void setTint(@ColorInt int tintColor) {
    setTintList(ColorStateList.valueOf(tintColor));
}
public void setTintList(@Nullable ColorStateList tint) {
    //你沒有看錯,居然是個空方法!!!!
}

剛看到這兒時候也有些納悶,後來一想確定是咱們在獲取Drawable原始實例的時,獲取的實際上是Drawable的子類實例,在Drawable子類裏對setTintList作了重寫,有圖有真相: setTintList重寫.png ####5:Drawable.mutate()的做用

/**
     * Make this drawable mutable. This operation cannot be reversed. A mutable
     * drawable is guaranteed to not share its state with any other drawable.
     * This is especially useful when you need to modify properties of drawables
     * loaded from resources. By default, all drawables instances loaded from
     * the same resource share a common state; if you modify the state of one
     * instance, all the other instances will receive the same modification.
     *
     * Calling this method on a mutable Drawable will have no effect.
     *
     * @return This drawable.
     * @see ConstantState
     * @see #getConstantState()
     */
    public @NonNull Drawable mutate() {
        return this;
    }

單純看源碼解釋可能比較抽象,說的通俗一點,咱們經過Resource獲取mipmap文件夾下的一張資源圖片,在獲取Drawable初始實例時候若是不使用mutate(),那麼咱們對這個Drawable進行着色,不只改變了當前Drawable實例的顏色,之後任何經過這個圖片獲取到的Drawable實例,都會具備以前設置的顏色。因此若是咱們對一張資源圖片的着色不是APP全局生效的,就須要使用mutate()。

具體緣由: Android爲了優化系統性能,同一張資源圖片生成的Drawable實例在內存中只存在一份,在不使用mutate的狀況下,修改任意Drawable都會全局發生變化。 使用mutate,Android系統也沒有把Drawable實例又單獨拷貝一份,僅僅是單獨存放了狀態值,很小的一部分數據,Drawable實例在內存中仍然保持1份,於是並不會影響系統的性能。 具體變化能夠經過2張圖片說明: 1:不使用mutate: 共享狀態.png 2:使用mutate: 不共享狀態.png

以上就是我的分析的一點結果,如有錯誤,請各位同窗留言告知!

That's all !

相關文章
相關標籤/搜索