android菜单_Android:菜单类调查

android菜单

Android提供了不错的功能,可在标准框架内实例化您自己的mill 菜单运行。 他们甚至对如何在您的应用程序中使用它们有一半的指导 。 大多数用户将非常熟悉股票菜单,了解Google Maps,GMail,“联系人”列表甚至默认的背景窗口如何使用它们。

它们给您的用户(所有熟悉的事物)和您的设计美学带来的舒适感,它们平淡,无聊且陈旧。 但是他们一定是吗? 这就是问题,这是我想解决的问题。 让我们从研究Android菜单的位置,原因和方式开始。 具体来说,让我们找出菜单中的逻辑在代码中的何处,然后让我们找出如何对其进行更改。

标准主题

没有主题支持的Android的组成部分是什么? 当然,对于不同类型的菜单,有不同类型的主题。 这是从git信息库中的XML 存储库中提取的菜单的标准两个(实际上有三个):

<style name="Theme.IconMenu">
 <!-- Menu/item attributes -->
 <item name="android:itemTextAppearance">@android:style/TextAppearance.Widget.IconMenu.Item</item>
    <item name="android:itemBackground">@android:drawable/menu_selector</item>
    <item name="android:itemIconDisabledAlpha">?android:attr/disabledAlpha</item>
    <item name="android:horizontalDivider">@android:drawable/divider_horizontal_bright</item>
    <item name="android:verticalDivider">@android:drawable/divider_vertical_bright</item>
    <item name="android:windowAnimationStyle">@android:style/Animation.OptionsPanel</item>
    <item name="android:moreIcon">@android:drawable/ic_menu_more</item>
    <item name="android:background">@null</item>
</style>

<style name="Theme.ExpandedMenu">
 <!-- Menu/item attributes -->
 <item name="android:itemTextAppearance">?android:attr/textAppearanceLargeInverse</item>
    <item name="android:listViewStyle">@android:style/Widget.ListView.Menu</item>
    <item name="android:windowAnimationStyle">@android:style/Animation.OptionsPanel</item>
    <item name="android:background">@null</item> 
</style>

第一个主题是您按下手机上的菜单按钮时看到的主题,第二个主题是您按下第一个上的“更多”选项按钮时看到的主题。
并且,如果您检查清单XML文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.owein"
      android:versionCode="1"
      android:versionName="1.0">
 <application android:icon="@drawable/icon" android:label="@string/app_name"> 
 <activity android:name=".SampleApp" android:label="@string/app_name"> 
 <intent-filter> 
 <action android:name="android.intent.action.MAIN" /> 
 <category android:name="android.intent.category.LAUNCHER" /> 
     </intent-filter> 
     </activity> 
    </application> 
    <uses-sdk android:minSdkVersion="8" />
</manifest>

您会看到这些主题出现在清单的一部分中……嗯,出现在清单的一部分中……嗯……等等,我在任何地方都看不到它们! 他们究竟是怎么摄取到应用程序? 我应该如何克服这个限制?

好了,该花点时间研究一下。 戴上迪克·特雷西的帽子。 在某个地方可以提取主题,一旦找到它们所在的位置,便可以弄清楚需要做什么以替换我们自己的主题。

介绍IconMenuView和ExpandedMenuView

可以肯定地说,您看到的菜单实际上是几类的产品; 即动作的侦听器和View类的子级。 由于Android开发人员指南未提及如何个性化菜单样式或风格化,因此我们不得不假定这是有原因的。 这个原因必须潜伏在“内部” Android代码中。

查找主题的位置的一个线索是菜单视图的实例化位置,无论是在视图代码本身中还是在使视图放大的对象中。 通常,在View构造期间,将从Context类中检索Theme(和Style)并将其放入TypedArray中。 幸运的是,这正是在类中发生的事情: IconMenuViewExpandedMenuView

这是IconMenuView的构造函数的构造函数:

