欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android源码笔记--Menu

程序员文章站 2022-08-11 07:51:31
这一节主要是看选项菜单的源码,因为最近使用到选项菜单,先看一下选项Menu的用法步骤:1在xml文件中定义布局文件;2重写onCreateOptionsMenu,创建目录;3 重写onOptionsItemSelected,响应目录的点击事件 &......

        这一节主要是记录翻看选项菜单的源码,因为最近使用到选项菜单,先看一下选项Menu的用法步骤:1 在xml文件中定义布局文件;2 重写onCreateOptionsMenu,创建目录;3 重写onOptionsItemSelected,响应目录的点击事件

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/menu_edit"
        android:title="编辑"
        android:showAsAction="always"
        />
    <item
        android:id="@+id/menu_search"
        android:title="搜索"
        android:showAsAction="always"
        />
</menu>
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    
    getMenuInflater().inflate(R.menu.menu,menu);
    //R.menu.menu是自己创建的目录xml文件
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    switch ( id ){
        case R.id.menu_edit :
            //TODO 
            break;
        case R.id.menu_search :
           //TODO 
            break;
        default:
            break;
    }
    return true;
}

      分析:菜单栏中的菜单项会分为两个部分。一部分可以直接在菜单栏中看见,我们可以称之为常驻菜单;另一部分会被集中收纳到溢出菜单中(就是菜单栏右侧的小点状图标)。一般情况下,常驻菜单项以图标形式显示(需要定义icon属性),而溢出菜单项则以文字形式显示(通过title属性定义)。主要是用到showAsAction这个属性,它的差异如下所示:always:菜单项永远不会被收纳到溢出菜单中,因此在菜单项过多的情况下可能超出菜单栏的显示范围;ifRoom:在空间足够时,菜单项会显示在菜单栏中,否则收纳入溢出菜单中; withText:无论菜单项是否定义了icon属性,都只会显示它的标题,而不会显示图标。使用这种方式的菜单项默认会被收纳入溢出菜单中; never:菜单项永远只会出现在溢出菜单中。

      注:如果项目中涉及到动态改变Menu的状态及点击事件,可以关注invalidateOptionsMenu方法,它可以让Menu重走创建方法以及onPrepareOptionsMenu方法;

getMenuInflater().inflate(R.menu.menu,menu);
public MenuInflater getMenuInflater() {
        return this.getDelegate().getMenuInflater();
    }
AppCompatDelegateImpl
   
public MenuInflater getMenuInflater() {
        if (this.mMenuInflater == null) {
            this.initWindowDecorActionBar();
            this.mMenuInflater = new SupportMenuInflater(this.mActionBar != null ? this.mActionBar.getThemedContext() : this.mContext);
        }

        return this.mMenuInflater;
    }
SupportMenuInflater
 
public void inflate(@LayoutRes int menuRes, Menu menu) {
        if (!(menu instanceof SupportMenu)) {
            super.inflate(menuRes, menu);
        } else {
            XmlResourceParser parser = null;

            try {
                parser = this.mContext.getResources().getLayout(menuRes);
                AttributeSet attrs = Xml.asAttributeSet(parser);
                this.parseMenu(parser, attrs, menu);
            } catch (XmlPullParserException var9) {
                throw new InflateException("Error inflating menu XML", var9);
            } catch (IOException var10) {
                throw new InflateException("Error inflating menu XML", var10);
            } finally {
                if (parser != null) {
                    parser.close();
                }

            }

        }
    }

然后Parser解析:

