| /* |
| * 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 android.widget; |
| |
| import android.annotation.MenuRes; |
| import android.annotation.TestApi; |
| import android.compat.annotation.UnsupportedAppUsage; |
| import android.content.Context; |
| import android.view.Gravity; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.View.OnTouchListener; |
| |
| import com.android.internal.R; |
| import com.android.internal.view.menu.MenuBuilder; |
| import com.android.internal.view.menu.MenuPopupHelper; |
| import com.android.internal.view.menu.ShowableListMenu; |
| |
| /** |
| * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a |
| * {@link View}. The popup will appear below the anchor view if there is room, |
| * or above it if there is not. If the IME is visible the popup will not |
| * overlap it until it is touched. Touching outside of the popup will dismiss |
| * it. |
| */ |
| public class PopupMenu { |
| @UnsupportedAppUsage |
| private final Context mContext; |
| private final MenuBuilder mMenu; |
| private final View mAnchor; |
| @UnsupportedAppUsage |
| private final MenuPopupHelper mPopup; |
| |
| private OnMenuItemClickListener mMenuItemClickListener; |
| private OnDismissListener mOnDismissListener; |
| private OnTouchListener mDragListener; |
| |
| /** |
| * Constructor to create a new popup menu with an anchor view. |
| * |
| * @param context Context the popup menu is running in, through which it |
| * can access the current theme, resources, etc. |
| * @param anchor Anchor view for this popup. The popup will appear below |
| * the anchor if there is room, or above it if there is not. |
| */ |
| public PopupMenu(Context context, View anchor) { |
| this(context, anchor, Gravity.NO_GRAVITY); |
| } |
| |
| /** |
| * Constructor to create a new popup menu with an anchor view and alignment |
| * gravity. |
| * |
| * @param context Context the popup menu is running in, through which it |
| * can access the current theme, resources, etc. |
| * @param anchor Anchor view for this popup. The popup will appear below |
| * the anchor if there is room, or above it if there is not. |
| * @param gravity The {@link Gravity} value for aligning the popup with its |
| * anchor. |
| */ |
| public PopupMenu(Context context, View anchor, int gravity) { |
| this(context, anchor, gravity, R.attr.popupMenuStyle, 0); |
| } |
| |
| /** |
| * Constructor a create a new popup menu with a specific style. |
| * |
| * @param context Context the popup menu is running in, through which it |
| * can access the current theme, resources, etc. |
| * @param anchor Anchor view for this popup. The popup will appear below |
| * the anchor if there is room, or above it if there is not. |
| * @param gravity The {@link Gravity} value for aligning the popup with its |
| * anchor. |
| * @param popupStyleAttr An attribute in the current theme that contains a |
| * reference to a style resource that supplies default values for |
| * the popup window. Can be 0 to not look for defaults. |
| * @param popupStyleRes A resource identifier of a style resource that |
| * supplies default values for the popup window, used only if |
| * popupStyleAttr is 0 or can not be found in the theme. Can be 0 |
| * to not look for defaults. |
| */ |
| public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr, |
| int popupStyleRes) { |
| mContext = context; |
| mAnchor = anchor; |
| |
| mMenu = new MenuBuilder(context); |
| mMenu.setCallback(new MenuBuilder.Callback() { |
| @Override |
| public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { |
| if (mMenuItemClickListener != null) { |
| return mMenuItemClickListener.onMenuItemClick(item); |
| } |
| return false; |
| } |
| |
| @Override |
| public void onMenuModeChange(MenuBuilder menu) { |
| } |
| }); |
| |
| mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes); |
| mPopup.setGravity(gravity); |
| mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { |
| @Override |
| public void onDismiss() { |
| if (mOnDismissListener != null) { |
| mOnDismissListener.onDismiss(PopupMenu.this); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Sets the gravity used to align the popup window to its anchor view. |
| * <p> |
| * If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown. |
| * |
| * @param gravity the gravity used to align the popup window |
| * @see #getGravity() |
| */ |
| public void setGravity(int gravity) { |
| mPopup.setGravity(gravity); |
| } |
| |
| /** |
| * @return the gravity used to align the popup window to its anchor view |
| * @see #setGravity(int) |
| */ |
| public int getGravity() { |
| return mPopup.getGravity(); |
| } |
| |
| /** |
| * Returns an {@link OnTouchListener} that can be added to the anchor view |
| * to implement drag-to-open behavior. |
| * <p> |
| * When the listener is set on a view, touching that view and dragging |
| * outside of its bounds will open the popup window. Lifting will select |
| * the currently touched list item. |
| * <p> |
| * Example usage: |
| * <pre> |
| * PopupMenu myPopup = new PopupMenu(context, myAnchor); |
| * myAnchor.setOnTouchListener(myPopup.getDragToOpenListener()); |
| * </pre> |
| * |
| * @return a touch listener that controls drag-to-open behavior |
| */ |
| public OnTouchListener getDragToOpenListener() { |
| if (mDragListener == null) { |
| mDragListener = new ForwardingListener(mAnchor) { |
| @Override |
| protected boolean onForwardingStarted() { |
| show(); |
| return true; |
| } |
| |
| @Override |
| protected boolean onForwardingStopped() { |
| dismiss(); |
| return true; |
| } |
| |
| @Override |
| public ShowableListMenu getPopup() { |
| // This will be null until show() is called. |
| return mPopup.getPopup(); |
| } |
| }; |
| } |
| |
| return mDragListener; |
| } |
| |
| /** |
| * Returns the {@link Menu} associated with this popup. Populate the |
| * returned Menu with items before calling {@link #show()}. |
| * |
| * @return the {@link Menu} associated with this popup |
| * @see #show() |
| * @see #getMenuInflater() |
| */ |
| public Menu getMenu() { |
| return mMenu; |
| } |
| |
| /** |
| * @return a {@link MenuInflater} that can be used to inflate menu items |
| * from XML into the menu returned by {@link #getMenu()} |
| * @see #getMenu() |
| */ |
| public MenuInflater getMenuInflater() { |
| return new MenuInflater(mContext); |
| } |
| |
| /** |
| * Inflate a menu resource into this PopupMenu. This is equivalent to |
| * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}. |
| * |
| * @param menuRes Menu resource to inflate |
| */ |
| public void inflate(@MenuRes int menuRes) { |
| getMenuInflater().inflate(menuRes, mMenu); |
| } |
| |
| /** |
| * Show the menu popup anchored to the view specified during construction. |
| * |
| * @see #dismiss() |
| */ |
| public void show() { |
| mPopup.show(); |
| } |
| |
| /** |
| * Dismiss the menu popup. |
| * |
| * @see #show() |
| */ |
| public void dismiss() { |
| mPopup.dismiss(); |
| } |
| |
| /** |
| * Sets a listener that will be notified when the user selects an item from |
| * the menu. |
| * |
| * @param listener the listener to notify |
| */ |
| public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { |
| mMenuItemClickListener = listener; |
| } |
| |
| /** |
| * Sets a listener that will be notified when this menu is dismissed. |
| * |
| * @param listener the listener to notify |
| */ |
| public void setOnDismissListener(OnDismissListener listener) { |
| mOnDismissListener = listener; |
| } |
| |
| /** |
| * Sets whether the popup menu's adapter is forced to show icons in the |
| * menu item views. |
| * <p> |
| * Changes take effect on the next call to show(). |
| * |
| * @param forceShowIcon {@code true} to force icons to be shown, or |
| * {@code false} for icons to be optionally shown |
| */ |
| public void setForceShowIcon(boolean forceShowIcon) { |
| mPopup.setForceShowIcon(forceShowIcon); |
| } |
| |
| /** |
| * Interface responsible for receiving menu item click events if the items |
| * themselves do not have individual item click listeners. |
| */ |
| public interface OnMenuItemClickListener { |
| /** |
| * This method will be invoked when a menu item is clicked if the item |
| * itself did not already handle the event. |
| * |
| * @param item the menu item that was clicked |
| * @return {@code true} if the event was handled, {@code false} |
| * otherwise |
| */ |
| boolean onMenuItemClick(MenuItem item); |
| } |
| |
| /** |
| * Callback interface used to notify the application that the menu has closed. |
| */ |
| public interface OnDismissListener { |
| /** |
| * Called when the associated menu has been dismissed. |
| * |
| * @param menu the popup menu that was dismissed |
| */ |
| void onDismiss(PopupMenu menu); |
| } |
| |
| /** |
| * Returns the {@link ListView} representing the list of menu items in the currently showing |
| * menu. |
| * |
| * @return The view representing the list of menu items. |
| * @hide |
| */ |
| @TestApi |
| public ListView getMenuListView() { |
| if (!mPopup.isShowing()) { |
| return null; |
| } |
| return mPopup.getPopup().getListView(); |
| } |
| } |