Android 7.0长按Actionbar的菜单选项显示Toast提示的实现

最近遇到要消除长按 Actionbar 上的菜单项会在选项底部出现一个 Toast 提示的需求,所以在网上查了很久解决方法,现配合源码简单整理一下。Actionbar 对于开发者来说想必就不用过多介绍了,就是下面这个东东:


Actionbar

在哪实现?

知道如何使用 Actionbar 的人肯定知道是在 activity 中的 onCreateOptionsMenu(Menu menu) 方法中将自己定义的 menu 选项加载到 menu 上等等。但是 menu 和其上的 item 到底是在哪里定义的?

通过在源码查找发现,与 menu 有关的类都在 /frameworks/base/core/java/com/android/internal/view/menu 目录下(此目录针对 Android 7.0)。

ActionMenuItem.java      ContextMenuBuilder.java  ListMenuItemView.java   MenuHelper.java       MenuView.java
ActionMenuItemView.java  ExpandedMenuView.java    ListMenuPresenter.java  MenuItemImpl.java     ShowableListMenu.java
ActionMenu.java          IconMenuItemView.java    MenuAdapter.java        MenuPopupHelper.java  StandardMenuPopup.java
BaseMenuPresenter.java   IconMenuPresenter.java   MenuBuilder.java        MenuPopup.java        SubMenuBuilder.java
CascadingMenuPopup.java  IconMenuView.java        MenuDialogHelper.java   MenuPresenter.java

这有很多个类,通过类名和验证可以找到 actionbar menu 中 item 对应的视图类,即 ActionMenuItemView.java。所以这个长按 item 显示 Toast 的提示一定是在这个类中实现的。

//frameworks\base\core\java\com\android\internal\view\menu\ActionMenuItemView.java

    @Override
    public boolean onLongClick(View v) {
        if (hasText()) {
            // Don't show the cheat sheet for items that already show text.
            return false;
        }

        final int[] screenPos = new int[2];
        final Rect displayFrame = new Rect();
        getLocationOnScreen(screenPos);
        getWindowVisibleDisplayFrame(displayFrame);

        final Context context = getContext();
        final int width = getWidth();
        final int height = getHeight();
        final int midy = screenPos[1] + height / 2;
        int referenceX = screenPos[0] + width / 2;
        if (v.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) {
            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
            referenceX = screenWidth - referenceX; // mirror
        }
        // 创建 Toast
        Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
        if (midy < displayFrame.height()) {
            // Show along the top; follow action buttons
            cheatSheet.setGravity(Gravity.TOP | Gravity.END, referenceX,
                    screenPos[1] + height - displayFrame.top);
        } else {
            // Show along the bottom center
            cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
        }
        cheatSheet.show();
        return true;
    }

果然,在 ActionMenuItemView 中的 onLongClick() 中,先是判断 item 是否已经显示了 text,若是的话则直接返回 false。若 text 没有显示的话,则根据 item 在屏幕中的位置来在其下方或屏幕底部显示一个 Toast 提示,而 Toast 显示的内容正是该 item 的 title。也就是下面在 xml 文件中所设置的 android:title 属性。

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/menu_add_contact"
        android:icon="@drawable/custom_drawable"
        android:title="@string/custom_title"
        app:showAsAction="ifRoom" />
    ...
</menu>

如何修改?

若是要修改这一行为有很多方式:

  • 系统层:对于可以访问和修改源码的开发者,可以直接修改 ActionMenuItemView.java 中的实现,但要注意,这只会对继承系统 Activity.java 的 activity 有效,对于继承了其他如 v7 包中的 AppCompatActivity.java 的 activity 就无法生效。而且修改系统层的代码对于 app 来说只会在这一个系统中生效,若是安装在别的源码编译的系统中则会使用它们自己的实现方式。

  • 应用层:若是在 app 中直接修改则不会有在系统中修改的许多问题。可以自己定义一个 actionbar 或者自己定义一个 view 做为 item 的 view,然后在自定义的 view 中做点击处理。相关修改可以查看Hiding of the Toast for long press on actionBar item

本文仅查看了长按 menu 选项出现 Toast 提示实现的部分,详细的完整 actionbar 框架网上有很多相关的介绍,感兴趣可以自己研究一下。

推荐阅读更多精彩内容