如何通过代码实现符合Material Design外观规范的Android禁用菜单项
问题本质
Android菜单系统的固有行为是:调用menuItem.setEnabled(false)或者在XML里写android:enabled="false",只会把菜单项的点击交互关掉,根本不会联动修改item视图的显示样式。默认的菜单item布局压根没做enabled状态对应的样式映射,所以哪怕交互已经禁用了,视觉上和正常可点的item完全没区别,自然不符合Material Design的禁用规范——Material规范里禁用态的可交互元素,是需要把整体透明度降到38%左右,同时去掉点击涟漪反馈的。
这种问题在老项目里特别常见:很多项目还停留在用AppCompat主题,没完全切到MaterialComponents的主题体系,自然拿不到系统自动适配的状态样式。
实际可用的解决方法
1. 全局主题适配(最省事,推荐)
只要项目已经引入了com.google.android.material:material依赖,根本不需要写一堆重复代码,只要把主题配对,系统会自动给所有菜单项套上对应的禁用态样式:
- 先确认
res/values/themes.xml里的应用主题,父级要换成Theme.MaterialComponents.DayNight.NoActionBar或者它的同系列分支,别再用老的Theme.AppCompat、Theme.Holo那套主题。 - 如果需要自定义禁用态的透明度(默认其实已经是符合规范的0.38),可以在主题里加自定义样式覆盖,示例代码:
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <!-- 把全局弹出菜单、溢出菜单的样式指向自定义配置 --> <item name="popupMenuStyle">@style/CustomPopupMenuStyle</item> <item name="actionOverflowMenuStyle">@style/CustomPopupMenuStyle</item> </style> <style name="CustomPopupMenuStyle" parent="Widget.MaterialComponents.PopupMenu"> <!-- 替换默认的item样式,绑定禁用态的alpha值 --> <item name="android:listItemStyle">@style/CustomMenuItemStyle</item> </style> <style name="CustomMenuItemStyle" parent="Widget.MaterialComponents.PopupMenu.Item"> <item name="android:alphaOnDisabled">0.38</item> </style>
配完之后,不管是代码里动态改menuItem.setEnabled(),还是XML里静态配置android:enabled="false",菜单项都会自动出正确的禁用外观,完全不用单独写逻辑给每个item改样式。
2. 自定义item布局做兼容
如果项目要兼容特别老的系统版本,或者需要自定义禁用态的效果,可以给菜单项指定自定义布局,自己绑定状态样式:
- 先写自定义的菜单项布局
menu_item_custom.xml,把图标和文字的透明度绑定到enabled状态:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="?attr/listPreferredItemHeightSmall" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="16dp"> <ImageView android:id="@+id/menu_icon" android:layout_width="24dp" android:layout_height="24dp" android:alpha="@drawable/selector_menu_alpha" android:contentDescription="@null"/> <TextView android:id="@+id/menu_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:textSize="16sp" android:alpha="@drawable/selector_menu_alpha"/> </LinearLayout>
- 写状态选择器
drawable/selector_menu_alpha.xml,实现禁用态的透明度切换:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 禁用态透明度0.38,符合Material规范 --> <item android:state_enabled="false" android:alpha="0.38"/> <item android:alpha="1.0"/> </selector>
- 在menu配置里给对应item指定自定义布局:
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/someItem" android:enabled="false" android:title="Some title" app:actionLayout="@layout/menu_item_custom"/> </menu>
这种写法下,菜单项的enabled状态会自动传递给布局里的所有子View,触发对应的样式变化,和系统原生菜单的行为一致。
3. 程序化动态改样式
如果不想动全局主题,也不想给每个item配自定义布局,也可以在菜单准备好的时候手动改样式:
@Override public void onPrepareOptionsMenu(@NonNull Menu menu) { super.onPrepareOptionsMenu(menu); MenuItem targetItem = menu.findItem(R.id.someItem); targetItem.setEnabled(false); // 拿到item的自定义视图,手动调整禁用态的透明度 View itemRoot = targetItem.getActionView(); if (itemRoot != null) { float targetAlpha = targetItem.isEnabled() ? 1f : 0.38f; itemRoot.setAlpha(targetAlpha); // 同步关掉禁用态的点击涟漪 itemRoot.setClickable(targetItem.isEnabled()); } }
注意:这个方法只对设置了自定义ActionView的菜单项有效,对于没有自定义视图的溢出菜单项,要拿到对应视图需要挂钩MenuPresenter的视图绑定流程,适配成本更高,优先推荐前两种全局方案。
内容的提问来源于stack exchange,提问作者Денис Вережников

