原文地址:其实你不懂: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作了重写,有图有真相: ####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: 2:使用mutate:
以上就是我的分析的一点结果,如有错误,请各位同窗留言告知!
That's all !