记解决MaterialButton背景颜色与设置值不同
最近的开发过程中,因为设计的风格采用了Android的Material风格,因此我们在项目开发过程中也使用了Android 的Material组件和主题,但是开发过程中法使用MaterialButton的时候,我们给按钮设置的背景颜色和实际展示的背景颜色不一样。网上搜索了一番也没找到原因,于是便开始查阅MateriButton的代码。 经过一番研读终于找到原因,最终通过在style文件中添加如下设置解决。 1 <item name="elevationOverlayEnabled">false</item> MaterialButton介绍 Google在Material库中给我们提供了MaterialButton组件,我们可以通过设置很多属性来设置它的样式,仅仅背景就可以设置它的边框,背景的圆角,背景的颜色,甚至可以自己设置背景的形状,除此之外还能设置文字样式,按钮上的图标等等。因为我们今天的主题是关于背景的问题的,这里我们仅仅介绍背景设置相关的东西。 正常情况下,对于一个Android的View我们可以通过设置setBackground() setBackgroundColor() setBackgroundResource()等方法来设置View的背景,而MaterialButton为了让我们能够直接修改颜色,设置圆角,则重写了setBackgroundColor()方法,实现如下: 1 2 3 4 5 6 7 8 public void setBackgroundColor(@ColorInt int color) { if (isUsingOriginalBackground()) { materialButtonHelper.setBackgroundColor(color); } else { // If default MaterialButton background has been overwritten, we will let View handle // setting the background color. super.setBackgroundColor(color); } } 通过查阅代码,我们可以看到MaterialButton内部通过MaterialButtonHelper这个类来管理它的背景,如果我们没有通过setBackground给这个Button设置一个背景Drawable,MaterialButtonHelper会帮我们创建一个Drawable,当我们调用 setBackgroundColor的时候,实际上也会在MaterialButtonHelper内部处理,至于MaterialButtonHelper如何创建BackgroundDrawabale的流程,可以自行去看源码。 修改背景颜色的具体过程 上面说到的MaterialButtonHelper创建的背景Drawable就是一个MaterialShapeDrawable,前面的setBackgroundColor调用后实际上会调用到MaterialShapeDrawable的setTintList()方法来修改背景的颜色,实际上就是使用了Tint来修改我们的背景,在draw方法中我们可以看到如下的代码: 1 fillPaint.setColorFilter(tintFilter); 以上代码实现来背景颜色的修改。 在setTintList()的代码中和调用的函数中,我们发现了这样一行代码它修改了tintFilter这个变量。 1 2 3 4 5 6 tintFilter = calculateTintFilter( drawableState.tintList, drawableState.tintMode, fillPaint, /* requiresElevationOverlay= */ true); 此处的tintList为我们刚刚设置的颜色,tintMode默认值是SRC_IN均不为空,该方法内部又调用了calculateTintColorTintFilter()方法。 到这里我们总结一下,setBackgroud是通过tint来修改了背景的颜色,tint的实现其实就是使用了Android画笔的颜色混合滤镜(PorterDuffColorFilter)来实现的。 背景颜色为什么与设置的不同? 1 2 3 4 5 6 7 8 9 10 11 private PorterDuffColorFilter calculateTintColorTintFilter( @NonNull ColorStateList tintList, @NonNull PorterDuff.Mode tintMode, boolean requiresElevationOverlay) { int tintColor = tintList.getColorForState(getState(), Color.TRANSPARENT); if (requiresElevationOverlay) { tintColor = compositeElevationOverlayIfNeeded(tintColor); } resolvedTintColor = tintColor; return new PorterDuffColorFilter(tintColor, tintMode); } 以上是calculateTintColorTintFilter方法的代码,我们知道requiresElevationOverlay总是true,那就一定会执行到compositeElevationOverlayIfNeeded,那就说明这个方法内部把我们的颜色修改了,查看其实现,果然如此,代码如下: 1 2 3 4 5 6 7 protected int compositeElevationOverlayIfNeeded(@ColorInt int backgroundColor) { float elevation = getZ() + getParentAbsoluteElevation(); return drawableState.elevationOverlayProvider != null ? drawableState.elevationOverlayProvider.compositeOverlayIfNeeded( backgroundColor, elevation) : backgroundColor; } 翻阅代码我们可以看到drawableState是在Drawable创建的时候就创建了,而elevationOverlayProvider则是在MaterialButtonHelper中调用drawable的initializeElevationOverlay方法来初始化的,为ElevationOverlayProvider,而正是它的compsiteOverlayIfNeeded方法来变化了颜色。 修改颜色为我们设置的颜色 看到这里,首先想到是吧这个elevationOverlayProvider设置为null,那我们不就不会调用这个方法,颜色也就是我们最初设置的颜色了吗。然而,我们没法在MaterialButton中或者它的子类中去拿到Drawable的DrawableState,因此只能作罢。 再来继续看,ElevationOverlayProvider的compositeOverlayIfNeeded方法,它既然有个IfNeeded,那看来也不是一定会改变颜色了,继续看它的实现。 1 2 3 4 5 6 7 public int compositeOverlayIfNeeded(@ColorInt int backgroundColor, float elevation) { if (elevationOverlayEnabled && isThemeSurfaceColor(backgroundColor)) { return compositeOverlay(backgroundColor, elevation); } else { return backgroundColor; } } 可以看到满足elevationOverlayEnabled且 backgroudColor和themSurfaceColor相同的情况下才会改变颜色,原来我设置的按钮颜色和主题中设置的colorSurface相同,此处我不可能去修改按钮颜色,我们只能去看看elevationOverlayEnabled能否修改,查看ElevationOverlayProvider的源码可以看到初始化的时候通过如下代码初始化了该值。 MaterialAttributes.resolveBoolean(context, R.attr.elevationOverlayEnabled, false) 看到这里,我们也就知道该如何解决我们的问题了,也就是在AppTheme或者当前Activity的Theme中修改elevationOverlayEnabled 为false。 除了Button之外,Material的其他一些组件也有同样使用这个属性来设置是否修改颜色的,遇到的时候也可以同样的方式解决。看完评论一下吧
以上内容来自博客订阅源,版权归原作者。关注作者后可以在 关注列表 第一时间查看作者文章。

登录后,加入这场讨论。
登录参与