深入分析ActionBarSherlock的MenuInflater实现

深入分析ActionBarSherlock的MenuInflater实现

【免费下载链接】ActionBarSherlock [DEPRECATED] Action bar implementation which uses the native action bar on Android 4.0+ and a custom implementation on pre-4.0 through a single API and theme. 【免费下载链接】ActionBarSherlock 项目地址: https://gitcode.com/gh_mirrors/ac/ActionBarSherlock

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解析器,它能够处理复杂的菜单结构,包括组、菜单项和子菜单,并支持丰富的属性设置。其核心特点包括:

  1. 递归解析:支持多层嵌套的菜单结构,通过递归调用parseMenu()方法实现
  2. 状态管理:使用MenuState类维护解析过程中的临时状态,清晰分离解析逻辑和状态存储
  3. 属性继承:菜单项可以继承所在组的属性,减少重复定义
  4. 事件绑定:支持通过XML属性直接绑定点击事件处理方法

使用MenuInflater时需要注意:

  • 菜单XML文件必须是编译资源,不能使用运行时动态创建的XML
  • ActionProvider和ActionView不能同时指定,否则ActionProvider会被忽略
  • 菜单项的某些属性(如checkable)可以在组级别统一设置

尽管ActionBarSherlock已被弃用,但其MenuInflater的实现思路仍然具有参考价值,展示了如何将XML资源高效解析为对象结构,并处理复杂的属性继承和冲突解决。

ActionBarSherlock菜单示例

官方文档:README.md 开发指南:CONTRIBUTING.md 版本历史:CHANGELOG.md

【免费下载链接】ActionBarSherlock [DEPRECATED] Action bar implementation which uses the native action bar on Android 4.0+ and a custom implementation on pre-4.0 through a single API and theme. 【免费下载链接】ActionBarSherlock 项目地址: https://gitcode.com/gh_mirrors/ac/ActionBarSherlock

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值