美文网首页
安卓自定义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