Android Espresso:Button背景色ViewMatcher实现遇转换及反射异常
问题根源
- ClassCastException原因:Android的Material Design Button默认背景是
RippleDrawable(带涟漪点击效果),调用setBackgroundColor后,颜色会被包裹在这个RippleDrawable内部,而非直接是ColorDrawable,强转必然报错。 - 反射失败原因:Android高版本(API 30+)限制了对系统隐藏字段的反射访问,
RippleDrawable$RippleState的mColor是隐藏字段,无法通过反射获取。 - 额外坑点:调用
withBackColor(R.color.custom_color_slider_widget_unselected)时传的是资源ID,但Drawable里存储的是实际颜色的int值,两者无法直接相等。
正确实现方案
下面是修复后的withBackColor ViewMatcher,同时处理RippleDrawable、ColorDrawable,以及资源ID转实际颜色值的问题:
import android.content.Context import android.graphics.drawable.ColorDrawable import android.graphics.drawable.RippleDrawable import android.view.View import android.widget.Button import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.matcher.BoundedMatcher import org.hamcrest.Description import org.hamcrest.Matcher fun withBackColor(colorResId: Int): Matcher<View?> { // 把资源ID转成实际颜色值 val targetColor = ApplicationProvider.getApplicationContext<Context>() .resources.getColor(colorResId, null) return object : BoundedMatcher<View?, Button>(Button::class.java) { override fun matchesSafely(button: Button): Boolean { val background = button.background return when (background) { is ColorDrawable -> background.color == targetColor is RippleDrawable -> { // 获取RippleDrawable内部的实际背景Drawable val contentDrawable = background.findDrawableByLayerId(android.R.id.background) (contentDrawable as? ColorDrawable)?.color == targetColor } else -> false } } override fun describeTo(description: Description) { description.appendText("with background color: $targetColor") } } }
调用注意事项
调用时直接传颜色资源ID即可,方法内部已完成资源ID到实际颜色值的转换:
onView(withId(R.id.continueBtn)).check(matches(withBackColor(R.color.custom_color_slider_widget_unselected)))
补充说明
- 如果Button使用了自定义Drawable(比如ShapeDrawable),可以在
matchesSafely里继续添加对应类型的判断逻辑。 - 避免使用反射访问系统隐藏API,不仅会因权限限制失败,还会因Android版本差异导致字段名变化,兼容性极差。
内容的提问来源于stack exchange,提问作者Neyross
相关产品推荐
相关产品推荐

