记解决MaterialButton背景颜色与设置值不同

最近的开发过程中,因为设计的风格采用了Android的Material风格,因此我们在项目开发过程中也使用了Android 的Material组件和主题,但是开发过程中在使用MaterialButton的时候,我们给按钮设置的背景颜色和实际展示的背景颜色不一样。网上搜索了一番也没找到原因,于是便开始查阅MateriButton的代码。

经过一番研读终于找到原因,最终通过在style文件中添加如下设置解决。

xml 复制代码
<item name="elevationOverlayEnabled">false</item>

MaterialButton介绍

Google在Material库中给我们提供了MaterialButton组件,我们可以通过设置很多属性来设置它的样式,仅仅背景就可以设置它的边框,背景的圆角,背景的颜色,甚至可以自己设置背景的形状,除此之外还能设置文字样式,按钮上的图标等等。因为我们今天的主题是关于背景的问题的,这里我们仅仅介绍背景设置相关的东西。

正常情况下,对于一个Android的View我们可以通过设置setBackground() setBackgroundColor() setBackgroundResource()等方法来设置View的背景,而MaterialButton为了让我们能够直接修改颜色,设置圆角,则重写了setBackgroundColor()方法,实现如下:

java 复制代码
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方法中我们可以看到如下的代码:

java 复制代码
fillPaint.setColorFilter(tintFilter);

以上代码实现来背景颜色的修改。

在setTintList()的代码中和调用的函数中,我们发现了这样一行代码它修改了tintFilter这个变量。

java 复制代码
tintFilter =  
    calculateTintFilter(  
        drawableState.tintList,  
        drawableState.tintMode,  
        fillPaint,  
        /* requiresElevationOverlay= */ true);

此处的tintList为我们刚刚设置的颜色,tintMode默认值是SRC_IN均不为空,该方法内部又调用了calculateTintColorTintFilter()方法。

到这里我们总结一下,setBackgroud是通过tint来修改了背景的颜色,tint的实现其实就是使用了Android画笔的颜色混合滤镜(PorterDuffColorFilter)来实现的。

背景颜色为什么与设置的不同?

java 复制代码
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,那就说明这个方法内部把我们的颜色修改了,查看其实现,果然如此,代码如下:

java 复制代码
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,那看来也不是一定会改变颜色了,继续看它的实现。

java 复制代码
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的源码可以看到初始化的时候通过如下代码初始化了该值。

arduino 复制代码
MaterialAttributes.resolveBoolean(context, R.attr.elevationOverlayEnabled, false)

看到这里,我们也就知道该如何解决我们的问题了,也就是在AppTheme或者当前Activity的Theme中修改elevationOverlayEnabled 为false。

除了Button之外,Material的其他一些组件也有同样使用这个属性来设置是否修改颜色的,遇到的时候也可以同样的方式解决。

原文地址: isming.me/2024-01-04-...

相关推荐
00后程序员张1 天前
对比 Ipa Guard 与 Swift Shield 在 iOS 应用安全处理中的使用差异
android·开发语言·ios·小程序·uni-app·iphone·swift
星辰徐哥1 天前
鸿蒙APP开发从入门到精通:ArkUI组件库详解与常用组件实战
华为·app·harmonyos·组件·arkui·组件库
悠哉清闲1 天前
不同车型drawable不同
android·开发语言
00后程序员张1 天前
在 iOS 设备上同时监控 CPU、GPU 与内存的方法
android·ios·小程序·https·uni-app·iphone·webview
测试_AI_一辰1 天前
项目实践笔记 9:打卡/日报Agent项目Bug 修改与稳定性收口(v1.0)
android·开发语言·人工智能·功能测试·ai编程·ab测试
马 孔 多 在下雨1 天前
Kotlin协程进阶王炸之作-Kotlin的协程到底是什么
android·开发语言·kotlin
冬奇Lab1 天前
【Kotlin系列15】多平台开发实战:一次编写,多端运行
android·开发语言·kotlin
Dxy12393102161 天前
告别默认排序:MySQL自定义排序的“炼金术”
android·数据库·mysql
请叫我大虾1 天前
发现一个jdk中ArrayList的小BUG
android·java·bug
一起养小猫1 天前
Flutter for OpenHarmony 实战:双控制系统实现(按钮+键盘)
android·flutter·计算机外设·harmonyos