public IconMenuView(Context context, AttributeSet attrs) {
    super(context, attrs);

    TypedArray a =
        context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.IconMenuView, 0, 0);
    
   mRowHeight = 
   a.getDimensionPixelSize(com.android.internal.R.styleable
      .IconMenuView_rowHeight, 64);

    mMaxRows = a.getInt(com.android.internal.R.styleable.IconMenuView_maxRows, 2);
    mMaxItems = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItems, 6);
    
    mMaxItemsPerRow =  
      a.getInt(com.android.internal.R.styleable.IconMenuView_maxItemsPerRow, 3);

    mMoreIcon = 
      a.getDrawable(com.android.internal.R.styleable.IconMenuView_moreIcon);
    a.recycle();

    a = context.obtainStyledAttributes(attrs, 
       com.android.internal.R.styleable.MenuView, 0, 0);
    
    mItemBackground = 
       a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground);
   
    mHorizontalDivider = 
       a.getDrawable(com.android.internal.R.styleable.MenuView_horizontalDivider);
    
    mHorizontalDividerRects = new ArrayList<Rect>();
    mVerticalDivider =  
       a.getDrawable(com.android.internal.R.styleable.MenuView_verticalDivider);
    
    mVerticalDividerRects = new ArrayList<Rect>();
    
    mAnimations = 
       a.getResourceId(com.android.internal.R.styleable.
          MenuView_windowAnimationStyle, 0);
    a.recycle();

    if (mHorizontalDivider != null) {
        mHorizontalDividerHeight = mHorizontalDivider.getIntrinsicHeight();
    }

    // Make sure to have some height for the divider
    if (mHorizontalDividerHeight == -1){
        mHorizontalDividerHeight = 1;
    }

    if (mVerticalDivider != null) {
        mVerticalDividerWidth = mVerticalDivider.getIntrinsicWidth();

        // Make sure to have some width for the divider
        if (mVerticalDividerWidth == -1){
            mVerticalDividerWidth = 1;
        }
    }

    mLayout = new int[mMaxRows];

    // This view will be drawing the dividers
    setWillNotDraw(false);

    // This is so we'll receive the MENU key in touch mode
    setFocusableInTouchMode(true);
    // This is so our children can still be arrow-key focused
    setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
}

显而易见,所有这些值都在其中进行了硬编码。 真的没有办法替代您自己的。 您甚至无法提供具有4、5或6行的IconMenuView。 为此,您必须创建自己的派生自MenuView的类。

但是视图只是菜单的一个组成部分。 还有哪些其他组件以及它们如何协同工作?

建立菜单

在Android git存储库的内部菜单目录中,有许多与菜单相关的类。 我们最感兴趣的是实现MenuContextMenuSubMenu 。 为什么? 如果您回顾一下菜单指南,他们会在Activity的onCreateOptionsMenu下推荐以下代码段:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.game_menu, menu);
    return true;
}

这告诉我们,活动是硬编码的,它期望Menu的子类。 如果我们甚至想开始考虑构建自己的自定义菜单,我们将不得不使用此界面。
博客文章的这一部分出于某种原因被称为“构建菜单”。 实现Menu的类为MenuBuilderContextMenuBuilderSubMenuBuilder 。 MenuBuilder通过调用方法getMenuView实例化Menu的视图:

public View getMenuView(int menuType, ViewGroup parent) {
    if (menuType == TYPE_EXPANDED 
            && (mMenuTypes[TYPE_ICON] == null || !mMenuTypes[TYPE_ICON].hasMenuView())) {
        getMenuType(TYPE_ICON).getMenuView(parent);
    }
    return (View) getMenuType(menuType).getMenuView(parent);
}

它委托给一个内部类的方法( MenuType :: getMenuView ):

MenuView getMenuView(ViewGroup parent) {
    if (LAYOUT_RES_FOR_TYPE[mMenuType] == 0) {
        return null;
    }

    synchronized (this) {
        MenuView menuView = mMenuView != null ? mMenuView.get() : null;
        if (menuView == null) {
            menuView = (MenuView) getInflater().inflate(
                    LAYOUT_RES_FOR_TYPE[mMenuType], parent, false);
            menuView.initialize(MenuBuilder.this, mMenuType);
            mMenuView = new WeakReference<MenuView>(menuView);
            if (mFrozenViewStates != null) {
                View view = (View) menuView;
                view.restoreHierarchyState(mFrozenViewStates);
                mFrozenViewStates.remove(view.getId());
            }
        }
        return menuView;
    }
}

依次将View的构建委托给LayoutInflater 。 在此阶段,就是在这里将硬编码的布局资源文件传递给MenuView。

getInflater()函数调用中检索LayoutInflator

