这一节主要是记录翻看选项菜单的源码,因为最近使用到选项菜单,先看一下选项Menu的用法步骤:1 在xml文件中定义布局文件;2 重写onCreateOptionsMenu,创建目录;3 重写onOptionsItemSelected,响应目录的点击事件
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="https://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); } }
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/addevelopment/893424.html