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