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">
<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 内多语言切换实现
推荐阅读
-
Android开发之菜单(menu)用法实例分析
-
Android开发笔记之: 数据存储方式详解
-
Android笔记之:深入ViewStub的应用
-
Android笔记之:深入为从右向左语言定义复杂字串的详解
-
Android笔记之:在ScrollView中嵌套ListView的方法
-
Android开发笔记之:在ImageView上绘制圆环的实现方法
-
Android开发笔记之:Handler Runnable与Thread的区别详解
-
Android开发笔记之:返回键的复写onBackPressed()介绍
-
Android开发笔记之:Log图文详解(Log.v,Log.d,Log.i,Log.w,Log.e)
-
Android开发笔记之:一分钟学会使用Logcat调试程序的详解