Merging ActionBar menu with options menu.

Options menu items may now specify if they would like to appear in the
action bar. Menu items defined in xml may set the showAsAction
attribute to one of "never"(default), "ifRoom", or "always". Action
buttons are populated as follows:

* All showAsAction="always" items become action buttons, even if it
  would crowd the navigation area of the action bar.

* If there is space remaining, showAsAction="ifRoom" items are added
  until no more will fit comfortably.

Action button click events are now handled by the
onOptionsItemSelected method used by the standard options menu.

The construction of options menus now happens earlier in order to
provide data to the action bar. Activities with an action bar can now
expect to have onCreateOptionsMenu called when activity start-up is
complete.

Activity#invalidateOptionsMenu can be used to force a refresh of menu
items where the previous API would use ActionBar#updateActionMenu.

Change-Id: If52ddf1cf9f6926206bcdeadf42072ea2c24fab9
This commit is contained in:
Adam Powell
2010-06-10 18:58:59 -07:00
parent e6ac8b9aad
commit 96675b1df3
18 changed files with 543 additions and 410 deletions

View File

@@ -83,10 +83,6 @@ public class SplitActionBar extends ActionBar {
mActionView.setBackgroundDrawable(d);
}
public void setDividerDrawable(Drawable d) {
mActionView.setDividerDrawable(d);
}
public View getCustomNavigationView() {
return mActionView.getCustomNavigationView();
}
@@ -106,9 +102,4 @@ public class SplitActionBar extends ActionBar {
public int getDisplayOptions() {
return mActionView.getDisplayOptions();
}
public void updateActionMenu() {
mActionView.updateActionMenu();
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.view.menu;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.SoundEffectConstants;
import android.view.View;
import android.widget.ImageButton;
/**
* @hide
*/
public class ActionMenuItemView extends ImageButton implements MenuView.ItemView {
private static final String TAG = "ActionMenuItemView";
private MenuItemImpl mItemData;
private CharSequence mTitle;
private MenuBuilder.ItemInvoker mItemInvoker;
public ActionMenuItemView(Context context) {
this(context, null);
}
public ActionMenuItemView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.actionButtonStyle);
}
public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MenuItemImpl getItemData() {
return mItemData;
}
public void initialize(MenuItemImpl itemData, int menuType) {
mItemData = itemData;
setClickable(true);
setFocusable(true);
setTitle(itemData.getTitle());
setIcon(itemData.getIcon());
setId(itemData.getItemId());
setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
setEnabled(itemData.isEnabled());
}
@Override
public boolean performClick() {
// Let the view's listener have top priority
if (super.performClick()) {
return true;
}
if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
playSoundEffect(SoundEffectConstants.CLICK);
return true;
} else {
return false;
}
}
public void setItemInvoker(MenuBuilder.ItemInvoker invoker) {
mItemInvoker = invoker;
}
public boolean prefersCondensedTitle() {
return false;
}
public void setCheckable(boolean checkable) {
// TODO Support checkable action items
}
public void setChecked(boolean checked) {
// TODO Support checkable action items
}
public void setIcon(Drawable icon) {
setImageDrawable(icon);
}
public void setShortcut(boolean showShortcut, char shortcutKey) {
// Action buttons don't show text for shortcut keys.
}
public void setTitle(CharSequence title) {
mTitle = title;
}
public boolean showsIcon() {
return true;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.view.menu;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import java.util.ArrayList;
/**
* @hide
*/
public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
private static final String TAG = "ActionMenuView";
// TODO: Make this a ViewConfiguration constant.
private static final int MAX_ACTION_ITEMS = 3;
private MenuBuilder mMenu;
public ActionMenuView(Context context) {
this(context, null);
}
public ActionMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
public int getWindowAnimations() {
return 0;
}
public void initialize(MenuBuilder menu, int menuType) {
menu.setMaxActionItems(MAX_ACTION_ITEMS);
mMenu = menu;
updateChildren(true);
}
public void updateChildren(boolean cleared) {
removeAllViews();
final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems();
final int itemCount = itemsToShow.size();
for (int i = 0; i < itemCount; i++) {
final MenuItemImpl itemData = itemsToShow.get(i);
addItemView((ActionMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ACTION_BUTTON,
this));
}
}
private void addItemView(ActionMenuItemView view) {
view.setItemInvoker(this);
addView(view);
}
}

