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