在平时开发中,我们有时候会用到设置图片的透明的功能,不假思索的情况下,我们自然就会通过设置getBackground().setAlpha(XXX)来去达到我们的目的,但是我们后续会发现白色背景变成透明的状况,并且引发很多界面出现颜色错乱的问题,是不是很无奈呢?
这个问题,我们网上一搜也都就知道怎么解决了?通用解决方式:
getBackground().setAlpha(XXX)改成getBackground().mutate().setAlpha(XXX);
这样就解决了。
我们在往后面看其他的解释,也就知道原因了,这里我们看一下mutate代码的google注释:
Drawable#
/**
* Make this drawable mutable. This operation cannot be reversed. A mutable
* drawable is guaranteed to not share its state with any other drawable.
* This is especially useful when you need to modify properties of drawables
* loaded from resources. By default, all drawables instances loaded from
* the same resource share a common state; if you modify the state of one
* instance, all the other instances will receive the same modification.
*
* Calling this method on a mutable Drawable will have no effect.
*
* @return This drawable.
* @see ConstantState
* @see #getConstantState()
*/
public @NonNull Drawable mutate() {
return this;
}
直接在线翻译:使这个可抽变。此操作无法反转。易变的可支取保证不与任何其他可支取共享其状态。当您需要修改drawables的属性时,这特别有用从资源加载。默认情况下,从同一资源共享一个公共状态;如果修改实例,所有其他实例将收到相同的修改。在可变Drawable上调用此方法将没有效果。
从上面的翻译可以反向推论得出,同一资源共享一个公共状态ConstantState,当然不同的Drawable的实现类有不同的ConstantState(比如ColorDrawable有内部类ColorState),但总的来说,还是因为公共状态的共享导致我们直接使用getBackground().setAlpha(XXX)时出现问题,而我们使用getBackground().mutate().setAlpha(XXX)时,这里以ColorDrawable的mutate()实现举例(其他Drawable实现类也大差不差的):
ColorDrawable#
/**
* A mutable BitmapDrawable still shares its Bitmap with any other Drawable
* that comes from the same resource.
*
* @return This drawable.
*/
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mColorState = new ColorState(mColorState);
mMutated = true;
}
return this;
}
我们可以看出,里面是重新创建了一个ColorState来实现自己的单独状态,而不影响共享状态的情况下来实现自己的状态需求的。到这里我们就有下面这张网上很流行的图的结论了:
然后我们就到这里了吗?NONONO,到这里我们还是不知道怎么实现公共状态ConstantState的共享的呢?我们一定要一探究竟,而非人云亦云。
上面从使用的角度和源码层了解了共享的机制,下面我们换个方向看一下共享的实现:
首先我们都知道,加载一张资源图片一般是通过getResources().getDrawable(iconId)来获得,然后我们继续看getDrawable里面实现,最终到这里:
Resources#
public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValueForDensity(id, density, value, true);
return impl.loadDrawable(this, value, id, density, theme);
} finally {
releaseTempTypedValue(value);
}
}
我们看到这里最终拿到了真正的Drawable对象,下面我们继续往里看,就到了共享状态的真正实现了,这里只看关键片段:
ResourcesImpl#loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id, int density, @Nullable Resources.Theme theme)
// Next, check preloaded drawables. Preloaded drawables may contain
// unresolved theme attributes.
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
这里我们就可以明显的看到类似key-value的意味了,然后我们继续看sPreloadedColorDrawables和sPreloadedColorDrawables,代码如下:
ResourcesImpl#
// Information about preloaded resources. Note that they are not
// protected by a lock, because while preloading in zygote we are all
// single-threaded, and after that these are immutable.
private static final LongSparseArray[] sPreloadedDrawables;
private static final LongSparseArray sPreloadedColorDrawables
= new LongSparseArray<>();
这里我们最终才真正看到了,此处是两个静态变量的LongSparseArray,我们可以得出:一旦加载到同一个图片资源Drawable时,LongSparseArray通过本地存储了同一key对应的Drawable.ConstantState,当下次加载同一资源,会使用共享一个公共状态ConstantState对象,因此才达到了同一资源的公共状态ConstantState的共享机制。到这里,我们就探究结束了,哈哈!
看完了到这里,是不是眼前一亮,不再人云亦云,恍然大悟的感觉呢,其实本猿也是经常一眼概论,但看过比别人的结论,总感觉自己只认识很浅的层面,因此想通过文章总结和看源码,来深刻认识一些东西,加深一些印象和思维,到这里,该说拜拜了,因个人认识有限,希望大家观看并评论指导!谢谢!
悟已往之不谏,知来者之可追。实迷途其未远,觉今是而昨非。纸上得来终觉浅,绝知此事要躬行,加油!