Android 图片着色Tint后向兼容DrawableCompat库实现原理分析并简化封装
来源:互联网 发布:翩跹紫晶淘宝 编辑:程序博客网 时间:2024/06/05 01:12
前言:
之前在Android Ui开发中实现ImageView背景图片点击变色,往往会要求UI设计师提供两种不同颜色的图片分别作为selector的不同选中状态下的背景图,可以想象就是仅仅颜色不一样,就需要一个相同大小的图片,这样不仅仅浪费资源,加大res下图片资源体积,而且还需要重新加载一个新图片而导致增加系统负担。所以如果可以利用一种颜色的图片就可以实现出来多种颜色,对这个图片进行着色,实现不同种颜色的背景图片显示,那将大大的减少重复类型的图片。那接下来介绍一个自己封装系support.v4中DrawableCompat.setTintList的实现而来的TintDrawable类。
Drawable.setTintList介绍
这是在系统Api在21开始提供的方法,同时Android Support v4 的包中提供了 DrawableCompat兼容
public void setTintList(@Nullable ColorStateList tint) {}
可以看到在Drawable中并没有实现这个功能,具体均由子类,如BitmapDrawable:
@Override public void setTintList(ColorStateList tint) { mBitmapState.mTint = tint; mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode); invalidateSelf(); }
所以我们只需要拿到基类Drawable引用之后,直接调用这个setTintList这个方法,就可以实现改变颜色,不论子类是BitmapDrawable、ColorDrawable等。
同时也注意到这个是新Api,要兼容虽然可以直接使用DrawableCompat来实现,但一个如果仅仅使用了这个一个功能就需要导入support.v4包的话,个人接受不了,故必须弄清DrawableCompat兼容低版本的实现原理,希望做到封装成一个简单的类。
示例代码如下
1、改变图片背景颜色为白色:
ImageView button = (ImageView) findViewById(R.id.button );Drawable drawable= button.getDrawable();Drawable wrappedDrawable = DrawableCompat.wrap(drawable);DrawableCompat.setTintList(wrappedDrawable, ColorStateList.valueOf(Color.WHITE));button.setImageDrawable(wrappedDrawable );
2、一个图片实现点击变色
在color目录下new 一个 xml取名为“selector_imageview.xml”作为我们的控制颜色selector,点击变为粉红色,正常状态为白色。
<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="#FF4081" android:state_pressed="true" /> <item android:color="#ffffff" /></selector>
ImageView button = (ImageView) findViewById(R.id.button );Drawable drawable= button.getDrawable();Drawable wrappedDrawable = DrawableCompat.wrap(drawable);//设置selector资源DrawableCompat.setTintList(wrappedDrawable, getResources().getColorStateList(R.color.selector_imageview));button.setImageDrawable(wrappedDrawable );
DrawableCompat.setTintList后向兼容实现原理
使用DrawableCompat实现着色的代码如下:
public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) { final Drawable wrappedDrawable = DrawableCompat.wrap(drawable); DrawableCompat.setTintList(wrappedDrawable, colors); return wrappedDrawable;}
直接查看源码:
public static Drawable wrap(@NonNull Drawable drawable) { return IMPL.wrap(drawable); }public static void setTintList(@NonNull Drawable drawable, @Nullable ColorStateList tint) { IMPL.setTintList(drawable, tint); }
其中IMPL的实现如下:
static final DrawableImpl IMPL; 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 if (version >= 5) { IMPL = new EclairDrawableImpl(); } else { IMPL = new BaseDrawableImpl(); } }
可以看出对所有的系统版本都有支持,结合实际测试发现,的确所有版本都可以实现了着色功能。既然是高版本才有的Api,那看下低版本具体是如何实现的,直接从最低版本BaseDrawableImpl开始:
@Override public Drawable wrap(Drawable drawable) { return DrawableCompatBase.wrapForTinting(drawable); } @Override public void setTintList(Drawable drawable, ColorStateList tint) { DrawableCompatBase.setTintList(drawable, tint); }
继续看DrawableCompatBase的实现:
public static Drawable wrapForTinting(Drawable drawable) { if (!(drawable instanceof DrawableWrapperDonut)) { return new DrawableWrapperDonut(drawable); } return drawable; } public static void setTintList(Drawable drawable, ColorStateList tint) { if (drawable instanceof DrawableWrapper) { ((DrawableWrapper) drawable).setCompatTintList(tint); } }
可以看到返回尽然DrawableWrapperDonut这个类,继承了Drawable并其实现了DrawableWrapper的接口:
class DrawableWrapperDonut extends Drawable implements Drawable.Callback, DrawableWrapper{......}
public interface DrawableWrapper { void setCompatTint(int tint); void setCompatTintList(ColorStateList tint); void setCompatTintMode(PorterDuff.Mode tintMode); Drawable getWrappedDrawable(); void setWrappedDrawable(Drawable drawable);}
所以都是通过DrawableWrapperDonut 来实现的:
@Override public void setCompatTintList(ColorStateList tint) { mState.mTint = tint; updateTint(getState()); } private boolean updateTint(int[] state) { if (!isCompatTintEnabled()) { // If compat tinting is not enabled, fail fast return false; } final ColorStateList tintList = mState.mTint; final PorterDuff.Mode tintMode = mState.mTintMode; if (tintList != null && tintMode != 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 { mColorFilterSet = false; clearColorFilter(); } return false; }
可以看到setTintList最后都是,先获取color值以及tintMode ,并通过 setColorFilter(color, tintMode)来设置。setColorFilter在Drawable中的实现
public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) { setColorFilter(new PorterDuffColorFilter(color, mode)); } public abstract void setColorFilter(@Nullable ColorFilter colorFilter);
发现是个抽象函数,子类必须实现,那看下子类DrawableWrapperDonut 是如何实现的:
@Override public void setColorFilter(ColorFilter cf) { mDrawable.setColorFilter(cf); }
发现转而交给上文中new DrawableWrapperDonut(drawable)时候传递而来的drawbale实现
DrawableWrapperDonut(@Nullable Drawable dr) { mState = mutateConstantState(); mDrawable = dr; }
就上文图片变成白色的例子而言,此时的mDrawable =button.getDrawable(),这个getDrawable()获取的是ImageView的图片背景BitmapDrawable,具体实现可以参考我的另一片文章:图片加载原理,所以 mDrawable.setColorFilter(cf)是由BitmapDrawable,可以看下其实现:
@Override public void setColorFilter(ColorFilter colorFilter) { mBitmapState.mPaint.setColorFilter(colorFilter); invalidateSelf(); }
可以得知就是对mBitmapState画笔mPaint设置ColorFilter属性,然后待在View绘制背景时候,会调用背景drawable.draw(Canvas canvas),在BitmapDrawable.draw(Canvas canvas)中canvas会利用paint来背景,其属性ColorFilter从而改变背景颜色。
简化封装
既然后向兼容都是DrawableWrapperDonut这个类实现的,那为何不直接抽象出来直接使用,而不再使用support.v4包,个人封装了一个类TintDrawable,模拟DrawableWrapperDonut实现,甚至可以直接使用。
细节注意
上文中有改变ImageView背景的例子,现在我们新建另一个ImageView,不改变背景颜色直接加载原图显示,会发现图片还是改过颜色的背景,而不是原图,这里主要是系统加载图片原理决定的(具体参考我的另一篇文章:Res目录下资源如图片文件和xml文件资源如何被加载显示出来),在系统加载一次图片之后,会对这次加载出来的Drawable做缓存,资源id作为Key,所以当加载相同资源id时候,会先查缓存,没有就加载。而上述第一次加载图片之后并且着色,改变了第一加载图片drawable对象,并且是直接改变该缓存对象,所以后面加载相同资源都是被改变过的Drawable。
解决办法:通过mutate复制一个相同类型对象出来,而不改变缓存的对象
ImageView button = (ImageView) findViewById(R.id.button );//通过mutate()复制加载出来的对象Drawable drawable= button.getDrawable().mutate();Drawable wrappedDrawable = DrawableCompat.wrap(drawable);DrawableCompat.setTintList(wrappedDrawable, ColorStateList.valueOf(Color.WHITE));button.setImageDrawable(wrappedDrawable );
- Android 图片着色Tint后向兼容DrawableCompat库实现原理分析并简化封装
- Android 图片着色 Tint 详解
- Android 低版本实现Tint--着色功能
- Android 着色器 Tint
- Android 着色器tint
- Android 图片着色 Tint 详解2—xml设置、selector
- Drawable 着色的后向兼容方案
- Android 着色器 Tint 研究
- Android 着色器 Tint 研究
- Android学习-----DrawableCompat(给Drawable 着色)的使用
- DrawableCompat使用:一张图片实现selector效果
- 浅谈 Android L 的 Tint(着色)
- 浅谈 Android L 的 Tint(着色)
- 浅谈 Android L 的 Tint(着色)
- Android着色器tint相关剖析
- Android开发-状态栏着色原理和API版本兼容处理
- 谈谈Android Material Design 中的Tint(着色)
- 谈谈Android Material Design 中的Tint(着色)
- Uinux/linux vi保存退出命令 (如何退出vi)
- shell命令行内快速跳转
- JavaScript面试之预编译与执行
- 图像融合(一)--概述
- mysql mac中相关问题
- Android 图片着色Tint后向兼容DrawableCompat库实现原理分析并简化封装
- 用可变参数扩展printf
- Unity 3D追踪效果的实现 目标箭头指引
- TortoiseGit学习笔记(二)
- Unity shader实现屏幕模糊特效
- sklearn: model_selection
- 移动webapp适配方案大全
- Android7.0中文文档(API)-- HeaderViewListAdapter
- 框架模式MVP在安卓中的实践