ImageView动态着色以及兼容性问题
前言
最近看第三方库的demo发现ImageView使用了一个tint的属性,可以改变图片的颜色,这样在部分需求前可以减少项目中图片的数量,从而减少apk安装包大小。
- 注意:以下示例中的表现效果均有一个前提:xml和代码不要混写。否则会出现很多种情况
应用
示例中原图片如下:
ic_func.png
1.根据业务逻辑,不同状态下,图片的颜色不同
(1)xml中可以设置tint属性,如下:
<ImageView
android:id="@+id/iv_one"
android:layout_width="40dp"
android:layout_height="40dp"
android:tint="@color/colorPrimary"
android:src="@drawable/ic_func"/>
<ImageView
android:id="@+id/iv_two"
android:layout_below="@+id/iv_one"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_func"/>
效果如下:
效果图一
(2)根据业务动态修改,还得看代码中的方式(xml写法去除上面iv_one的tint属性):
代码实现一:
//Drawable drawable = getResources().getDrawable(R.drawable.ic_func);这种获取drawable效果是一样的
Drawable drawable = iv_one.getDrawable();//
Drawable wrap = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(wrap, ContextCompat.getColor(this,android.R.color.holo_red_dark));
iv_one.setImageDrawable(wrap);
iv_two.setImageResource(R.drawable.ic_func);
效果如下:
20200107112227.png
代码实现二:
//Drawable drawable = getResources().getDrawable(R.drawable.ic_func).mutate();这种获取drawable效果是一样的
Drawable drawable = iv_one.getDrawable().mutate();//
Drawable wrap = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(wrap, ContextCompat.getColor(this,android.R.color.holo_red_dark));
iv_one.setImageDrawable(wrap);
iv_two.setImageResource(R.drawable.ic_func);
效果如下:
20200107112204.png
代码一和代码二的区别:
在xml中都不添加tint属性的情况下,代码二多了一个mutate()方法,具体作用后面在说。只需要记住,如果你不想影响到该图片其他地方的使用,要添加mutate()方法。
代码实现三:
//Drawable drawable = getResources().getDrawable(R.drawable.ic_func).mutate();这种获取drawable效果是一样的
Drawable drawable = iv_one.getDrawable().mutate();//
Drawable wrap = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(wrap, ContextCompat.getColor(this,android.R.color.holo_red_dark));
iv_one.setImageDrawable(wrap);
iv_two.setImageDrawable(drawable);
这种写法效果和代码一相同。
2.设置图片的点击效果
- 注意:这个比较麻烦,主要是兼容问题
先看一看一般的实现步骤:
(1)首先在res下创建color资源目录,在color目录下创建一个color resource文件,如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/colorPrimary" android:state_pressed="true"/>
<item android:color="@android:color/holo_red_dark"/>
</selector>
(2)代码中如下:
//Drawable drawable = getResources().getDrawable(R.drawable.ic_func);
Drawable drawable = iv_one.getDrawable();
Drawable wrap = DrawableCompat.wrap(drawable).mutate();
DrawableCompat.setTintList(wrap,ContextCompat.getColorStateList(this,R.color.click_style_color));
iv_one.setImageDrawable(wrap);
iv_one.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this,"点击了",Toast.LENGTH_SHORT).show();
}
});
效果如下:
单击效果.gif
适配兼容性问题:
方案一:
/**
* 支持tintList着色ImageView
*/
public class TintableImageView extends AppCompatImageView {
private ColorStateList tint;
public TintableImageView(Context context) {
super(context);
}
public TintableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TintableImageView, defStyle, 0);
tint = a.getColorStateList(R.styleable.TintableImageView_iv_tint);
a.recycle();
}
public TintableImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
public void setColorFilter(ColorStateList tint) {
this.tint = tint;
super.setColorFilter(tint.getColorForState(getDrawableState(), 0));
}
public void setTintList(ColorStateList colorList) {
if (colorList != null) {
tint = colorList;
drawableStateChanged();
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (tint != null && tint.isStateful())
updateTintColor();
}
private void updateTintColor() {
int color = tint.getColorForState(getDrawableState(), 0);
setColorFilter(color);
}
}
attr.xml中声明属性如下:
<resources>
<declare-styleable name="TintableImageView">
<attr name="iv_tint" format="reference"/>
</declare-styleable>
</resources>
代码中使用:
TintableImageView iv_one;
... ...
iv_one.setTintList(ContextCompat.getColorStateList(this,R.color.click_style_color));
iv_two.setImageResource(R.drawable.ic_func);
方案二:
Android 着色器(TintList) 兼容性问题
这里面也介绍了为啥会出现兼容性的问题,同时给出了一个解决方案,其实解决方案一也是从这个原理来解决的,就是updateTintFilter() 方法只是更新了tintFilter的color和mode,并没有触发Drawable的绘制,所以按下时的着色 tint color自然没有显示出来,这两个解决方案都是添加了重绘的方法。但是随着google官方的androidx替代support包之后,方案二中的android.support.v4.graphics.drawable.DrawableCompatLollipop已经不存在了,所以在使用androidx的项目中,方案二是不生效的。
注意:
关于DrawableCompat的wrap(Drawable drawable)方法
通过这个方法获取的Drawable对象,在使用DrawableCompat类中的染色一类的方法,可以在不同的API级别上应用着色。
因此想要着色就先把原先的Drawable对象wrap一下后回去到新的Drawable对象。
上文提到的mutate()方法
Android为了优化系统性能,同一张资源图片生成的Drawable实例在内存中只存在一份,在不使用mutate的情况下,修改任意Drawable都会全局发生变化。
使用mutate,Android系统也没有把Drawable实例又单独拷贝一份,仅仅是单独存放了状态值,很小的一部分数据,Drawable实例在内存中仍然保持1份,因而并不会影响系统的性能。
相关文章:
Android Drawable / DrawableCompat # setTintList( ) 使用时一个值得注意的问题
其实你不懂:Drawable着色(tint)的兼容方案 源码解析