LayoutInflater getInflater() {
    // Create an inflater that uses the given theme for the Views it inflates
    if (mInflater == null) {
        Context wrappedContext = 
     new ContextThemeWrapper(mContext, THEME_RES_FOR_TYPE[mMenuType]);
        mInflater = (LayoutInflater) wrappedContext
                         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    return mInflater;
}

ContextThemeWrapper确保使用原始的Android菜单主题替换“活动”的主题。 再次进行了硬编码。

令人惊讶的是,ContextMenuBuider和SubMenuBuilder没有实现自己的版本。 它们依赖于与MenuBuilder相同的功能。 他们每个人都经过一个硬编码的主题。 您无法传递自己的菜单主题。 为此,我们需要创建一个从Menu派生的自定义构建器。 这听起来像是很多工作。

因此,如果LayoutInflator负责创建我们在手机上看到的View,那么MenuInflater的所有含义是什么,以及如何将这些按钮放入Menu本身?

菜单通货膨胀

因此,您已经从Android开发人员页面上阅读了有关菜单资源创建菜单的内容,对吗? MenuInflater是填充并放置占据MenuView的所有MenuItemsSubMenus的对象。 它的工作是解析menu.xml文件,并将该文件的内容加载到呈现给最终用户的视图中。 因此,它实际上不会创建或分配您要处理的菜单类型。 那直接来自主动性本身。

那么菜单在哪里出生?

我们想知道的是如何将库存活动与库存菜单分开? 为此,我们需要知道菜单的实际引用在何处保存。 直到我们看到对ContextMenu或OptionsMenu的引用调用“ new”的位置之前,我们可以编写任意数量的自定义菜单,这无关紧要

查看Android开发人员上Activity类的文档,有许多与菜单相关的方法,例如closeContextMenuonContextMenuClosedonContextItemSelectedonMenuOpenedonCreateContextMenu等。 最后一个是一个痛处,因为我们不是在菜单上操作一种方法(允许完全自定义的行为),而是在严格地处理ContextMenu(这扼杀了我们的创造力)。希望我们能够控制该菜单按钮pfft。

由于Android开发人员的常规活动指南或应用程序基础知识并没有进一步说明这种情况,我们再次前往git存储库只是为了找到一个特别令人沮丧的发现:

public void openContextMenu(View view) {
    view.showContextMenu();
}

他们已将ContextMenu附加到现有的每个View中! 但是我分心了。 如果我在搜索ContextMenu时遇到很多麻烦,也许OptionsMenu提供了更直接的途径:

public void openOptionsMenu() {
    mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
}

mWindow变量是在attach方法中设置的,如下所示:

mWindow = PolicyManager.makeNewWindow(this);

并且是Window类型的接口。 从Window的描述中:

此抽象类的唯一现有实现是android.policy.PhoneWindow,您需要在需要Window时实例化该实例。 最终,该类将被重构,并添加一个工厂方法以创建Window实例,而无需了解特定的实现。

我认为PolicyManager是该工厂方法,我们在另一篇博客文章中已经见过PhoneWindow 。 那么菜单在哪里诞生? 您可以从PhoneWindow的以下代码片段中进行猜测:

@Override
public boolean showContextMenuForChild(View originalView) {
    // Reuse the context menu builder
    if (mContextMenu == null) {
        mContextMenu = new ContextMenuBuilder(getContext());
        mContextMenu.setCallback(mContextMenuCallback);
    } else {
        mContextMenu.clearAll();
    }

    mContextMenuHelper = mContextMenu.show(originalView, 
       originalView.getWindowToken());
    return mContextMenuHelper != null;
}

结论

我将此博客文章命名为“如何实现自定义菜单类?” 而不先知道答案。 我要调查此问题的全部原因是为尚未完成的自定义菜单创建一个开源库。 在这个教程/徽标中,我发现的很少。

要回答这个问题,您可以根据需要实现它们,但永远无法使用它们。 我的问题是给Google工程师。 为什么? 为什么会这样呢? 为什么无法将自定义主题传递给菜单? 为什么我不能选择如何设置样式? 是的,我可以理解,您不希望某些de回的开发人员通过禁用电话的所有按钮(但禁用菜单按钮)来控制电话吗? 那好吧。 继续进行下一个宠物项目。

参考: Android:如何实现自定义菜单类? 可以从我们的JCG合作伙伴Statictyped获得

相关文章 :

翻译自: https://www.javacodegeeks.com/2011/05/android-menu-class-investigation.html

android菜单

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值