在ImageView中有tint属性,用于设置图片的颜色,其实现原理是使用了ColorFilter的PorterDuffColorFilter,来重新生成drawable,关键代码是:读取tint,设置ColorFilter,生成新的Drawable,重新绘制
// 读取tint
int tint = a.getInt(com.android.internal.R.styleable.ImageView_tint, 0);
if (tint != 0) {
setColorFilter(tint);
}
// 设置ColorFilter
public final void setColorFilter(int color) {
setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
public final void setColorFilter(int color, PorterDuff.Mode mode) {
setColorFilter(new PorterDuffColorFilter(color, mode));
}
// 生成新的Drawable并重新绘制
public void setColorFilter(ColorFilter cf) {
if (mColorFilter != cf) {
mColorFilter = cf;
mColorMod = true;
applyColorMod();
invalidate();
}
}
// 生成新的Drawable
private void applyColorMod() {
// Only mutate and apply when modifications have occurred. This should
// not reset the mColorMod flag, since these filters need to be
// re-applied if the Drawable is changed.
if (mDrawable != null && mColorMod) {
mDrawable = mDrawable.mutate();
mDrawable.setColorFilter(mColorFilter);
mDrawable.setXfermode(mXfermode);
mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
}
}
我们看到5.1之前的版本默认的PorterDuff.Mode是SRC_ATOP,如果想设置mode的话必须使用java代码,所以在5.1新增了tintMode属性,而且默认。如果兼容5.1之前的版本可以使用support库或androidx库的AppCompatImageView,使用app:tint和app:tintMode属性即可。
而对于TextView来说,想要对DrawableLeft等做变换,在android6.0才有drawableTint和drawableTintMode属性(可能是忘了加),而support库的AppCompatTextView也一直没有添加对应的兼容属性,直到androidx的1.1.0版本才添加上
<declare-styleable name="AppCompatTextView">
…………
<!-- Tint to apply to the compound (left, top, etc.) drawables. -->
<attr format="color" name="drawableTint"/>
<!-- Blending mode used to apply the compound (left, top, etc.) drawables tint. -->
<attr name="drawableTintMode">
<!-- The tint is drawn on top of the drawable.
[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
<enum name="src_over" value="3"/>
<!-- The tint is masked by the alpha channel of the drawable. The drawable’s
color channels are thrown out. [Sa * Da, Sc * Da] -->
<enum name="src_in" value="5"/>
<!-- The tint is drawn above the drawable, but with the drawable’s alpha
channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
<enum name="src_atop" value="9"/>
<!-- Multiplies the color and alpha channels of the drawable with those of
the tint. [Sa * Da, Sc * Dc] -->
<enum name="multiply" value="14"/>
<!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
<enum name="screen" value="15"/>
<!-- Combines the tint and drawable color and alpha channels, clamping the
result to valid color values. Saturate(S + D) -->
<enum name="add" value="16"/>
</attr>
</declare-styleable>
那么,如果想使用DrawableTint就有这些方案:
1.使用的是androidx库1.1.0之后的版本,直接使用兼容控件AppCompatTextView。
2.无法使用AppCompatTextView的情况,只能通过判断版本,高版本设置属性,低版本则通过java代码来实现对drawable的变换,然后重新设置drawable left等。
private void setDrawableLeft() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
textView.setCompoundDrawableTintList(getColorStateList(R.color.tint_color));
// 如果需要设置 mode
//textView.setCompoundDrawableTintMode(mode);
} else {
Drawable drawable = textView.getCompoundDrawables()[0];
Drawable left = tintListDrawable(drawable, ContextCompat.getColorStateList(this, R.color.tint_color));
textView.setCompoundDrawables(left,null,null,null);
}
}
public Drawable tintListDrawable(@NonNull Drawable drawable, ColorStateList colors) {
Drawable wrappedDrawable = getCanTintDrawable(drawable);
// 进行着色
DrawableCompat.setTintList(wrappedDrawable, colors);
// 如果需要设置 mode
//DrawableCompat.setTintMode(wrappedDrawable, mode);
return wrappedDrawable;
}
@NonNull
private Drawable getCanTintDrawable(@NonNull Drawable drawable) {
// 获取此drawable的共享状态实例
Drawable.ConstantState state = drawable.getConstantState();
// 对drawable 进行重新实例化、包装、可变操作
return DrawableCompat.wrap(state == null ? drawable : state.newDrawable()).mutate();
}
注意的地方,对Drawable变换时生成的Drawable必须使用mutate,否则可能导致其他也使用当前图片的地方也会发生变换,因为Android系统加载资源,资源都是缓存的,对资源的改变是永久的。