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