Merge "Add Cascading submenus implementation."
This commit is contained in:
committed by
Android (Google) Code Review
commit
96b22a98ce
@@ -85,6 +85,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter
|
||||
private OpenOverflowRunnable mPostedOpenRunnable;
|
||||
private ActionMenuPopupCallback mPopupCallback;
|
||||
|
||||
private final boolean mShowCascadingMenus;
|
||||
|
||||
final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
|
||||
int mOpenSubMenuId;
|
||||
|
||||
@@ -126,6 +128,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter
|
||||
public ActionMenuPresenter(Context context) {
|
||||
super(context, com.android.internal.R.layout.action_menu_layout,
|
||||
com.android.internal.R.layout.action_menu_item_layout);
|
||||
|
||||
mShowCascadingMenus = context.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_enableCascadingSubmenus);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -500,14 +505,29 @@ public class ActionMenuPresenter extends BaseMenuPresenter
|
||||
}
|
||||
View anchor = findViewForItem(topSubMenu.getItem());
|
||||
if (anchor == null) {
|
||||
if (mOverflowButton == null) return false;
|
||||
anchor = mOverflowButton;
|
||||
// This means the submenu was opened from an overflow menu item, indicating the
|
||||
// MenuPopupHelper will handle opening the submenu via its MenuPopup. Return false to
|
||||
// ensure that the MenuPopup acts as presenter for the submenu, and acts on its
|
||||
// responsibility to display the new submenu.
|
||||
return false;
|
||||
}
|
||||
|
||||
mOpenSubMenuId = subMenu.getItem().getItemId();
|
||||
mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
|
||||
mActionButtonPopup.setAnchorView(anchor);
|
||||
|
||||
boolean preserveIconSpacing = false;
|
||||
final int count = subMenu.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
MenuItem childItem = subMenu.getItem(i);
|
||||
if (childItem.isVisible() && childItem.getIcon() != null) {
|
||||
preserveIconSpacing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu, anchor);
|
||||
mActionButtonPopup.setForceShowIcon(preserveIconSpacing);
|
||||
mActionButtonPopup.show();
|
||||
|
||||
super.onSubMenuSelected(subMenu);
|
||||
return true;
|
||||
}
|
||||
@@ -926,12 +946,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter
|
||||
}
|
||||
|
||||
private class ActionButtonSubmenu extends MenuPopupHelper {
|
||||
private SubMenuBuilder mSubMenu;
|
||||
|
||||
public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
|
||||
super(context, subMenu, null, false,
|
||||
public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu, View anchorView) {
|
||||
super(context, subMenu, anchorView, false,
|
||||
com.android.internal.R.attr.actionOverflowMenuStyle);
|
||||
mSubMenu = subMenu;
|
||||
|
||||
MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
|
||||
if (!item.isActionButton()) {
|
||||
@@ -940,17 +957,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter
|
||||
}
|
||||
|
||||
setCallback(mPopupPresenterCallback);
|
||||
|
||||
boolean preserveIconSpacing = false;
|
||||
final int count = subMenu.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
MenuItem childItem = subMenu.getItem(i);
|
||||
if (childItem.isVisible() && childItem.getIcon() != null) {
|
||||
preserveIconSpacing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setForceShowIcon(preserveIconSpacing);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,10 +25,8 @@ import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.IntProperty;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ListView;
|
||||
@@ -128,6 +126,61 @@ public class DropDownListView extends ListView {
|
||||
setCacheColorHint(0); // Transparent, since the background drawable could be anything.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldShowSelector() {
|
||||
View selectedView = getSelectedView();
|
||||
return selectedView != null && selectedView.isEnabled() || super.shouldShowSelector();
|
||||
}
|
||||
|
||||
protected void clearSelection() {
|
||||
setSelectedPositionInt(-1);
|
||||
setNextSelectedPositionInt(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHoverEvent(MotionEvent ev) {
|
||||
final int action = ev.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_HOVER_ENTER
|
||||
|| action == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
|
||||
if (position != INVALID_POSITION && position != mSelectedPosition) {
|
||||
final View hoveredItem = getChildAt(position - getFirstVisiblePosition());
|
||||
if (hoveredItem.isEnabled()) {
|
||||
// Force a focus so that the proper selector state gets used when we update.
|
||||
requestFocus();
|
||||
|
||||
positionSelector(position, hoveredItem);
|
||||
setSelectedPositionInt(position);
|
||||
setNextSelectedPositionInt(position);
|
||||
}
|
||||
updateSelectorState();
|
||||
}
|
||||
} else {
|
||||
// Do not cancel the selected position if the selection is visible by other reasons.
|
||||
if (!super.shouldShowSelector()) {
|
||||
setSelectedPositionInt(INVALID_POSITION);
|
||||
}
|
||||
}
|
||||
return super.onHoverEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
final int x = (int) event.getX();
|
||||
final int y = (int) event.getY();
|
||||
final int position = pointToPosition(x, y);
|
||||
if (position == INVALID_POSITION) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
if (position != mSelectedPosition) {
|
||||
setSelectedPositionInt(position);
|
||||
setNextSelectedPositionInt(position);
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles forwarded events.
|
||||
*
|
||||
|
||||
@@ -69,7 +69,6 @@ public class ListPopupWindow implements ShowableListMenu {
|
||||
private static final int EXPAND_LIST_TIMEOUT = 250;
|
||||
|
||||
private Context mContext;
|
||||
private PopupWindow mPopup;
|
||||
private ListAdapter mAdapter;
|
||||
private DropDownListView mDropDownList;
|
||||
|
||||
@@ -112,6 +111,8 @@ public class ListPopupWindow implements ShowableListMenu {
|
||||
|
||||
private int mLayoutDirection;
|
||||
|
||||
PopupWindow mPopup;
|
||||
|
||||
/**
|
||||
* The provided prompt view should appear above list content.
|
||||
*
|
||||
|
||||
@@ -17,10 +17,17 @@
|
||||
package android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.transition.Transition;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import com.android.internal.view.menu.ListMenuItemView;
|
||||
import com.android.internal.view.menu.MenuAdapter;
|
||||
|
||||
/**
|
||||
* A MenuPopupWindow represents the popup window for menu.
|
||||
@@ -40,52 +47,58 @@ public class MenuPopupWindow extends ListPopupWindow {
|
||||
return new MenuDropDownListView(context, hijackFocus);
|
||||
}
|
||||
|
||||
static class MenuDropDownListView extends DropDownListView {
|
||||
private boolean mHoveredOnDisabledItem = false;
|
||||
private AccessibilityManager mAccessibilityManager;
|
||||
|
||||
MenuDropDownListView(Context context, boolean hijackFocus) {
|
||||
super(context, hijackFocus);
|
||||
mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(
|
||||
Context.ACCESSIBILITY_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldShowSelector() {
|
||||
return (isHovered() && !mHoveredOnDisabledItem) || super.shouldShowSelector();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHoverEvent(MotionEvent ev) {
|
||||
mHoveredOnDisabledItem = false;
|
||||
|
||||
// Accessibility system should already handle hover events and selections, menu does
|
||||
// not have to handle it by itself.
|
||||
if (mAccessibilityManager.isTouchExplorationEnabled()) {
|
||||
return super.onHoverEvent(ev);
|
||||
}
|
||||
|
||||
final int action = ev.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_HOVER_ENTER
|
||||
|| action == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
|
||||
if (position != INVALID_POSITION && position != mSelectedPosition) {
|
||||
final View hoveredItem = getChildAt(position - getFirstVisiblePosition());
|
||||
if (hoveredItem.isEnabled()) {
|
||||
positionSelector(position, hoveredItem);
|
||||
setSelectedPositionInt(position);
|
||||
} else {
|
||||
mHoveredOnDisabledItem = true;
|
||||
}
|
||||
updateSelectorState();
|
||||
}
|
||||
} else {
|
||||
// Do not cancel the selected position if the selection is visible by other reasons.
|
||||
if (!super.shouldShowSelector()) {
|
||||
setSelectedPositionInt(INVALID_POSITION);
|
||||
}
|
||||
}
|
||||
return super.onHoverEvent(ev);
|
||||
}
|
||||
public void setEnterTransition(Transition enterTransition) {
|
||||
mPopup.setEnterTransition(enterTransition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this window is touch modal or if outside touches will be sent to
|
||||
* other windows behind it.
|
||||
*/
|
||||
public void setTouchModal(boolean touchModal) {
|
||||
mPopup.setTouchModal(touchModal);
|
||||
}
|
||||
|
||||
private static class MenuDropDownListView extends DropDownListView {
|
||||
final int mAdvanceKey;
|
||||
final int mRetreatKey;
|
||||
|
||||
public MenuDropDownListView(Context context, boolean hijackFocus) {
|
||||
super(context, hijackFocus);
|
||||
|
||||
final Resources res = context.getResources();
|
||||
final Configuration config = res.getConfiguration();
|
||||
if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
|
||||
mAdvanceKey = KeyEvent.KEYCODE_DPAD_LEFT;
|
||||
mRetreatKey = KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||
} else {
|
||||
mAdvanceKey = KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||
mRetreatKey = KeyEvent.KEYCODE_DPAD_LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView();
|
||||
if (selectedItem != null && keyCode == mAdvanceKey) {
|
||||
if (selectedItem.isEnabled() &&
|
||||
((ListMenuItemView) selectedItem).getItemData().hasSubMenu()) {
|
||||
performItemClick(
|
||||
selectedItem,
|
||||
getSelectedItemPosition(),
|
||||
getSelectedItemId());
|
||||
}
|
||||
return true;
|
||||
} else if (selectedItem != null && keyCode == mRetreatKey) {
|
||||
setSelectedPositionInt(-1);
|
||||
setNextSelectedPositionInt(-1);
|
||||
|
||||
((MenuAdapter) getAdapter()).getAdapterMenu().close();
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,6 +43,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
|
||||
private final MenuBuilder mMenu;
|
||||
private final View mAnchor;
|
||||
private final MenuPopupHelper mPopup;
|
||||
private final boolean mShowCascadingMenus;
|
||||
|
||||
private OnMenuItemClickListener mMenuItemClickListener;
|
||||
private OnDismissListener mDismissListener;
|
||||
@@ -107,6 +108,8 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
|
||||
public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
|
||||
int popupStyleRes) {
|
||||
mContext = context;
|
||||
mShowCascadingMenus = context.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_enableCascadingSubmenus);
|
||||
mMenu = new MenuBuilder(context);
|
||||
mMenu.setCallback(this);
|
||||
mAnchor = anchor;
|
||||
@@ -273,8 +276,12 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Current menu will be dismissed by the normal helper, submenu will be shown in its place.
|
||||
new MenuPopupHelper(mContext, subMenu, mAnchor).show();
|
||||
if (!mShowCascadingMenus) {
|
||||
// Current menu will be dismissed by the normal helper, submenu will be shown in its
|
||||
// place. (If cascading menus are enabled, the cascading implementation will show the
|
||||
// submenu itself).
|
||||
new MenuPopupHelper(mContext, subMenu, mAnchor).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
394
core/java/com/android/internal/view/menu/CascadingMenuPopup.java
Normal file
394
core/java/com/android/internal/view/menu/CascadingMenuPopup.java
Normal file
@@ -0,0 +1,394 @@
|
||||
package com.android.internal.view.menu;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Parcelable;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnKeyListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.DropDownListView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.MenuPopupWindow;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.PopupWindow.OnDismissListener;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A popup for a menu which will allow multiple submenus to appear in a cascading fashion, side by
|
||||
* side.
|
||||
* @hide
|
||||
*/
|
||||
final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemClickListener,
|
||||
MenuPresenter, OnKeyListener, PopupWindow.OnDismissListener {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT})
|
||||
public @interface HorizPosition {}
|
||||
|
||||
private static final int HORIZ_POSITION_LEFT = 0;
|
||||
private static final int HORIZ_POSITION_RIGHT = 1;
|
||||
|
||||
private final Context mContext;
|
||||
private final int mMenuMaxWidth;
|
||||
private final int mPopupStyleAttr;
|
||||
private final int mPopupStyleRes;
|
||||
private final boolean mOverflowOnly;
|
||||
private final int mLayoutDirection;
|
||||
|
||||
private int mDropDownGravity = Gravity.NO_GRAVITY;
|
||||
private View mAnchor;
|
||||
private List<DropDownListView> mListViews;
|
||||
private List<MenuPopupWindow> mPopupWindows;
|
||||
private List<int[]> mOffsets;
|
||||
private int mPreferredPosition;
|
||||
private boolean mForceShowIcon;
|
||||
private Callback mPresenterCallback;
|
||||
private PopupWindow.OnDismissListener mOnDismissListener;
|
||||
|
||||
/**
|
||||
* Initializes a new cascading-capable menu popup.
|
||||
*
|
||||
* @param parent A parent view to get the {@link android.view.View#getWindowToken()} token from.
|
||||
*/
|
||||
public CascadingMenuPopup(Context context, View anchor, int popupStyleAttr,
|
||||
int popupStyleRes, boolean overflowOnly) {
|
||||
mContext = Preconditions.checkNotNull(context);
|
||||
mAnchor = Preconditions.checkNotNull(anchor);
|
||||
mPopupStyleAttr = popupStyleAttr;
|
||||
mPopupStyleRes = popupStyleRes;
|
||||
mOverflowOnly = overflowOnly;
|
||||
|
||||
mForceShowIcon = false;
|
||||
|
||||
final Resources res = context.getResources();
|
||||
final Configuration config = res.getConfiguration();
|
||||
mLayoutDirection = config.getLayoutDirection();
|
||||
mPreferredPosition = mLayoutDirection == View.LAYOUT_DIRECTION_RTL ? HORIZ_POSITION_LEFT :
|
||||
HORIZ_POSITION_RIGHT;
|
||||
mMenuMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
|
||||
res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
|
||||
|
||||
mPopupWindows = new ArrayList<MenuPopupWindow>();
|
||||
mListViews = new ArrayList<DropDownListView>();
|
||||
mOffsets = new ArrayList<int[]>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForceShowIcon(boolean forceShow) {
|
||||
mForceShowIcon = forceShow;
|
||||
}
|
||||
|
||||
private MenuPopupWindow createPopupWindow() {
|
||||
MenuPopupWindow popupWindow = new MenuPopupWindow(
|
||||
mContext, null, mPopupStyleAttr, mPopupStyleRes);
|
||||
popupWindow.setOnItemClickListener(this);
|
||||
popupWindow.setOnDismissListener(this);
|
||||
popupWindow.setAnchorView(mAnchor);
|
||||
popupWindow.setDropDownGravity(mDropDownGravity);
|
||||
popupWindow.setModal(true);
|
||||
popupWindow.setTouchModal(false);
|
||||
return popupWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
if (isShowing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show any menus that have been added via #addMenu(MenuBuilder) but which have not yet been
|
||||
// shown.
|
||||
// In a typical use case, #addMenu(MenuBuilder) would be called once, followed by a call to
|
||||
// this #show() method -- which would actually show the popup on the screen.
|
||||
for (int i = 0; i < mPopupWindows.size(); i++) {
|
||||
MenuPopupWindow popupWindow = mPopupWindows.get(i);
|
||||
popupWindow.show();
|
||||
mListViews.add((DropDownListView) popupWindow.getListView());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
// Need to make another list to avoid a concurrent modification exception, as #onDismiss
|
||||
// may clear mPopupWindows while we are iterating.
|
||||
List<MenuPopupWindow> popupWindows = new ArrayList<MenuPopupWindow>(mPopupWindows);
|
||||
for (MenuPopupWindow popupWindow : popupWindows) {
|
||||
if (popupWindow != null && popupWindow.isShowing()) {
|
||||
popupWindow.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
MenuAdapter adapter = (MenuAdapter) parent.getAdapter();
|
||||
adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the next submenu (of the given width) should display on the right or on
|
||||
* the left of the most recent menu.
|
||||
*
|
||||
* @param nextMenuWidth Width of the next submenu to display.
|
||||
* @return The position to display it.
|
||||
*/
|
||||
@HorizPosition
|
||||
private int getNextMenuPosition(int nextMenuWidth) {
|
||||
ListView lastListView = mListViews.get(mListViews.size() - 1);
|
||||
|
||||
final int[] screenLocation = new int[2];
|
||||
lastListView.getLocationOnScreen(screenLocation);
|
||||
|
||||
final Rect displayFrame = new Rect();
|
||||
mAnchor.getWindowVisibleDisplayFrame(displayFrame);
|
||||
|
||||
if (mPreferredPosition == HORIZ_POSITION_RIGHT) {
|
||||
final int right = screenLocation[0] + lastListView.getWidth() + nextMenuWidth;
|
||||
if (right > displayFrame.right) {
|
||||
return HORIZ_POSITION_LEFT;
|
||||
}
|
||||
return HORIZ_POSITION_RIGHT;
|
||||
} else { // LEFT
|
||||
final int left = screenLocation[0] - nextMenuWidth;
|
||||
if (left < 0) {
|
||||
return HORIZ_POSITION_RIGHT;
|
||||
}
|
||||
return HORIZ_POSITION_LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMenu(MenuBuilder menu) {
|
||||
boolean addSubMenu = mListViews.size() > 0;
|
||||
|
||||
menu.addMenuPresenter(this, mContext);
|
||||
|
||||
MenuPopupWindow popupWindow = createPopupWindow();
|
||||
|
||||
MenuAdapter adapter = new MenuAdapter(menu, LayoutInflater.from(mContext), mOverflowOnly);
|
||||
adapter.setForceShowIcon(mForceShowIcon);
|
||||
|
||||
popupWindow.setAdapter(adapter);
|
||||
|
||||
int menuWidth = measureIndividualMenuWidth(adapter, null, mContext, mMenuMaxWidth);
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
if (addSubMenu) {
|
||||
popupWindow.setEnterTransition(null);
|
||||
|
||||
ListView lastListView = mListViews.get(mListViews.size() - 1);
|
||||
@HorizPosition int nextMenuPosition = getNextMenuPosition(menuWidth);
|
||||
boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT;
|
||||
mPreferredPosition = nextMenuPosition;
|
||||
|
||||
int[] lastLocation = new int[2];
|
||||
lastListView.getLocationOnScreen(lastLocation);
|
||||
|
||||
int[] lastOffset = mOffsets.get(mOffsets.size() - 1);
|
||||
|
||||
// Note: By now, mDropDownGravity is the absolute gravity, so this should work in both
|
||||
// LTR and RTL.
|
||||
if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) {
|
||||
if (showOnRight) {
|
||||
x = lastOffset[0] + menuWidth;
|
||||
} else {
|
||||
x = lastOffset[0] - lastListView.getWidth();
|
||||
}
|
||||
} else {
|
||||
if (showOnRight) {
|
||||
x = lastOffset[0] + lastListView.getWidth();
|
||||
} else {
|
||||
x = lastOffset[0] - menuWidth;
|
||||
}
|
||||
}
|
||||
|
||||
y = lastOffset[1] + lastListView.getSelectedView().getTop() -
|
||||
lastListView.getChildAt(0).getTop();
|
||||
}
|
||||
|
||||
popupWindow.setWidth(menuWidth);
|
||||
popupWindow.setHorizontalOffset(x);
|
||||
popupWindow.setVerticalOffset(y);
|
||||
mPopupWindows.add(popupWindow);
|
||||
|
||||
// NOTE: This case handles showing submenus once the CascadingMenuPopup has already
|
||||
// been shown via a call to its #show() method. If it hasn't yet been show()n, then
|
||||
// we deliberately do not yet show the popupWindow, as #show() will do that later.
|
||||
if (isShowing()) {
|
||||
popupWindow.show();
|
||||
mListViews.add((DropDownListView) popupWindow.getListView());
|
||||
}
|
||||
|
||||
int[] offsets = {x, y};
|
||||
mOffsets.add(offsets);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the popup is currently showing, {@code false} otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isShowing() {
|
||||
return mPopupWindows.size() > 0 && mPopupWindows.get(0).isShowing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one or more of the popup windows was dismissed.
|
||||
*/
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
int dismissedIndex = -1;
|
||||
for (int i = 0; i < mPopupWindows.size(); i++) {
|
||||
if (!mPopupWindows.get(i).isShowing()) {
|
||||
dismissedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dismissedIndex != -1) {
|
||||
for (int i = dismissedIndex; i < mListViews.size(); i++) {
|
||||
ListView view = mListViews.get(i);
|
||||
MenuAdapter adapter = (MenuAdapter) view.getAdapter();
|
||||
adapter.mAdapterMenu.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMenuView(boolean cleared) {
|
||||
for (ListView view : mListViews) {
|
||||
((MenuAdapter) view.getAdapter()).notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(Callback cb) {
|
||||
mPresenterCallback = cb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
|
||||
// Don't allow double-opening of the same submenu.
|
||||
for (ListView view : mListViews) {
|
||||
if (((MenuAdapter) view.getAdapter()).mAdapterMenu.equals(subMenu)) {
|
||||
// Just re-focus that one.
|
||||
view.requestFocus();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (subMenu.hasVisibleItems()) {
|
||||
this.addMenu(subMenu);
|
||||
if (mPresenterCallback != null) {
|
||||
mPresenterCallback.onOpenSubMenu(subMenu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
|
||||
int menuIndex = -1;
|
||||
boolean wasSelected = false;
|
||||
|
||||
for (int i = 0; i < mListViews.size(); i++) {
|
||||
ListView view = mListViews.get(i);
|
||||
MenuAdapter adapter = (MenuAdapter) view.getAdapter();
|
||||
|
||||
if (menuIndex == -1 && menu == adapter.mAdapterMenu) {
|
||||
menuIndex = i;
|
||||
wasSelected = view.getSelectedItem() != null;
|
||||
}
|
||||
|
||||
// Once the menu has been found, remove it and all submenus beneath it from the
|
||||
// container view. Also remove the presenter.
|
||||
if (menuIndex != -1) {
|
||||
adapter.mAdapterMenu.removeMenuPresenter(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Then, actually remove the views for these [sub]menu(s) from our list of views.
|
||||
if (menuIndex != -1) {
|
||||
for (int i = menuIndex; i < mPopupWindows.size(); i++) {
|
||||
mPopupWindows.get(i).dismiss();
|
||||
}
|
||||
mPopupWindows.subList(menuIndex, mPopupWindows.size()).clear();
|
||||
mListViews.subList(menuIndex, mListViews.size()).clear();
|
||||
mOffsets.subList(menuIndex, mOffsets.size()).clear();
|
||||
|
||||
// If there's still a menu open, refocus the new leaf [sub]menu.
|
||||
if (mListViews.size() > 0) {
|
||||
mListViews.get(mListViews.size() - 1).requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
if (mListViews.size() == 0 || wasSelected) {
|
||||
dismiss();
|
||||
if (mPresenterCallback != null) {
|
||||
mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
|
||||
}
|
||||
}
|
||||
|
||||
if (mPopupWindows.size() == 0) {
|
||||
// If every [sub]menu was dismissed, that means the whole thing was dismissed, so notify
|
||||
// the owner.
|
||||
mOnDismissListener.onDismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean flagActionItems() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGravity(int dropDownGravity) {
|
||||
mDropDownGravity = Gravity.getAbsoluteGravity(dropDownGravity, mLayoutDirection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnchorView(View anchor) {
|
||||
mAnchor = anchor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnDismissListener(OnDismissListener listener) {
|
||||
mOnDismissListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListView getListView() {
|
||||
return mListViews.size() > 0 ? mListViews.get(mListViews.size() - 1) : null;
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ public class MenuBuilder implements Menu {
|
||||
|
||||
private final Context mContext;
|
||||
private final Resources mResources;
|
||||
private final boolean mShowCascadingMenus;
|
||||
|
||||
/**
|
||||
* Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
|
||||
@@ -186,6 +187,8 @@ public class MenuBuilder implements Menu {
|
||||
public MenuBuilder(Context context) {
|
||||
mContext = context;
|
||||
mResources = context.getResources();
|
||||
mShowCascadingMenus = context.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_enableCascadingSubmenus);
|
||||
|
||||
mItems = new ArrayList<MenuItemImpl>();
|
||||
|
||||
@@ -909,7 +912,9 @@ public class MenuBuilder implements Menu {
|
||||
invoked |= itemImpl.expandActionView();
|
||||
if (invoked) close(true);
|
||||
} else if (itemImpl.hasSubMenu() || providerHasSubMenu) {
|
||||
close(false);
|
||||
if (!mShowCascadingMenus) {
|
||||
close(false);
|
||||
}
|
||||
|
||||
if (!itemImpl.hasSubMenu()) {
|
||||
itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl));
|
||||
|
||||
@@ -35,9 +35,11 @@ public abstract class MenuPopup implements ShowableListMenu, MenuPresenter {
|
||||
public abstract void setForceShowIcon(boolean forceShow);
|
||||
|
||||
/**
|
||||
* Adds the given menu to the popup. If this is the first menu shown it'll be displayed; if it's
|
||||
* a submenu it will be displayed adjacent to the most recent menu (if supported by the
|
||||
* implementation).
|
||||
* Adds the given menu to the popup, if it is capable of displaying submenus within itself.
|
||||
* If menu is the first menu shown, it won't be displayed until show() is called.
|
||||
* If the popup was already showing, adding a submenu via this method will cause that new
|
||||
* submenu to be shown immediately (that is, if this MenuPopup implementation is capable of
|
||||
* showing its own submenus).
|
||||
*
|
||||
* @param menu
|
||||
*/
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
package com.android.internal.view.menu;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcelable;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.PopupWindow;
|
||||
|
||||
@@ -30,7 +28,7 @@ import android.widget.PopupWindow;
|
||||
* @hide
|
||||
*/
|
||||
public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener,
|
||||
PopupWindow.OnDismissListener, View.OnAttachStateChangeListener, MenuPresenter {
|
||||
PopupWindow.OnDismissListener, View.OnAttachStateChangeListener {
|
||||
private final Context mContext;
|
||||
private final MenuBuilder mMenu;
|
||||
private final boolean mOverflowOnly;
|
||||
@@ -70,9 +68,8 @@ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener,
|
||||
private MenuPopup createMenuPopup() {
|
||||
if (mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_enableCascadingSubmenus)) {
|
||||
// TODO: Return a Cascading implementation of MenuPopup instead.
|
||||
return new StandardMenuPopup(
|
||||
mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly);
|
||||
return new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr, mPopupStyleRes,
|
||||
mOverflowOnly);
|
||||
}
|
||||
return new StandardMenuPopup(
|
||||
mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly);
|
||||
@@ -187,62 +184,7 @@ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener,
|
||||
v.removeOnAttachStateChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initForMenu(Context context, MenuBuilder menu) {
|
||||
// Don't need to do anything; we added as a presenter in the constructor.
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuView getMenuView(ViewGroup root) {
|
||||
throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMenuView(boolean cleared) {
|
||||
mPopup.updateMenuView(cleared);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(Callback cb) {
|
||||
public void setCallback(MenuPresenter.Callback cb) {
|
||||
mPopup.setCallback(cb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
|
||||
return mPopup.onSubMenuSelected(subMenu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
|
||||
mPopup.onCloseMenu(menu, allMenusAreClosing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean flagActionItems() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user