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