深入分析ActionBarSherlock的MenuInflater实现
ActionBarSherlock是一个已弃用的Android库,它提供了统一的Action Bar实现,在Android 4.0+系统上使用原生Action Bar,在4.0之前的系统上使用自定义实现。本文将深入分析其MenuInflater组件的实现原理,该组件负责将XML菜单资源解析为Menu对象。
MenuInflater核心功能与类结构
MenuInflater是ActionBarSherlock中负责解析菜单XML文件的关键类,位于actionbarsherlock/src/com/actionbarsherlock/view/MenuInflater.java。它的主要功能是将定义在XML中的菜单结构转换为运行时可用的Menu对象,实现了XML菜单资源到Java对象的映射。
MenuInflater的核心类结构包括:
- MenuInflater类:提供inflate()方法作为入口点,接收菜单资源ID和Menu对象
- MenuState内部类:维护菜单解析过程中的临时状态,处理组和菜单项的属性
- InflatedOnMenuItemClickListener内部类:处理菜单项的点击事件绑定
菜单XML解析流程
MenuInflater的解析流程主要通过inflate()方法启动,该方法接收菜单资源ID和目标Menu对象,然后通过XML解析器解析菜单结构并填充Menu对象。
解析入口:inflate()方法
public void inflate(int menuRes, Menu menu) {
XmlResourceParser parser = null;
try {
parser = mContext.getResources().getLayout(menuRes);
AttributeSet attrs = Xml.asAttributeSet(parser);
parseMenu(parser, attrs, menu);
} catch (XmlPullParserException e) {
throw new InflateException("Error inflating menu XML", e);
} catch (IOException e) {
throw new InflateException("Error inflating menu XML", e);
} finally {
if (parser != null) parser.close();
}
}
该方法首先获取XML资源解析器,然后调用parseMenu()方法开始实际解析过程。
核心解析逻辑:parseMenu()方法
parseMenu()方法实现了XML菜单的递归解析,处理<menu>、<group>和<item>三种主要标签:
private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)
throws XmlPullParserException, IOException {
MenuState menuState = new MenuState(menu);
int eventType = parser.getEventType();
String tagName;
// 跳过直到菜单开始标签
do {
if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
if (tagName.equals(XML_MENU)) {
eventType = parser.next();
break;
}
throw new RuntimeException("Expecting menu, got " + tagName);
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
boolean reachedEndOfMenu = false;
while (!reachedEndOfMenu) {
switch (eventType) {
case XmlPullParser.START_TAG:
tagName = parser.getName();
if (tagName.equals(XML_GROUP)) {
menuState.readGroup(attrs);
} else if (tagName.equals(XML_ITEM)) {
menuState.readItem(attrs);
} else if (tagName.equals(XML_MENU)) {
// 子菜单处理
SubMenu subMenu = menuState.addSubMenuItem();
parseMenu(parser, attrs, subMenu);
}
break;
case XmlPullParser.END_TAG:
// 标签结束处理逻辑
// ...
}
eventType = parser.next();
}
}
解析过程中,parseMenu()方法使用MenuState对象来跟踪当前解析状态,递归处理子菜单,并根据XML标签类型调用相应的处理方法。
菜单状态管理:MenuState类
MenuState是MenuInflater的内部类,负责维护菜单解析过程中的临时状态,处理组和菜单项的属性,并最终将解析结果添加到Menu对象中。
组属性处理
MenuState通过readGroup()方法处理<group>标签的属性:
public void readGroup(AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(attrs,
R.styleable.SherlockMenuGroup);
groupId = a.getResourceId(R.styleable.SherlockMenuGroup_android_id, defaultGroupId);
groupCategory = a.getInt(R.styleable.SherlockMenuGroup_android_menuCategory, defaultItemCategory);
groupOrder = a.getInt(R.styleable.SherlockMenuGroup_android_orderInCategory, defaultItemOrder);
groupCheckable = a.getInt(R.styleable.SherlockMenuGroup_android_checkableBehavior, defaultItemCheckable);
groupVisible = a.getBoolean(R.styleable.SherlockMenuGroup_android_visible, defaultItemVisible);
groupEnabled = a.getBoolean(R.styleable.SherlockMenuGroup_android_enabled, defaultItemEnabled);
a.recycle();
}
这些属性包括组ID、分类、顺序、可选行为、可见性和启用状态等,这些属性将作为该组内所有菜单项的默认属性。
菜单项属性处理
对于<item>标签,MenuState通过readItem()方法处理其属性:
public void readItem(AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(attrs,
R.styleable.SherlockMenuItem);
// 读取菜单项属性
itemId = a.getResourceId(R.styleable.SherlockMenuItem_android_id, defaultItemId);
final int category = a.getInt(R.styleable.SherlockMenuItem_android_menuCategory, groupCategory);
final int order = a.getInt(R.styleable.SherlockMenuItem_android_orderInCategory, groupOrder);
itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);
itemTitle = a.getText(R.styleable.SherlockMenuItem_android_title);
// ... 其他属性读取
a.recycle();
}
菜单项属性包括ID、标题、图标、快捷键、可选状态等。如果菜单项没有显式设置某些属性,则会继承其所在组的对应属性。
菜单项添加
解析完成后,MenuState通过addItem()或addSubMenuItem()方法将解析结果添加到Menu对象:
public void addItem() {
itemAdded = true;
setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
}
public SubMenu addSubMenuItem() {
itemAdded = true;
SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
setItem(subMenu.getItem());
return subMenu;
}
setItem()方法负责将解析得到的属性应用到MenuItem对象:
private void setItem(MenuItem item) {
item.setChecked(itemChecked)
.setVisible(itemVisible)
.setEnabled(itemEnabled)
.setCheckable(itemCheckable >= 1)
.setTitleCondensed(itemTitleCondensed)
.setIcon(itemIconResId)
.setAlphabeticShortcut(itemAlphabeticShortcut)
.setNumericShortcut(itemNumericShortcut);
// ... 其他属性设置
}
点击事件绑定机制
MenuInflater通过InflatedOnMenuItemClickListener内部类实现菜单项点击事件的绑定。当XML中定义了android:onClick属性时,MenuInflater会创建该类的实例并设置为菜单项的点击监听器。
public boolean onMenuItemClick(MenuItem item) {
try {
if (mMethod.getReturnType() == Boolean.TYPE) {
return (Boolean) mMethod.invoke(mRealOwner, item);
} else {
mMethod.invoke(mRealOwner, item);
return true;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
这种机制允许在XML中直接指定点击事件处理方法,简化了菜单与事件处理的绑定过程。
菜单解析过程中的冲突处理
MenuInflater在解析过程中会处理各种属性冲突情况,例如当菜单项同时指定了actionProvider和actionView时:
final boolean hasActionProvider = itemActionProviderClassName != null;
if (hasActionProvider && itemActionViewLayout == 0 && itemActionViewClassName == null) {
itemActionProvider = newInstance(itemActionProviderClassName,
ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE,
mActionProviderConstructorArguments);
} else {
if (hasActionProvider) {
Log.w(LOG_TAG, "Ignoring attribute 'actionProviderClass'."
+ " Action view already specified.");
}
itemActionProvider = null;
}
当同时指定了ActionProvider和ActionView时,MenuInflater会忽略ActionProvider并记录警告日志,确保只有一个有效的操作视图被应用。
实际应用示例
以下是一个典型的菜单XML文件示例,展示了MenuInflater能够解析的结构:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/group1"
android:checkableBehavior="single">
<item android:id="@+id/item1"
android:title="菜单项1"
android:icon="@drawable/icon1"
android:showAsAction="ifRoom"/>
<item android:id="@+id/item2"
android:title="菜单项2"
android:showAsAction="never"/>
</group>
<item android:id="@+id/submenu"
android:title="子菜单">
<menu>
<item android:id="@+id/subitem1"
android:title="子菜单项1"/>
</menu>
</item>
</menu>
MenuInflater会将上述XML解析为包含一个组和一个子菜单的Menu对象,组内包含两个菜单项,子菜单中包含一个子菜单项。
总结与注意事项
ActionBarSherlock的MenuInflater实现了一个功能完备的菜单XML解析器,它能够处理复杂的菜单结构,包括组、菜单项和子菜单,并支持丰富的属性设置。其核心特点包括:
- 递归解析:支持多层嵌套的菜单结构,通过递归调用parseMenu()方法实现
- 状态管理:使用MenuState类维护解析过程中的临时状态,清晰分离解析逻辑和状态存储
- 属性继承:菜单项可以继承所在组的属性,减少重复定义
- 事件绑定:支持通过XML属性直接绑定点击事件处理方法
使用MenuInflater时需要注意:
- 菜单XML文件必须是编译资源,不能使用运行时动态创建的XML
- ActionProvider和ActionView不能同时指定,否则ActionProvider会被忽略
- 菜单项的某些属性(如checkable)可以在组级别统一设置
尽管ActionBarSherlock已被弃用,但其MenuInflater的实现思路仍然具有参考价值,展示了如何将XML资源高效解析为对象结构,并处理复杂的属性继承和冲突解决。
ActionBarSherlock菜单示例
官方文档:README.md 开发指南:CONTRIBUTING.md 版本历史:CHANGELOG.md
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



