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