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