private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu) throws XmlPullParserException, IOException {
        SupportMenuInflater.MenuState menuState = new SupportMenuInflater.MenuState(menu);
        int eventType = parser.getEventType();
        boolean lookingForEndOfUnknownTag = false;
        String unknownTagName = null;

        String tagName;
        do {
            if (eventType == 2) {
                tagName = parser.getName();
                if (!tagName.equals("menu")) {
                    throw new RuntimeException("Expecting menu, got " + tagName);
                }

                eventType = parser.next();
                break;
            }

            eventType = parser.next();
        } while(eventType != 1);

        for(boolean reachedEndOfMenu = false; !reachedEndOfMenu; eventType = parser.next()) {
            switch(eventType) {
            case 1:
                throw new RuntimeException("Unexpected end of document");
            case 2:
                if (!lookingForEndOfUnknownTag) {
                    tagName = parser.getName();
                    if (tagName.equals("group")) {
                        menuState.readGroup(attrs);
                    } else if (tagName.equals("item")) {
                        menuState.readItem(attrs);
                    } else if (tagName.equals("menu")) {
                        SubMenu subMenu = menuState.addSubMenuItem();
                        this.parseMenu(parser, attrs, subMenu);
                    } else {
                        lookingForEndOfUnknownTag = true;
                        unknownTagName = tagName;
                    }
                }
                break;
            case 3:
                tagName = parser.getName();
                if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {
                    lookingForEndOfUnknownTag = false;
                    unknownTagName = null;
                } else if (tagName.equals("group")) {
                    menuState.resetGroup();
                } else if (tagName.equals("item")) {
                    if (!menuState.hasAddedItem()) {
                        if (menuState.itemActionProvider != null && menuState.itemActionProvider.hasSubMenu()) {
                            menuState.addSubMenuItem();
                        } else {
                            menuState.addItem();
                        }
                    }
                } else if (tagName.equals("menu")) {
                    reachedEndOfMenu = true;
                }
            }
        }

    }

来看一下invalidateOptionsMenu方法,看看它为什么能够使Menu重绘:

 AppCompatAcitivity.java 
   
      @Override
    public void invalidateOptionsMenu() {
        getDelegate().invalidateOptionsMenu();
    }

    /**
     * @return The {@link AppCompatDelegate} being used by this Activity.
     */
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
	
	public abstract class AppCompatDelegate  {}

AppCompatDelegate 的实现类AppCompatDelegateImpl

 AppCompatDelegateImpl.java
   
      @Override
    public void invalidateOptionsMenu() {
        final ActionBar ab = getSupportActionBar();
        if (ab != null && ab.invalidateOptionsMenu()) return;

        invalidatePanelMenu(FEATURE_OPTIONS_PANEL);
    }
private void invalidatePanelMenu(int featureId) {
        mInvalidatePanelMenuFeatures |= 1 << featureId;

        if (!mInvalidatePanelMenuPosted) {
         //以动画的形式来改变Menu的状态
            ViewCompat.postOnAnimation(mWindow.getDecorView(), mInvalidatePanelMenuRunnable);
            mInvalidatePanelMenuPosted = true;
        }
    }
ViewCompat.java 

 public static void postOnAnimation(@NonNull View view, Runnable action) {
        if (Build.VERSION.SDK_INT >= 16) {
            view.postOnAnimation(action);
        } else {
            view.postDelayed(action, ValueAnimator.getFrameDelay());
        }
    }

    public void postOnAnimation(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
		//通过ViewRootImpl中的弄舞者来实现动画的效果的状态改变;
            attachInfo.mViewRootImpl.mChoreographer.postCallback(
                    Choreographer.CALLBACK_ANIMATION, action, null);
        } else {
            // Postpone the runnable until we know
            // on which thread it needs to run.
            getRunQueue().post(action);
        }
    }
boolean mInvalidatePanelMenuPosted;
    int mInvalidatePanelMenuFeatures;
    private final Runnable mInvalidatePanelMenuRunnable = new Runnable() {
        @Override
        public void run() {
            if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_OPTIONS_PANEL) != 0) {
                doInvalidatePanelMenu(FEATURE_OPTIONS_PANEL);
            }
            if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_SUPPORT_ACTION_BAR) != 0) {
                doInvalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
            mInvalidatePanelMenuPosted = false;
            mInvalidatePanelMenuFeatures = 0;
        }
    };