View File

@@ -337,7 +337,7 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
// This method does a clear refresh of children
removeAllViews();
final ArrayList<MenuItemImpl> itemsToShow = mMenu.getVisibleItems();
final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems();
final int numItems = itemsToShow.size();
final int numItemsThatCanFit = mMaxItems;
// Minimum of the num that can fit and the num that we have

View File

@@ -27,16 +27,17 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.util.SparseArray;
import android.view.ContextThemeWrapper;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
@@ -54,7 +55,7 @@ public class MenuBuilder implements Menu {
private static final String LOGTAG = "MenuBuilder";
/** The number of different menu types */
public static final int NUM_TYPES = 3;
public static final int NUM_TYPES = 4;
/** The menu type that represents the icon menu view */
public static final int TYPE_ICON = 0;
/** The menu type that represents the expanded menu view */
@@ -65,6 +66,11 @@ public class MenuBuilder implements Menu {
* have an ItemView.
*/
public static final int TYPE_DIALOG = 2;
/**
* The menu type that represents a button in the application's action bar.
*/
public static final int TYPE_ACTION_BUTTON = 3;
private static final String VIEWS_TAG = "android:views";
@@ -73,6 +79,7 @@ public class MenuBuilder implements Menu {
com.android.internal.R.style.Theme_IconMenu,
com.android.internal.R.style.Theme_ExpandedMenu,
0,
0,
};
// Order must be the same order as the TYPE_*
@@ -80,6 +87,7 @@ public class MenuBuilder implements Menu {
com.android.internal.R.layout.icon_menu_layout,
com.android.internal.R.layout.expanded_menu_layout,
0,
com.android.internal.R.layout.action_menu_layout,
};
// Order must be the same order as the TYPE_*
@@ -87,6 +95,7 @@ public class MenuBuilder implements Menu {
com.android.internal.R.layout.icon_menu_item_layout,
com.android.internal.R.layout.list_menu_item_layout,
com.android.internal.R.layout.list_menu_item_layout,
com.android.internal.R.layout.action_menu_item_layout,
};
private static final int[] sCategoryToOrder = new int[] {
@@ -130,6 +139,24 @@ public class MenuBuilder implements Menu {
* fetched from {@link #getVisibleItems()}
*/
private boolean mIsVisibleItemsStale;
/**
* Contains only the items that should appear in the Action Bar, if present.
*/
private ArrayList<MenuItemImpl> mActionItems;
/**
* Contains items that should NOT appear in the Action Bar, if present.
*/
private ArrayList<MenuItemImpl> mNonActionItems;
/**
* The number of visible action buttons permitted in this menu
*/
private int mMaxActionItems;
/**
* Whether or not the items (or any one item's action state) has changed since it was
* last fetched.
*/
private boolean mIsActionItemsStale;
/**
* Current use case is Context Menus: As Views populate the context menu, each one has
@@ -281,6 +308,10 @@ public class MenuBuilder implements Menu {
mVisibleItems = new ArrayList<MenuItemImpl>();
mIsVisibleItemsStale = true;
mActionItems = new ArrayList<MenuItemImpl>();
mNonActionItems = new ArrayList<MenuItemImpl>();
mIsActionItemsStale = true;
mShortcutsVisible =
(mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS);
}
@@ -900,6 +931,7 @@ public class MenuBuilder implements Menu {
private void onItemsChanged(boolean cleared) {
if (!mPreventDispatchingItemsChanged) {
if (mIsVisibleItemsStale == false) mIsVisibleItemsStale = true;
if (mIsActionItemsStale == false) mIsActionItemsStale = true;
MenuType[] menuTypes = mMenuTypes;
for (int i = 0; i < NUM_TYPES; i++) {
@@ -920,6 +952,15 @@ public class MenuBuilder implements Menu {
onItemsChanged(false);
}
/**
* Called by {@link MenuItemImpl} when its action request status is changed.
* @param item The item that has gone through a change in action request status.
*/
void onItemActionRequestChanged(MenuItemImpl item) {
// Notify of items being changed
onItemsChanged(false);
}
ArrayList<MenuItemImpl> getVisibleItems() {
if (!mIsVisibleItemsStale) return mVisibleItems;
@@ -934,9 +975,64 @@ public class MenuBuilder implements Menu {
}
mIsVisibleItemsStale = false;
mIsActionItemsStale = true;
return mVisibleItems;
}
private void flagActionItems() {
if (!mIsActionItemsStale) {
return;
}
final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
final int itemsSize = visibleItems.size();
int maxActions = mMaxActionItems;
for (int i = 0; i < itemsSize; i++) {
MenuItemImpl item = visibleItems.get(i);
if (item.requiresActionButton()) {
maxActions--;
}
}
// Flag as many more requested items as will fit.
for (int i = 0; i < itemsSize; i++) {
MenuItemImpl item = visibleItems.get(i);
if (item.requestsActionButton()) {
item.setIsActionButton(maxActions > 0);
maxActions--;
}
}
mActionItems.clear();
mNonActionItems.clear();
for (int i = 0; i < itemsSize; i++) {
MenuItemImpl item = visibleItems.get(i);
if (item.isActionButton()) {
mActionItems.add(item);
} else {
mNonActionItems.add(item);
}
}
mIsActionItemsStale = false;
}
ArrayList<MenuItemImpl> getActionItems() {
flagActionItems();
return mActionItems;
}
ArrayList<MenuItemImpl> getNonActionItems() {
flagActionItems();
return mNonActionItems;
}
void setMaxActionItems(int maxActionItems) {
mMaxActionItems = maxActionItems;
mIsActionItemsStale = true;
}
public void clearHeader() {
mHeaderIcon = null;

View File

@@ -74,6 +74,15 @@ public final class MenuItemImpl implements MenuItem {
private static final int EXCLUSIVE = 0x00000004;
private static final int HIDDEN = 0x00000008;
private static final int ENABLED = 0x00000010;
private static final int IS_ACTION = 0x00000020;
/*
* These should be kept in sync with attrs.xml enum constants for showAsAction
*/
private static final int SHOW_AS_ACTION_NEVER = 0;
private static final int SHOW_AS_ACTION_IF_ROOM = 1;
private static final int SHOW_AS_ACTION_ALWAYS = 2;
private int mShowAsAction = SHOW_AS_ACTION_NEVER;
/** Used for the icon resource ID if this item does not have an icon */
static final int NO_ICON = 0;
@@ -630,4 +639,35 @@ public final class MenuItemImpl implements MenuItem {
public boolean shouldShowIcon(int menuType) {
return menuType == MenuBuilder.TYPE_ICON || mMenu.getOptionalIconsVisible();
}
public boolean isActionButton() {
return (mFlags & IS_ACTION) == IS_ACTION || requiresActionButton();
}
public boolean requestsActionButton() {
return mShowAsAction == SHOW_AS_ACTION_IF_ROOM;
}
public boolean requiresActionButton() {
return mShowAsAction == SHOW_AS_ACTION_ALWAYS;
}
public void setIsActionButton(boolean isActionButton) {
if (isActionButton) {
mFlags |= IS_ACTION;
} else {
mFlags &= ~IS_ACTION;
}
}
/**
* Used by MenuInflater to set how this item should display in the presence of
* an Action Bar.
*
* @param actionEnum Enum flag as defined in attrs.xml/SHOW_AS_ constants.
*/
public void setShowAsAction(int actionEnum) {
mShowAsAction = actionEnum;
mMenu.onItemActionRequestChanged(this);
}
}