它们给您的用户(所有熟悉的事物)和您的设计美学带来的舒适感,它们平淡,无聊且陈旧。 但是他们一定是吗? 这就是问题,这是我想解决的问题。 让我们从研究Android菜单的位置,原因和方式开始。 具体来说,让我们找出菜单中的逻辑在代码中的何处,然后让我们找出如何对其进行更改。
标准主题
没有主题支持的Android的组成部分是什么? 当然,对于不同类型的菜单,有不同类型的主题。 这是从git信息库中的XML 存储库中获取的菜单的标准两个(实际上有三个):
第一个主题是您按下手机上的菜单按钮时看到的主题,第二个主题是您按下第一个上的“更多”选项按钮时看到的主题。
并且,如果您检查清单XML文件:
您会看到这些主题出现在清单的一部分中……嗯,出现在清单的一部分中……等等……等等,我在任何地方都看不到它们! 他们究竟是怎么摄取到应用程序? 我应该如何克服这个限制?
好了,该花点时间研究一下。 戴上迪克·特雷西的帽子。 在某个地方可以提取主题,一旦找到它们所在的位置,我们就可以找出需要做些什么以替代我们自己的主题。
介绍IconMenuView和ExpandedMenuView
可以肯定地说,您看到的菜单实际上是几类的产品。 即动作的侦听器和View类的子级。 由于Android开发人员指南未提及如何个性化菜单样式或风格化,因此我们不得不假定这是有原因的。 这个原因必须潜伏在“内部” Android代码中。
查找主题的位置的一个线索是在视图代码本身或在使视图放大的对象中实例化菜单视图的位置。 通常,在View构建期间,将从Context类中检索Theme(和Style)并将其放入TypedArray中。 幸运的是,这正是在类中发生的事情: IconMenuView和ExpandedMenuView 。
这是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();
mVerticalDivider =
a.getDrawable(com.android.internal.R.styleable.MenuView_verticalDivider);
mVerticalDividerRects = new ArrayList();
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存储库的内部菜单目录中,有许多与菜单相关的类。 我们最感兴趣的是实现Menu , ContextMenu或SubMenu 。 为什么? 如果您回顾一下菜单指南,他们会在Activity的onCreateOptionsMenu下推荐以下代码段:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.game_menu, menu);
return true;
}
这就告诉我们,Activity是硬编码的,它期望Menu的子类。 如果我们甚至想开始考虑构建自己的自定义菜单,我们将不得不使用此界面。
博客文章的这一部分出于某种原因被称为“构建菜单”。 实现Menu的类为MenuBuilder , ContextMenuBuilder和SubMenuBuilder 。 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);
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的所有MenuItems或SubMenus的对象。 它的工作是解析menu.xml文件,并将该文件的内容加载到呈现给最终用户的视图中。 因此,它实际上不会创建或分配您要处理的菜单类型。 那直接来自主动性本身。
那么菜单在哪里出生?
我们想知道的是如何将库存活动与库存菜单分开? 为此,我们需要知道菜单的实际引用在何处保存。 直到我们看到对ContextMenu或OptionsMenu的引用调用“ new”的位置之前,我们可以编写任意数量的自定义Menu,这无关紧要
查看Android开发人员上Activity类的文档,有许多与菜单相关的方法,例如closeContextMenu , onContextMenuClosed , onContextItemSelected , onMenuOpened , onCreateContextMenu等。 最后一个是一个痛处,因为我们不是在菜单上操作一种方法(允许完全自定义的行为),而是在严格地处理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获得 。
- “ Android完整应用程序教程”系列
- Android Full App,第6部分:用于数据演示的自定义列表视图
- Android Full App,第7部分:使用选项菜单和自定义对话框进行用户交互
- Android文字转语音应用
- 使用VirtualBox在PC上安装Android OS
翻译自: https://www.javacodegeeks.com/2011/05/android-menu-class-investigation.html