void doInvalidatePanelMenu(int featureId) {
        PanelFeatureState st = getPanelState(featureId, true);
        Bundle savedActionViewStates = null;
        if (st.menu != null) {
            savedActionViewStates = new Bundle();
            st.menu.saveActionViewStates(savedActionViewStates);
            if (savedActionViewStates.size() > 0) {
                st.frozenActionViewState = savedActionViewStates;
            }
            // 停止之前Menu的状态
            st.menu.stopDispatchingItemsChanged();
            st.menu.clear();
        }
        st.refreshMenuContent = true;
        st.refreshDecorView = true;

        // Prepare the options panel if we have an action bar
        if ((featureId == FEATURE_SUPPORT_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL)
                && mDecorContentParent != null) {
            st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
            if (st != null) {
                st.isPrepared = false;
                //关键方法
                preparePanel(st, null);
            }
        }
    }
private boolean preparePanel(PanelFeatureState st, KeyEvent event) {
        if (mIsDestroyed) {
            return false;
        }

        // Already prepared (isPrepared will be reset to false later)
        if (st.isPrepared) {
            return true;
        }

        if ((mPreparedPanel != null) && (mPreparedPanel != st)) {
            // Another Panel is prepared and possibly open, so close it
            closePanel(mPreparedPanel, false);
        }
		//cb起到通知的作用
        final Window.Callback cb = getWindowCallback();

        if (cb != null) {
			//重新走创建menu的方法
            st.createdPanelView = cb.onCreatePanelView(st.featureId);
        }

        final boolean isActionBarMenu =
                (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR);

        if (isActionBarMenu && mDecorContentParent != null) {
            // Enforce ordering guarantees around events so that the action bar never
            // dispatches menu-related events before the panel is prepared.
            mDecorContentParent.setMenuPrepared();
        }

        if (st.createdPanelView == null &&
                (!isActionBarMenu || !(peekSupportActionBar() instanceof ToolbarActionBar))) {
            // Since ToolbarActionBar handles the list options menu itself, we only want to
            // init this menu panel if we're not using a TAB.
            if (st.menu == null || st.refreshMenuContent) {
                if (st.menu == null) {
                    if (!initializePanelMenu(st) || (st.menu == null)) {
                        return false;
                    }
                }

                if (isActionBarMenu && mDecorContentParent != null) {
                    if (mActionMenuPresenterCallback == null) {
                        mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
                    }
					//创建menu 需要的元素,以及menu的管理者
                    mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
                }

                // Creating the panel menu will involve a lot of manipulation;
                // don't dispatch change events to presenters until we're done.
                st.menu.stopDispatchingItemsChanged();
                if (!cb.onCreatePanelMenu(st.featureId, st.menu)) {
                    // Ditch the menu created above
                    st.setMenu(null);

                    if (isActionBarMenu && mDecorContentParent != null) {
                        // Don't show it in the action bar either
                        mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
                    }

                    return false;
                }

                st.refreshMenuContent = false;
            }

            // Preparing the panel menu can involve a lot of manipulation;
            // don't dispatch change events to presenters until we're done.
            st.menu.stopDispatchingItemsChanged();

            // Restore action view state before we prepare. This gives apps
            // an opportunity to override frozen/restored state in onPrepare.
            if (st.frozenActionViewState != null) {
                st.menu.restoreActionViewStates(st.frozenActionViewState);
                st.frozenActionViewState = null;
            }

            // Callback and return if the callback does not want to show the menu
            if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
                if (isActionBarMenu && mDecorContentParent != null) {
                    // The app didn't want to show the menu for now but it still exists.
                    // Clear it out of the action bar.
                    mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
                }
                st.menu.startDispatchingItemsChanged();
                return false;
            }

            // Set the proper keymap
            KeyCharacterMap kmap = KeyCharacterMap.load(
                    event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
            st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
            st.menu.setQwertyMode(st.qwertyMode);
            st.menu.startDispatchingItemsChanged();
        }

        // Set other state
        st.isPrepared = true;
        st.isHandled = false;
        mPreparedPanel = st;

        return true;
    }

分析:上面这个函数是让Menu重新创建的关键;

 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
        ActionMenuPresenterCallback() {
        }

        @Override
        public boolean onOpenSubMenu(MenuBuilder subMenu) {
            Window.Callback cb = getWindowCallback();
            if (cb != null) {
                cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu);
            }
            return true;
        }

        @Override
        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
            checkCloseActionMenu(menu);
        }
    }

本文地址:https://blog.csdn.net/ljt2724960661/article/details/108174757

相关标签: Android源码笔记