在菜单栏改版开发中,发现需要引入大量图片,其中像设置开关部分差异仅在颜色,但是需要两张图片。这样包体随着图片的添加也会变大。有没有更好的办法呢?
Android在5.0的时候推出Tint可以很方便的将一张图片”着色”为任何颜色。这样我们只需要放一张图片即可。
<ImageView
android:id="@+id/img_tint_1"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@android:color/darker_gray"
android:scaleType="center"
android:src="@drawable/icon_test_tint"/>
<ImageView
android:id="@+id/img_tint_2"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@android:color/darker_gray"
android:scaleType="center"
android:src="@drawable/icon_test_tint"
android:tint="@color/blue"/>
两个imageView是一样的,为了好展现结果,都添加了一个灰色背景。第二个添加了 android:tint属性,结果如下:
我们发现,我们的白色图标被“着色”为我们自己设置的蓝色。是不是很方便呢?
ImageView imageView = (ImageView) findViewById(R.id.img_tint_2);
Drawable drawable = getResources().getDrawable(R.drawable.icon_test_tint);
drawable.setTint(getResources().getColor(R.color.colorPrimary));
imageView.setImageDrawable(drawable);
其实除了android:tint之外还有android:backgroundTint熟悉,可以将背景进行“着色”
<ImageView
android:id="@+id/img_tint_1"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@android:color/darker_gray"
android:scaleType="center"
android:src="@drawable/icon_test_tint"/>
<ImageView
android:id="@+id/img_tint_2"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@android:color/darker_gray"
android:backgroundTint="@color/blue"
android:scaleType="center"
android:src="@drawable/icon_test_tint"/>
我们在第二个imageView上添加了android:backgroundTint="@color/blue"
属性,我们看下效果:
背景颜色按照我们设置的颜色被“着色”了。代码实现也类似。
android:tint=”“属性不仅可以是颜色值,还可以是一个selector 文件引用,在res目录下新建一个color目录,其中新建一个selector文件,引用该资源即可。
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="@color/blue" />
<item android:state_pressed="true" android:color="@color/blue" />
<item android:color="@android:color/white"/>
selector>
这样我们就能根据状态进行相应的着色。
既然Tint这么神奇,那我们看看是怎么实现的呢?我们看源码
// BitmapDrawable.java
private PorterDuffColorFilter mTintFilter;
@Override
// 设置tint,参数ColorStateList可以由color值或者selector xml构建
public void setTintList(ColorStateList tint) {
final BitmapState state = mBitmapState;
if (state.mTint != tint) {
state.mTint = tint;
// 生成PorterDuffColorFilter
mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
// 调用draw刷新view
invalidateSelf();
}
}
@Override
public void draw(Canvas canvas) {
...
final boolean clearColorFilter;
// 将生成的mTintFilter设置到paint上
if (mTintFilter != null && paint.getColorFilter() == null) {
paint.setColorFilter(mTintFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
}
updateDstRectAndInsetsIfDirty();
final Shader shader = paint.getShader();
final boolean needMirroring = needMirroring();
if (shader == null) {
if (needMirroring) {
canvas.save();
// Mirror the bitmap
canvas.translate(mDstRect.right - mDstRect.left, 0);
canvas.scale(-1.0f, 1.0f);
}
canvas.drawBitmap(bitmap, null, mDstRect, paint);
if (needMirroring) {
canvas.restore();
}
} else {
updateShaderMatrix(bitmap, paint, shader, needMirroring);
canvas.drawRect(mDstRect, paint);
}
if (clearColorFilter) {
paint.setColorFilter(null);
}
...
}
// Drawable.java
/**
* Ensures the tint filter is consistent with the current tint color and
* mode.
*/
@Nullable PorterDuffColorFilter updateTintFilter(@Nullable PorterDuffColorFilter tintFilter,
@Nullable ColorStateList tint, @Nullable PorterDuff.Mode tintMode) {
if (tint == null || tintMode == null) {
return null;
}
// 根据view状态取出当前设置color
final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
if (tintFilter == null) {
// 根据color与tintMode生成PorterDuffColorFilter
return new PorterDuffColorFilter(color, tintMode);
}
tintFilter.setColor(color);
tintFilter.setMode(tintMode);
return tintFilter;
}
我们通过以上代码可以知道,“着色”的实现其实就是在绘制时给画笔paint设置了一个ColorFilter,在drawable绘制时根据ColorFilter产生不同的渲染结果。对 PorterDuff不熟悉的同学可以看看我上次分享的文章:
Android PorterDuffXfermode使用中的一些坑
因为tint 是Android 5.0(API 21)添加的,所以必须在minSdkVersion>=21以上才能使用,为了兼容低版本有以下两种方式:
/**
* drawable 着色器,兼容低版本
*
* @param drawable drawable图片,避免引用同一资源需调用drawable.mutate()
* @param colors 着色颜色,可使用selector xml 文件
* @return
*/
public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) {
final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
DrawableCompat.setTintList(wrappedDrawable, colors);
return wrappedDrawable;
}
ImageView image = ...
image.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);