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.view.Gravity; 24 import android.view.Menu; 25 import android.view.MenuInflater; 26 import android.view.MenuItem; 27 import android.view.View; 28 import android.view.View.OnTouchListener; 29 30 import com.android.internal.R; 31 import com.android.internal.view.menu.MenuBuilder; 32 import com.android.internal.view.menu.MenuPopupHelper; 33 import com.android.internal.view.menu.ShowableListMenu; 34 35 /** 36 * 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. 41 */ 42 public class PopupMenu { 43 @UnsupportedAppUsage 44 private final Context mContext; 45 private final MenuBuilder mMenu; 46 private final View mAnchor; 47 @UnsupportedAppUsage 48 private final MenuPopupHelper mPopup; 49 50 private OnMenuItemClickListener mMenuItemClickListener; 51 private OnDismissListener mOnDismissListener; 52 private OnTouchListener mDragListener; 53 54 /** 55 * Constructor to create a new popup menu with an anchor view. 56 * 57 * @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. 61 */ PopupMenu(Context context, View anchor)62 public PopupMenu(Context context, View anchor) { 63 this(context, anchor, Gravity.NO_GRAVITY); 64 } 65 66 /** 67 * Constructor to create a new popup menu with an anchor view and alignment 68 * gravity. 69 * 70 * @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. 76 */ PopupMenu(Context context, View anchor, int gravity)77 public PopupMenu(Context context, View anchor, int gravity) { 78 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 */ PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr, int popupStyleRes)98 public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr, 99 int popupStyleRes) { 100 mContext = context; 101 mAnchor = anchor; 102 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 118 mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes); 119 mPopup.setGravity(gravity); 120 mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { 121 @Override 122 public void onDismiss() { 123 if (mOnDismissListener != null) { 124 mOnDismissListener.onDismiss(PopupMenu.this); 125 } 126 } 127 }); 128 } 129 130 /** 131 * 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 137 * @see #getGravity() 138 */ setGravity(int gravity)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 145 * @see #setGravity(int) 146 */ getGravity()147 public int getGravity() { 148 return mPopup.getGravity(); 149 } 150 151 /** 152 * 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 156 * outside of its bounds will open the popup window. Lifting will select 157 * the currently touched list item. 158 * <p> 159 * Example usage: 160 * <pre> 161 * PopupMenu myPopup = new PopupMenu(context, myAnchor); 162 * myAnchor.setOnTouchListener(myPopup.getDragToOpenListener()); 163 * </pre> 164 * 165 * @return a touch listener that controls drag-to-open behavior 166 */ getDragToOpenListener()167 public OnTouchListener getDragToOpenListener() { 168 if (mDragListener == null) { 169 mDragListener = new ForwardingListener(mAnchor) { 170 @Override 171 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 183 public ShowableListMenu getPopup() { 184 // This will be null until show() is called. 185 return mPopup.getPopup(); 186 } 187 }; 188 } 189 190 return mDragListener; 191 } 192 193 /** 194 * Returns the {@link Menu} associated with this popup. Populate the 195 * returned Menu with items before calling {@link #show()}. 196 * 197 * @return the {@link Menu} associated with this popup 198 * @see #show() 199 * @see #getMenuInflater() 200 */ getMenu()201 public Menu getMenu() { 202 return mMenu; 203 } 204 205 /** 206 * @return a {@link MenuInflater} that can be used to inflate menu items 207 * from XML into the menu returned by {@link #getMenu()} 208 * @see #getMenu() 209 */ getMenuInflater()210 public MenuInflater getMenuInflater() { 211 return new MenuInflater(mContext); 212 } 213 214 /** 215 * Inflate a menu resource into this PopupMenu. This is equivalent to 216 * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}. 217 * 218 * @param menuRes Menu resource to inflate 219 */ inflate(@enuRes int menuRes)220 public void inflate(@MenuRes int menuRes) { 221 getMenuInflater().inflate(menuRes, mMenu); 222 } 223 224 /** 225 * Show the menu popup anchored to the view specified during construction. 226 * 227 * @see #dismiss() 228 */ show()229 public void show() { 230 mPopup.show(); 231 } 232 233 /** 234 * Dismiss the menu popup. 235 * 236 * @see #show() 237 */ dismiss()238 public void dismiss() { 239 mPopup.dismiss(); 240 } 241 242 /** 243 * Sets a listener that will be notified when the user selects an item from 244 * the menu. 245 * 246 * @param listener the listener to notify 247 */ setOnMenuItemClickListener(OnMenuItemClickListener listener)248 public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { 249 mMenuItemClickListener = listener; 250 } 251 252 /** 253 * Sets a listener that will be notified when this menu is dismissed. 254 * 255 * @param listener the listener to notify 256 */ setOnDismissListener(OnDismissListener listener)257 public void setOnDismissListener(OnDismissListener listener) { 258 mOnDismissListener = listener; 259 } 260 261 /** 262 * 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 */ setForceShowIcon(boolean forceShowIcon)270 public void setForceShowIcon(boolean forceShowIcon) { 271 mPopup.setForceShowIcon(forceShowIcon); 272 } 273 274 /** 275 * Interface responsible for receiving menu item click events if the items 276 * themselves do not have individual item click listeners. 277 */ 278 public interface OnMenuItemClickListener { 279 /** 280 * This method will be invoked when a menu item is clicked if the item 281 * itself did not already handle the event. 282 * 283 * @param item the menu item that was clicked 284 * @return {@code true} if the event was handled, {@code false} 285 * otherwise 286 */ onMenuItemClick(MenuItem item)287 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 */ onDismiss(PopupMenu menu)299 void onDismiss(PopupMenu menu); 300 } 301 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 getMenuListView()310 public ListView getMenuListView() { 311 if (!mPopup.isShowing()) { 312 return null; 313 } 314 return mPopup.getPopup().getListView(); 315 } 316 } 317