美文网首页
安卓自定义AppCompat支持库:让菜单项支持长按、按情景动态

安卓自定义AppCompat支持库:让菜单项支持长按、按情景动态

作者: 天下第九九八十一 | 来源:发表于2020-08-13 19:02 被阅读0次

一、简述定制方法

AppCompat 早在v7和eclipse的时代就可以自定义魔改。那时要改支持库非常简单,因为传统的eclipse项目不会自动引入依赖,一切要靠开发者架好,一开始固然麻烦,后面就简单多了。

现在支持库更新为AndroidX模块,但仍然可以定制,方法是利用Gradle脚本的排除语句,阻止其他AndroidX模块自动引入AppCompat支持库,取而代之的可以是本地项目里的一个子模块。(还没学会如何把library模块上传github)

比如,build.gradle里可以这样写:

    if (use_compat_official == '1') { //如果使用原版支持库
        api(libs_compat) 
    } else { //使用定制版支持库
        implementation project(':AppCompat')
        implementation(libs_appres){
            exclude module:"core"
        }
        configurations { //这个写法还没试对
            //all*.exclude group: 'androidx.appcompat'
        }
    }

    if (use_mat_official == '1') { //原版
        api(libs_mat) { //利用排除语句排除自带的appcompat依赖
            exclude module: "appcompat"
            exclude module: "appcompat-resources"
        }
    } else { //定制版
        implementation project(':Designer')
    }

上面引用了定义带properties.gradle中的一些属性:

use_mat_official=
use_compat_official=

libs_compat=androidx.appcompat:appcompat:1.1.0-rc01
libs_mat=com.google.android.material:material:1.1.0-alpha09

这样写,比分开包名和版本号好得多,不必单为版本号新建脚本和变量。

二、改进下拉菜单(Toolbar Overflow Menu)

2.1 支持长按

传统的处理方式是多级菜单,抑或将多而杂的功能选项放在设置里面,或者根本就没有。

类似于MXPlayer的多级菜单用起来很麻烦,尤其是两级都需要滚动的时候,你脑海里的“字符串定位匹配API”要被调用两次,中间还要等待菜单动画结束。

放设置里就更不用说了,而且很多还都是多级设置,比如via浏览器开关JS(“启用Javascript”)的功能,处于设置->高级设置的页面下。手指点快了还会打开两个高级设置。

支持长按,其实要加这一功能还算挺方便的,关键以下两点:

  • 控制Toolbar Overflow Menu显示的MenuPopup类会有两种实现被调用:CascadingMenuPopup、StandardMenuPopup。

  • 菜单项MenuItem的实现是MenuItemImpl类。

§ 2.1.1 修改支持库中的类(androidx.appcompat.view.menu)
  • 让抽象类 MenuPopup 实现列表项长按接口:
abstract class MenuPopup implements ShowableListMenu, MenuPresenter,
        AdapterView.OnItemClickListener

,/*and....*/ AdapterView.OnItemLongClickListener {


    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
        ListAdapter outerAdapter = (ListAdapter) parent.getAdapter();
        MenuAdapter wrappedAdapter = toMenuAdapter(outerAdapter);
        boolean ret = wrappedAdapter.mAdapterMenu.dispatchMenuItemLongClicked(wrappedAdapter.mAdapterMenu, (MenuItem) outerAdapter.getItem(position));

        return ret;
    }
  • 在CascadingMenuPopup、StandardMenuPopup两个实现创建MenuPopupWindow之时记得调用popupWindow.setOnItemLongClickListener(this),设置列表的长按监听器。
  • 菜单总类,SupportMenu 的实现为 MenuBuilder,在其中参考并改写单击事件的分发——dispatchMenuItemSelected,然后为其添加长按事件的分发处理:
    boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
        ((MenuItemImpl)item).isLongClicked=false;
        return mCallback != null && mCallback.onMenuItemSelected(menu, item);
    }

    public  boolean dispatchMenuItemLongClicked(MenuBuilder menu, MenuItem item) {
        ((MenuItemImpl)item).isLongClicked=true;
        return mCallback != null && mCallback.onMenuItemSelected(menu, item);
    }
  • 为菜单项实现类MenuItemImpl添加isLongClicked布尔变量。每次调用onMenuItemSelected前设置好是否是经过长按。
§ 2.1.2 Activity 中使用,复用onMenuItemClick回调以处理长按事件,可以精细控制是否关闭菜单。
toolbar.inflateMenu(R.menu.menu); //照常使用
toolbar.setOnMenuItemClickListener(this);


    @Override
    public boolean onMenuItemClick(MenuItem item) {
        int id = item.getItemId();
        MenuItemImpl mmi = item instanceof MenuItemImpl?(MenuItemImpl)item:null;
        boolean isLongClicked= mmi!=null && mmi.isLongClicked;
        /* 长按事件默认不处理,因此长按时默认返回false,且不关闭menu。 */
        boolean ret = !isLongClicked;
        boolean closeMenu=ret;
        switch(id){
        ……
        }

        if(closeMenu)
            closeIfNoActionView(mmi);
        return ret;
    }

    void closeIfNoActionView(MenuItemImpl mi) {
        if(mi!=null && !mi.isActionButton()) toolbar.getMenu().close();
    }

2.2 高效率处理动态情景模式的变化

怎么让工具菜单中的项目动态显示和隐藏,按照情景模式显示不同的菜单项组合呢?

或许有人想到setGroupVisible,但交叉的菜单项组合怎么处理?而且这个方法是遍历所有项目的,效率并不高。

可以直接设置内部的mItems数组,然后通知更新。

§ 2.2.1 改写 MenuBuilder.java
  • 添加:
    public void setItems(List<MenuItemImpl> newItems) {
        if(mItems!=newItems) {
            mItems=newItems;
            onItemsChanged(true);
        }
    }
§ 2.2.2 Activity 中使用
  • 首先,规则化 menu.xml 的格式:
<!--0--><item android:id="@+id/toolbar_action1" android:icon="@drawable/ic_btn_siglemode" android:title="" app:showAsAction="always"/>

<!--1--><item android:id="@+id/toolbar_action0" android:icon="@drawable/ic_btn_siglemode" android:title="@string/fold_all" app:showAsAction="never"/>

<!--2--><item android:id="@+id/toolbar_action12" android:icon="@drawable/ic_btn_siglemode" android:title="@string/peruse_mode" app:showAsAction="never"/>

  • 初始化:

    toolbar.inflateMenu(R.menu.menu);

    Menu AllMenus = toolbar.getMenu();

    List<MenuItemImpl> MenuInContext1 = MapNumberToMenu(0, 2, 3, 9, 11, 12);
    List<MenuItemImpl> MenuInContext2 = MapNumberToMenu(0, 1, 2, 3, 9, 10, 12);

    public List<MenuItemImpl> MapNumberToMenu(int...numbers) {
        MenuItemImpl[] items = new MenuItemImpl[numbers.length];
        for (int i = 0; i < numbers.length; i++) {
            items[i] = (MenuItemImpl) AllMenus.getItem(numbers[i]);
        }
        return Arrays.asList(items);
    }
  • 使用:
情景模式1:
    AllMenus.setItems(MenuInContext1);
情景模式2:
    AllMenus.setItems(MenuInContext2);

相关文章

网友评论

      本文标题:安卓自定义AppCompat支持库:让菜单项支持长按、按情景动态

      本文链接:https://www.haomeiwen.com/subject/kljidktx.html