• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.annotation.MenuRes;
20 import android.annotation.TestApi;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.Context;
23 import android.os.Build;
24 import android.view.Gravity;
25 import android.view.Menu;
26 import android.view.MenuInflater;
27 import android.view.MenuItem;
28 import android.view.View;
29 import android.view.View.OnTouchListener;
30 
31 import com.android.internal.R;
32 import com.android.internal.view.menu.MenuBuilder;
33 import com.android.internal.view.menu.MenuPopupHelper;
34 import com.android.internal.view.menu.ShowableListMenu;
35 
36 /**
37  * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a
38  * {@link View}. The popup will appear below the anchor view if there is room,
39  * or above it if there is not. If the IME is visible the popup will not
40  * overlap it until it is touched. Touching outside of the popup will dismiss
41  * it.
42  */
43 public class PopupMenu {
44     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
45     private final Context mContext;
46     private final MenuBuilder mMenu;
47     private final View mAnchor;
48     @UnsupportedAppUsage
49     private final MenuPopupHelper mPopup;
50 
51     private OnMenuItemClickListener mMenuItemClickListener;
52     private OnDismissListener mOnDismissListener;
53     private OnTouchListener mDragListener;
54 
55     /**
56      * Constructor to create a new popup menu with an anchor view.
57      *
58      * @param context Context the popup menu is running in, through which it
59      *        can access the current theme, resources, etc.
60      * @param anchor Anchor view for this popup. The popup will appear below
61      *        the anchor if there is room, or above it if there is not.
62      */
PopupMenu(Context context, View anchor)63     public PopupMenu(Context context, View anchor) {
64         this(context, anchor, Gravity.NO_GRAVITY);
65     }
66 
67     /**
68      * Constructor to create a new popup menu with an anchor view and alignment
69      * gravity.
70      *
71      * @param context Context the popup menu is running in, through which it
72      *        can access the current theme, resources, etc.
73      * @param anchor Anchor view for this popup. The popup will appear below
74      *        the anchor if there is room, or above it if there is not.
75      * @param gravity The {@link Gravity} value for aligning the popup with its
76      *        anchor.
77      */
PopupMenu(Context context, View anchor, int gravity)78     public PopupMenu(Context context, View anchor, int gravity) {
79         this(context, anchor, gravity, R.attr.popupMenuStyle, 0);
80     }
81 
82     /**
83      * Constructor a create a new popup menu with a specific style.
84      *
85      * @param context Context the popup menu is running in, through which it
86      *        can access the current theme, resources, etc.
87      * @param anchor Anchor view for this popup. The popup will appear below
88      *        the anchor if there is room, or above it if there is not.
89      * @param gravity The {@link Gravity} value for aligning the popup with its
90      *        anchor.
91      * @param popupStyleAttr An attribute in the current theme that contains a
92      *        reference to a style resource that supplies default values for
93      *        the popup window. Can be 0 to not look for defaults.
94      * @param popupStyleRes A resource identifier of a style resource that
95      *        supplies default values for the popup window, used only if
96      *        popupStyleAttr is 0 or can not be found in the theme. Can be 0
97      *        to not look for defaults.
98      */
PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr, int popupStyleRes)99     public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
100             int popupStyleRes) {
101         mContext = context;
102         mAnchor = anchor;
103 
104         mMenu = new MenuBuilder(context);
105         mMenu.setCallback(new MenuBuilder.Callback() {
106             @Override
107             public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
108                 if (mMenuItemClickListener != null) {
109                     return mMenuItemClickListener.onMenuItemClick(item);
110                 }
111                 return false;
112             }
113 
114             @Override
115             public void onMenuModeChange(MenuBuilder menu) {
116             }
117         });
118 
119         mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
120         mPopup.setGravity(gravity);
121         mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
122             @Override
123             public void onDismiss() {
124                 if (mOnDismissListener != null) {
125                     mOnDismissListener.onDismiss(PopupMenu.this);
126                 }
127             }
128         });
129     }
130 
131     /**
132      * Sets the gravity used to align the popup window to its anchor view.
133      * <p>
134      * If the popup is showing, calling this method will take effect only
135      * the next time the popup is shown.
136      *
137      * @param gravity the gravity used to align the popup window
138      * @see #getGravity()
139      */
setGravity(int gravity)140     public void setGravity(int gravity) {
141         mPopup.setGravity(gravity);
142     }
143 
144     /**
145      * @return the gravity used to align the popup window to its anchor view
146      * @see #setGravity(int)
147      */
getGravity()148     public int getGravity() {
149         return mPopup.getGravity();
150     }
151 
152     /**
153      * Returns an {@link OnTouchListener} that can be added to the anchor view
154      * to implement drag-to-open behavior.
155      * <p>
156      * When the listener is set on a view, touching that view and dragging
157      * outside of its bounds will open the popup window. Lifting will select
158      * the currently touched list item.
159      * <p>
160      * Example usage:
161      * <pre>
162      * PopupMenu myPopup = new PopupMenu(context, myAnchor);
163      * myAnchor.setOnTouchListener(myPopup.getDragToOpenListener());
164      * </pre>
165      *
166      * @return a touch listener that controls drag-to-open behavior
167      */
getDragToOpenListener()168     public OnTouchListener getDragToOpenListener() {
169         if (mDragListener == null) {
170             mDragListener = new ForwardingListener(mAnchor) {
171                 @Override
172                 protected boolean onForwardingStarted() {
173                     show();
174                     return true;
175                 }
176 
177                 @Override
178                 protected boolean onForwardingStopped() {
179                     dismiss();
180                     return true;
181                 }
182 
183                 @Override
184                 public ShowableListMenu getPopup() {
185                     // This will be null until show() is called.
186                     return mPopup.getPopup();
187                 }
188             };
189         }
190 
191         return mDragListener;
192     }
193 
194     /**
195      * Returns the {@link Menu} associated with this popup. Populate the
196      * returned Menu with items before calling {@link #show()}.
197      *
198      * @return the {@link Menu} associated with this popup
199      * @see #show()
200      * @see #getMenuInflater()
201      */
getMenu()202     public Menu getMenu() {
203         return mMenu;
204     }
205 
206     /**
207      * @return a {@link MenuInflater} that can be used to inflate menu items
208      *         from XML into the menu returned by {@link #getMenu()}
209      * @see #getMenu()
210      */
getMenuInflater()211     public MenuInflater getMenuInflater() {
212         return new MenuInflater(mContext);
213     }
214 
215     /**
216      * Inflate a menu resource into this PopupMenu. This is equivalent to
217      * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
218      *
219      * @param menuRes Menu resource to inflate
220      */
inflate(@enuRes int menuRes)221     public void inflate(@MenuRes int menuRes) {
222         getMenuInflater().inflate(menuRes, mMenu);
223     }
224 
225     /**
226      * Show the menu popup anchored to the view specified during construction.
227      *
228      * @see #dismiss()
229      */
show()230     public void show() {
231         mPopup.show();
232     }
233 
234     /**
235      * Dismiss the menu popup.
236      *
237      * @see #show()
238      */
dismiss()239     public void dismiss() {
240         mPopup.dismiss();
241     }
242 
243     /**
244      * Sets a listener that will be notified when the user selects an item from
245      * the menu.
246      *
247      * @param listener the listener to notify
248      */
setOnMenuItemClickListener(OnMenuItemClickListener listener)249     public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
250         mMenuItemClickListener = listener;
251     }
252 
253     /**
254      * Sets a listener that will be notified when this menu is dismissed.
255      *
256      * @param listener the listener to notify
257      */
setOnDismissListener(OnDismissListener listener)258     public void setOnDismissListener(OnDismissListener listener) {
259         mOnDismissListener = listener;
260     }
261 
262     /**
263      * Sets whether the popup menu's adapter is forced to show icons in the
264      * menu item views.
265      * <p>
266      * Changes take effect on the next call to show().
267      *
268      * @param forceShowIcon {@code true} to force icons to be shown, or
269      *                  {@code false} for icons to be optionally shown
270      */
setForceShowIcon(boolean forceShowIcon)271     public void setForceShowIcon(boolean forceShowIcon) {
272         mPopup.setForceShowIcon(forceShowIcon);
273     }
274 
275     /**
276      * Interface responsible for receiving menu item click events if the items
277      * themselves do not have individual item click listeners.
278      */
279     public interface OnMenuItemClickListener {
280         /**
281          * This method will be invoked when a menu item is clicked if the item
282          * itself did not already handle the event.
283          *
284          * @param item the menu item that was clicked
285          * @return {@code true} if the event was handled, {@code false}
286          *         otherwise
287          */
onMenuItemClick(MenuItem item)288         boolean onMenuItemClick(MenuItem item);
289     }
290 
291     /**
292      * Callback interface used to notify the application that the menu has closed.
293      */
294     public interface OnDismissListener {
295         /**
296          * Called when the associated menu has been dismissed.
297          *
298          * @param menu the popup menu that was dismissed
299          */
onDismiss(PopupMenu menu)300         void onDismiss(PopupMenu menu);
301     }
302 
303     /**
304      * Returns the {@link ListView} representing the list of menu items in the currently showing
305      * menu.
306      *
307      * @return The view representing the list of menu items.
308      * @hide
309      */
310     @TestApi
getMenuListView()311     public ListView getMenuListView() {
312         if (!mPopup.isShowing()) {
313             return null;
314         }
315         return mPopup.getPopup().getListView();
316     }
317 }
318