Android视图elevation属性搭配半透明颜色出现内部阴影异常问题
问题描述
以下是布局代码:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/bg_rounded" android:backgroundTint="@color/view_primary_color" android:elevation="4dp" android:foregroundTint="@android:color/white"> <com.google.android.material.textview.MaterialTextView android:id="@+id/tvTitle" style="@style/Header4.Semibold16.White" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_gravity="center" android:gravity="center" android:text="Add to cart" android:textAllCaps="false" app:tint="@android:color/white" tools:ignore="ContentDescription" /> <ProgressBar android:id="@+id/pbLoading" style="?android:attr/progressBarStyleSmall" android:layout_width="24dp" android:layout_height="24dp" android:layout_centerInParent="true" android:layout_gravity="center" android:visibility="gone" /> </FrameLayout>
以下是@color/view_primary_color的定义:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@color/colorPrimary" android:state_enabled="true" /> <item android:color="@color/colorPrimaryDisabled" android:state_enabled="false" /> </selector>
视图处于enabled启用状态时,背景色为colorPrimary;处于disabled禁用状态时,背景色为colorPrimaryDisabled,该颜色与colorPrimary参数几乎一致,仅alpha通道透明度为20%。
视图在空闲(启用)状态下的显示效果如下:
点击视图切换为禁用状态后显示效果如下:
按预期视图仅应变为稍透明的效果,但实际内部莫名出现了阴影。若使用非半透明颜色,或者移除视图的elevation属性,该问题就会消失。请问这是什么原因导致的?
回答
问题原因
这个现象是Android系统对带elevation属性的半透明View的渲染逻辑导致的:
设置了elevation的View,系统会先后绘制两层内容:
- View下方的阴影层
- View自身的背景和内容层
当View背景是完全不透明时,背景会完全遮挡住下方的阴影层,你只能看到View边缘外溢出的阴影,不会看到内部有异常。当你切换到禁用状态时,背景变成了alpha仅20%的半透明色,半透明背景无法完全遮挡下层的阴影,阴影就会透过背景显示出来,看起来就像View内部多了一层深色阴影。
你观察到的两个现象也和这个逻辑完全吻合:
- 使用非半透明颜色时问题消失:不透明背景完全挡住了下方阴影
- 移除
elevation属性时问题消失:没有elevation系统就不会绘制阴影层,自然不会有阴影透出
解决方案
这里提供几个常用的修复方案,你可以根据业务场景选择:
- 动态调整elevation
切换View禁用状态的同时同步修改elevation,禁用时置为0,恢复启用时改回原数值即可:
// 切换为禁用状态 button.isEnabled = false button.elevation = 0f // 恢复为启用状态 button.isEnabled = true button.elevation = resources.getDimension(R.dimen.elevation_4dp)
- 用View的alpha属性替代半透明背景
把selector里的禁用状态颜色改成和启用状态一致的不透明色,切换状态时直接修改View的整体alpha。这种方式整个View(包括边缘阴影)都会同步变透明,不会出现阴影透出的问题:
// 切换为禁用状态 button.isEnabled = false button.alpha = 0.2f // 恢复为启用状态 button.isEnabled = true button.alpha = 1f
- 修改阴影颜色为透明
如果不想调整elevation也不想改alpha,可以在禁用状态下把阴影颜色设置为透明,启用时再恢复:
// 禁用时 button.outlineSpotShadowColor = Color.TRANSPARENT button.outlineAmbientShadowColor = Color.TRANSPARENT // 恢复时改回原阴影色即可
内容的提问来源于stack exchange,提问作者Nikita Kalugin
相关产品推荐
相关产品推荐

