其实做原生 Android 开发这么久,对 Android 的原生控件还是比较满意的,毕竟 Material Design 也不是吃素的,但是目前不太满意的地方可能就是 Menu 了。

原生 Menu 的默认样式其实和其他控件一样,都是十分简约的。

Menu 默认样式

可以发现,默认的 Menu 当 Item 折叠的时候,下拉的 Menu 是会把 Toolbar 挡住的,虽然这并不影响我们的使用,但是却和我们既有的交互习惯相悖,而且默认的白色背景也和我们的主题颜色不搭,所以我们需要根据我们的需要定制。

但定制的时候,你会发现定制的方式比其他控件要相对繁琐,不像其他控件一样,只需要到相应的 layout 文件中修改控件的属性即可,它还需要修改其 values 文件并在 layout 中引用才行。

首先修改「styles.xml」文件,添加一个 <style> 定义其 Style 属性:

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="actionOverflowMenuStyle">@style/OverflowMenu</item>
    </style>

    <style name="OverflowMenu" parent="Base.Widget.AppCompat.PopupMenu.Overflow">
        <item name="overlapAnchor">false</item>
        <item name="android:popupBackground">@color/colorPrimary</item>
    </style>
</resources>

其中 overlapAnchor 是用来修改 Menu 悬浮位置的,默认为 TRUE,当为 FALSE 的时候则不遮挡 Toolbar

Menu 不遮挡 Toolbar

如果使用的是 Toolbar 而不是默认的 ActionBar,则不要忘了在 Toolbar 控件中添加 app:popupTheme 属性:

<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    app:popupTheme="@style/OverflowMenu" />

为了符合 Material Design 以及开发方便需要,十分建议使用 Toolbar 而不是 ActionBar,在定制 Menu 这个场景中,如果使用 ActionBar 则还需要使用比较繁琐的方法手动修改 Menu 中的字体颜色,而 Toolbar 只需定义好其主题即可自动修改。

android:popupBackground 则是用来修改下拉 Menu 时的背景颜色,默认是深灰色,我这里则把它设置为与 Toolbar 一样,这样会比较和谐。

Menu 修改 Background

Toolbar 上的 Menu 我们一般都会为其设置 ICON,因为用户对图形的感知会强于文字,所以 ICON 很重要,但是 Menu 默认情况下只会在 Toolbar 上显示 ICON,而下拉则仅显示文字,这就十分不友好了,甚至还浪费了美工的设计成果,必须要改。

但是这个居然在样式文件中是不能直接设置的,需要到 Activity 中通过反射设置其显示。

@Override
public boolean onMenuOpened(int featureId, Menu menu) {
    if (menu != null) {
        if (menu.getClass() == MenuBuilder.class) {
            try {
                Method method = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
                method.setAccessible(true);
                method.invoke(menu, true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    return super.onMenuOpened(featureId, menu);
}

这样,就能够将 ICON 和文字同时显示出来了。

下拉 Menu 显示 ICON

还有在最近的开发中我比较喜欢一体化布局,即 Toolbar 和下方融为一体,虽然不知道 Material Design 是否推崇这种设计方式,但我个人还是觉得挺好看的。

那么就会出现一种情况,Menu 的「More」三个点是默认的,颜色也是跟随默认的主题,那么如果我在项目中想修改它的颜色也会比较麻烦,如下:

Menu 修改 More 图标

我一开始的想法是,会有一个关于 Color 的属性让我可以直接设置,但是怎么找都找不到,无奈之下只能选择其他方式,修改这个默认的图标。

先导入一个喜欢的 ICON,我这里在『Android Studio』中提供的图标库中选择了跟原来相同的「more vert」图标,并修改为合适的颜色。

Android Studio Select Icon

然后在「styles.xml」文件中添加一个新的 <style>

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="actionOverflowMenuStyle">@style/OverflowMenu</item>
        <item name="actionOverflowButtonStyle">@style/OverflowButtonStyle</item>
    </style>
    ...
    <style name="OverflowButtonStyle" parent="Base.Widget.AppCompat.ActionButton.Overflow">
        <item name="android:src">@drawable/ic_more</item>
    </style>
</resources>

这里通过指定 actionOverflowButtonStyle 的 Style 设定其图标。

同时,我还想指定 Menu 中的字体颜色,那么我可以添加属性:

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="actionOverflowMenuStyle">@style/OverflowMenu</item>
        <item name="actionOverflowButtonStyle">@style/OverflowButtonStyle</item>
        <item name="actionMenuTextColor">@android:color/white</item>
    </style>
    ...
</resources>

只需指定 actionMenuTextColor 的颜色即可,我在这里直接指定为白色。

其实 Menu 还有其他可定制的属性,比如背景透明之类的,但都不能直接在 layout 中修改控件属性,所以的确是比较折腾的。