blob: 0ce964666a2b984b83bcdde6d1a5182ca6871dac [file] [log] [blame]
Adam Powell4be0d522010-08-03 17:53:14 -07001/*
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
17package android.widget;
18
Tor Norbye7b9c9122013-05-30 16:48:33 -070019import android.annotation.MenuRes;
Vladislav Kaznacheevf087a172017-01-11 17:06:28 -080020import android.annotation.TestApi;
Artur Satayeved5a6ae2019-12-10 17:47:54 +000021import android.compat.annotation.UnsupportedAppUsage;
Adam Powell4be0d522010-08-03 17:53:14 -070022import android.content.Context;
Adam Powell54c94de2013-09-26 15:36:34 -070023import android.view.Gravity;
Adam Powell4be0d522010-08-03 17:53:14 -070024import android.view.Menu;
25import android.view.MenuInflater;
26import android.view.MenuItem;
27import android.view.View;
Alan Viverette1955a5b52013-08-27 15:45:16 -070028import android.view.View.OnTouchListener;
Adam Powell4be0d522010-08-03 17:53:14 -070029
Aurimas Liutikas99441c52016-10-11 16:48:32 -070030import com.android.internal.R;
31import com.android.internal.view.menu.MenuBuilder;
32import com.android.internal.view.menu.MenuPopupHelper;
33import com.android.internal.view.menu.ShowableListMenu;
34
Adam Powell4be0d522010-08-03 17:53:14 -070035/**
Alan Viverette73238102015-11-16 16:55:58 -050036 * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a
37 * {@link View}. The popup will appear below the anchor view if there is room,
38 * or above it if there is not. If the IME is visible the popup will not
39 * overlap it until it is touched. Touching outside of the popup will dismiss
40 * it.
Adam Powell4be0d522010-08-03 17:53:14 -070041 */
Alan Viverette73238102015-11-16 16:55:58 -050042public class PopupMenu {
Mathew Inwood978c6e22018-08-21 15:58:55 +010043 @UnsupportedAppUsage
Alan Viverette29632522014-10-15 17:19:30 -070044 private final Context mContext;
45 private final MenuBuilder mMenu;
46 private final View mAnchor;
Mathew Inwood978c6e22018-08-21 15:58:55 +010047 @UnsupportedAppUsage
Alan Viverette29632522014-10-15 17:19:30 -070048 private final MenuPopupHelper mPopup;
49
Adam Powell4be0d522010-08-03 17:53:14 -070050 private OnMenuItemClickListener mMenuItemClickListener;
Alan Viverette708aa9d2015-11-20 15:21:30 -050051 private OnDismissListener mOnDismissListener;
Alan Viverette1955a5b52013-08-27 15:45:16 -070052 private OnTouchListener mDragListener;
Adam Powell42b91bb2011-06-21 18:32:26 -070053
54 /**
Alan Viverette29632522014-10-15 17:19:30 -070055 * Constructor to create a new popup menu with an anchor view.
Adam Powell4be0d522010-08-03 17:53:14 -070056 *
Alan Viverette29632522014-10-15 17:19:30 -070057 * @param context Context the popup menu is running in, through which it
58 * can access the current theme, resources, etc.
59 * @param anchor Anchor view for this popup. The popup will appear below
60 * the anchor if there is room, or above it if there is not.
Adam Powell4be0d522010-08-03 17:53:14 -070061 */
62 public PopupMenu(Context context, View anchor) {
Adam Powell54c94de2013-09-26 15:36:34 -070063 this(context, anchor, Gravity.NO_GRAVITY);
64 }
65
66 /**
Alan Viverette29632522014-10-15 17:19:30 -070067 * Constructor to create a new popup menu with an anchor view and alignment
68 * gravity.
Adam Powell54c94de2013-09-26 15:36:34 -070069 *
Alan Viverette29632522014-10-15 17:19:30 -070070 * @param context Context the popup menu is running in, through which it
71 * can access the current theme, resources, etc.
72 * @param anchor Anchor view for this popup. The popup will appear below
73 * the anchor if there is room, or above it if there is not.
74 * @param gravity The {@link Gravity} value for aligning the popup with its
75 * anchor.
Adam Powell54c94de2013-09-26 15:36:34 -070076 */
77 public PopupMenu(Context context, View anchor, int gravity) {
Alan Viverette29632522014-10-15 17:19:30 -070078 this(context, anchor, gravity, R.attr.popupMenuStyle, 0);
79 }
80
81 /**
82 * Constructor a create a new popup menu with a specific style.
83 *
84 * @param context Context the popup menu is running in, through which it
85 * can access the current theme, resources, etc.
86 * @param anchor Anchor view for this popup. The popup will appear below
87 * the anchor if there is room, or above it if there is not.
88 * @param gravity The {@link Gravity} value for aligning the popup with its
89 * anchor.
90 * @param popupStyleAttr An attribute in the current theme that contains a
91 * reference to a style resource that supplies default values for
92 * the popup window. Can be 0 to not look for defaults.
93 * @param popupStyleRes A resource identifier of a style resource that
94 * supplies default values for the popup window, used only if
95 * popupStyleAttr is 0 or can not be found in the theme. Can be 0
96 * to not look for defaults.
97 */
98 public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
99 int popupStyleRes) {
Adam Powell4be0d522010-08-03 17:53:14 -0700100 mContext = context;
Adam Powell4be0d522010-08-03 17:53:14 -0700101 mAnchor = anchor;
Alan Viverette73238102015-11-16 16:55:58 -0500102
103 mMenu = new MenuBuilder(context);
104 mMenu.setCallback(new MenuBuilder.Callback() {
105 @Override
106 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
107 if (mMenuItemClickListener != null) {
108 return mMenuItemClickListener.onMenuItemClick(item);
109 }
110 return false;
111 }
112
113 @Override
114 public void onMenuModeChange(MenuBuilder menu) {
115 }
116 });
117
Alan Viverette29632522014-10-15 17:19:30 -0700118 mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
Adam Powell54c94de2013-09-26 15:36:34 -0700119 mPopup.setGravity(gravity);
Alan Viverette708aa9d2015-11-20 15:21:30 -0500120 mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
Alan Viverette73238102015-11-16 16:55:58 -0500121 @Override
Alan Viverette708aa9d2015-11-20 15:21:30 -0500122 public void onDismiss() {
123 if (mOnDismissListener != null) {
124 mOnDismissListener.onDismiss(PopupMenu.this);
Alan Viverette73238102015-11-16 16:55:58 -0500125 }
126 }
Alan Viverette73238102015-11-16 16:55:58 -0500127 });
Adam Powell4be0d522010-08-03 17:53:14 -0700128 }
129
130 /**
Alan Viverette75d83792015-01-07 15:51:54 -0800131 * Sets the gravity used to align the popup window to its anchor view.
132 * <p>
133 * If the popup is showing, calling this method will take effect only
134 * the next time the popup is shown.
135 *
136 * @param gravity the gravity used to align the popup window
Alan Viverette75d83792015-01-07 15:51:54 -0800137 * @see #getGravity()
138 */
139 public void setGravity(int gravity) {
140 mPopup.setGravity(gravity);
141 }
142
143 /**
144 * @return the gravity used to align the popup window to its anchor view
Alan Viverette75d83792015-01-07 15:51:54 -0800145 * @see #setGravity(int)
146 */
147 public int getGravity() {
148 return mPopup.getGravity();
149 }
150
151 /**
Alan Viverette1955a5b52013-08-27 15:45:16 -0700152 * Returns an {@link OnTouchListener} that can be added to the anchor view
153 * to implement drag-to-open behavior.
154 * <p>
155 * When the listener is set on a view, touching that view and dragging
Alan Viverette73238102015-11-16 16:55:58 -0500156 * outside of its bounds will open the popup window. Lifting will select
157 * the currently touched list item.
Alan Viverette1955a5b52013-08-27 15:45:16 -0700158 * <p>
159 * Example usage:
Alan Viverette3f9832d2013-08-30 14:43:25 -0700160 * <pre>
161 * PopupMenu myPopup = new PopupMenu(context, myAnchor);
162 * myAnchor.setOnTouchListener(myPopup.getDragToOpenListener());
163 * </pre>
Alan Viverette1955a5b52013-08-27 15:45:16 -0700164 *
165 * @return a touch listener that controls drag-to-open behavior
166 */
167 public OnTouchListener getDragToOpenListener() {
168 if (mDragListener == null) {
169 mDragListener = new ForwardingListener(mAnchor) {
170 @Override
Alan Viverette99d72492013-09-24 11:27:08 -0700171 protected boolean onForwardingStarted() {
172 show();
173 return true;
174 }
175
176 @Override
177 protected boolean onForwardingStopped() {
178 dismiss();
179 return true;
180 }
181
182 @Override
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700183 public ShowableListMenu getPopup() {
Alan Viverette99d72492013-09-24 11:27:08 -0700184 // This will be null until show() is called.
Alan Viverette1955a5b52013-08-27 15:45:16 -0700185 return mPopup.getPopup();
186 }
187 };
188 }
189
190 return mDragListener;
191 }
192
193 /**
Alan Viverette73238102015-11-16 16:55:58 -0500194 * Returns the {@link Menu} associated with this popup. Populate the
195 * returned Menu with items before calling {@link #show()}.
Adam Powell4be0d522010-08-03 17:53:14 -0700196 *
Alan Viverette73238102015-11-16 16:55:58 -0500197 * @return the {@link Menu} associated with this popup
Adam Powell4be0d522010-08-03 17:53:14 -0700198 * @see #show()
199 * @see #getMenuInflater()
200 */
201 public Menu getMenu() {
202 return mMenu;
203 }
204
205 /**
Alan Viverette73238102015-11-16 16:55:58 -0500206 * @return a {@link MenuInflater} that can be used to inflate menu items
207 * from XML into the menu returned by {@link #getMenu()}
Adam Powell4be0d522010-08-03 17:53:14 -0700208 * @see #getMenu()
209 */
210 public MenuInflater getMenuInflater() {
211 return new MenuInflater(mContext);
212 }
213
214 /**
Alan Viverette73238102015-11-16 16:55:58 -0500215 * Inflate a menu resource into this PopupMenu. This is equivalent to
216 * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
217 *
Adam Powell42b91bb2011-06-21 18:32:26 -0700218 * @param menuRes Menu resource to inflate
219 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700220 public void inflate(@MenuRes int menuRes) {
Adam Powell42b91bb2011-06-21 18:32:26 -0700221 getMenuInflater().inflate(menuRes, mMenu);
222 }
223
224 /**
Adam Powell4be0d522010-08-03 17:53:14 -0700225 * Show the menu popup anchored to the view specified during construction.
Alan Viverette73238102015-11-16 16:55:58 -0500226 *
Adam Powell4be0d522010-08-03 17:53:14 -0700227 * @see #dismiss()
228 */
229 public void show() {
230 mPopup.show();
231 }
232
233 /**
234 * Dismiss the menu popup.
Alan Viverette73238102015-11-16 16:55:58 -0500235 *
Adam Powell4be0d522010-08-03 17:53:14 -0700236 * @see #show()
237 */
238 public void dismiss() {
239 mPopup.dismiss();
240 }
241
Adam Powell42b91bb2011-06-21 18:32:26 -0700242 /**
Alan Viverette73238102015-11-16 16:55:58 -0500243 * Sets a listener that will be notified when the user selects an item from
244 * the menu.
Adam Powell42b91bb2011-06-21 18:32:26 -0700245 *
Alan Viverette73238102015-11-16 16:55:58 -0500246 * @param listener the listener to notify
Adam Powell42b91bb2011-06-21 18:32:26 -0700247 */
Adam Powell4be0d522010-08-03 17:53:14 -0700248 public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
249 mMenuItemClickListener = listener;
250 }
251
252 /**
Alan Viverette73238102015-11-16 16:55:58 -0500253 * Sets a listener that will be notified when this menu is dismissed.
Adam Powell42b91bb2011-06-21 18:32:26 -0700254 *
Alan Viverette73238102015-11-16 16:55:58 -0500255 * @param listener the listener to notify
Adam Powell42b91bb2011-06-21 18:32:26 -0700256 */
257 public void setOnDismissListener(OnDismissListener listener) {
Alan Viverette708aa9d2015-11-20 15:21:30 -0500258 mOnDismissListener = listener;
Adam Powell42b91bb2011-06-21 18:32:26 -0700259 }
260
261 /**
Mihai Popada59e1d2019-02-05 16:02:21 +0000262 * Sets whether the popup menu's adapter is forced to show icons in the
263 * menu item views.
264 * <p>
265 * Changes take effect on the next call to show().
266 *
267 * @param forceShowIcon {@code true} to force icons to be shown, or
268 * {@code false} for icons to be optionally shown
269 */
270 public void setForceShowIcon(boolean forceShowIcon) {
271 mPopup.setForceShowIcon(forceShowIcon);
272 }
273
274 /**
Alan Viverette73238102015-11-16 16:55:58 -0500275 * Interface responsible for receiving menu item click events if the items
276 * themselves do not have individual item click listeners.
Adam Powell4be0d522010-08-03 17:53:14 -0700277 */
278 public interface OnMenuItemClickListener {
279 /**
Alan Viverette73238102015-11-16 16:55:58 -0500280 * This method will be invoked when a menu item is clicked if the item
281 * itself did not already handle the event.
Adam Powell4be0d522010-08-03 17:53:14 -0700282 *
Alan Viverette73238102015-11-16 16:55:58 -0500283 * @param item the menu item that was clicked
284 * @return {@code true} if the event was handled, {@code false}
285 * otherwise
Adam Powell4be0d522010-08-03 17:53:14 -0700286 */
Alan Viverette73238102015-11-16 16:55:58 -0500287 boolean onMenuItemClick(MenuItem item);
288 }
289
290 /**
291 * Callback interface used to notify the application that the menu has closed.
292 */
293 public interface OnDismissListener {
294 /**
295 * Called when the associated menu has been dismissed.
296 *
297 * @param menu the popup menu that was dismissed
298 */
299 void onDismiss(PopupMenu menu);
Adam Powell4be0d522010-08-03 17:53:14 -0700300 }
Vladislav Kaznacheevf087a172017-01-11 17:06:28 -0800301
302 /**
303 * Returns the {@link ListView} representing the list of menu items in the currently showing
304 * menu.
305 *
306 * @return The view representing the list of menu items.
307 * @hide
308 */
309 @TestApi
310 public ListView getMenuListView() {
311 if (!mPopup.isShowing()) {
312 return null;
313 }
314 return mPopup.getPopup().getListView();
315 }
Adam Powell4be0d522010-08-03 17:53:14 -0700316}