ColorDrawable源码分析
ColorDrawable是Drawable子类中最简单的,代表一种颜色图。
在代码中使用是非常简单的。一般对于纯色背景都可以使用ColorDrawable。
<?xml version="1.0" encoding="utf-8"?>
<color
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#0000ff">
</color>
这样就定义了一个纯蓝色的背景
然后就可以在Java代码中或者xml中使用
Drawable d = getResources().getDrawable(R.drawable.color_drawable);
Log.i(TAG, d.getClass().getSimpleName());//输出ColorDrawable
在xml中,就是比如某个组件的background之类的属性就可以把资源引用加上去,系统就会加载该资源
(一)前一篇对Drawable的分析中,有一个setColorFilter方法,可以改变颜色,那么我们看一下到底是不是这么回事?
float[] array = {0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0};//颜色矩阵计算,从蓝色转变为红色
d.setColorFilter(new ColorMatrixColorFilter(array));
parent.setBackground(d);//重新设置背景色
执行上述代码之后,发现颜色没变,还是蓝色!WTF?Drawable源码里面明明写的是通过setColorFilter就可以改变颜色啊!那我们要使用ColorDrawable改变颜色怎么办?
parent.setBackground(new ColorDrawable(Color.RED));//成功变成红色
显然,新创建一个ColorDrawable当然没问题,但是为什么setColorFilter没有用呢?
//ColorDrawable开头的注释内容
A specialized Drawable that fills the Canvas with a specified color.
Note that a ColorDrawable ignores the ColorFilter.//会忽略ColorFilter
原来是这样子,它会忽略ColorFilter的值,那么到底是在哪里处理的?因为ColorFilter是设置在Paint上的,所以我们看一下子类的draw方法,可能会有什么发现。
public void draw(Canvas canvas) {
// 获取ColorFilter
final ColorFilter colorFilter = mPaint.getColorFilter();
// 判断使用的颜色透明度是否为0,如果为0,则没必要绘制背景了
// 这里需要注意,如果动态设置颜色的时候没有明确透明度,那么这里就是按照24位RGB来计算的,最后就是0!!!
if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) {
if (colorFilter == null) {
mPaint.setColorFilter(mTintFilter);
}
// 关键点在这里啊,重新设置了颜色值,这样就和ColorFilter无关了
mPaint.setColor(mColorState.mUseColor);
// 可以看到,ColorDrawable是按照矩形绘制的
canvas.drawRect(getBounds(), mPaint);
// Restore original color filter.
// 再把ColorFilter保存回来
mPaint.setColorFilter(colorFilter);
}
}
到这里,我们就知道对于ColorDrawable为什么设置ColorFilter无效了。
(二)接下来看,ConstantState在这里的子类实现,ColorState
int mBaseColor; // 基础颜色,和透明度独立
int mUseColor; // 会被透明度影响的基础颜色
刚才我们在draw方法里面用到的也是mUseColor,因此,我们可以这样理解:
mBaseColor是保存了set后的颜色
mUseColor是保存每次变化后的颜色
为什么这么说呢?因为从源码中搜索可以看出,mBaseColor只有在setColor和updateFromTypedArray中才有更新
当颜色不一致时才设置并重绘自身,因此可以通过setColor的方式改变颜色
public void setColor(@ColorInt int color) {
if (mColorState.mBaseColor != color || mColorState.mUseColor != color) {
mColorState.mBaseColor = mColorState.mUseColor = color;
invalidateSelf();
}
}
从xml中获取属性值
state.mBaseColor = a.getColor(R.styleable.ColorDrawable_color, state.mBaseColor);
那么改变透明度就表示在mUseColor上面做动作么?
public void setAlpha(int alpha) {
alpha += alpha >> 7; // make it 0..256
final int baseAlpha = mColorState.mBaseColor >>> 24;//无符号右移,所以前24位都是0,最后8位是透明度
final int useAlpha = baseAlpha * alpha >> 8;
final int useColor = (mColorState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
// 先左移8位去掉8位透明度,再无符号右移8位。
// 前8位0,后24为RGB颜色,再或透明度左移24位,最后得到新的32位ARGB颜色
if (mColorState.mUseColor != useColor) {
mColorState.mUseColor = useColor;
invalidateSelf();
}
}
这么一大段左右移运算到底在干啥?为啥不能简单点?
useColor & 0xFFFFFF | alpha << 24//这样不行么?
说实话。。我没看懂透明度那部分为什么要这么计算。。Google的工程师还是天资聪颖
但是我们也可以看到,所有的改变都是在mUserColor上进行,mBaseColor是一个基准颜色。
(三)最关键的mutate方法,它到底做了什么?
private boolean mMutated;//保存是否改变过的布尔值
public Drawable mutate() {
// 如果没有改变过,并且是同一个Drawable(super.mutate方法直接返回this)
if (!mMutated && super.mutate() == this) {
// 可以看到直接新建了一个ColorState,这样就不和其他ColorDrawable共享状态,因此不会相互影响,相当于深拷贝
mColorState = new ColorState(mColorState);
// 标记已改变
mMutated = true;
}
// mColorState是成员变量,因此this是一个已经改变后的ColorDrawable
return this;
}
(四)那改变了之后还能不能复用呢?有没有改变mMutated变量的方法呢?
public void clearMutated() {
super.clearMutated();
mMutated = false;
}
// 可以看到该方法是可以清除标记位的,但是实际由于Hide,是无法调用的。所以一旦mutate调用了之后,就无法回头了哦。
(五)那么如果我想再创建一个一模一样的ColorDrawable应该怎么办呢?
@Override
public Drawable newDrawable() {
return new ColorDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
return new ColorDrawable(this, res);
}
// 这个this指代的就是ColorState,因为该方法是在ColorState类中定义的。
那么在Java代码中,就可以使用
d.getConstantState().newDrawable();
// 就可以创建一个和当前状态一模一样的ColorDrawable对象,但是他们还是共享一个ColorState哦。
对于最简单的ColorDrawable需要了解的就这么多了。下一节将讨论比ColorDrawable稍微复杂一点的ShapeDrawable。敬请期待。