当前位置: 移动技术网 > 移动技术>移动开发>Android > Android源码笔记--Menu

Android源码笔记--Menu

2020年08月01日  | 移动技术网移动技术  | 我要评论
这一节主要是看选项菜单的源码,因为最近使用到选项菜单,先看一下选项Menu的用法步骤:1在xml文件中定义布局文件;2重写onCreateOptionsMenu,创建目录;3 重写onOptionsItemSelected,响应目录的点击事件<?xml version="1.0" encoding="utf-8"?><menu xmlns:android="http://schemas.android.com/apk/res/android"> &...

        这一节主要是记录翻看选项菜单的源码,因为最近使用到选项菜单,先看一下选项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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网