android开发分享Android源码笔记–Menu

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

(0)
上一篇 2021年10月20日
下一篇 2021年10月20日

精彩推荐