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 * Sets the gravity used to align the popup window to its anchor view. 118 * <p> 119 * If the popup is showing, calling this method will take effect only 120 * the next time the popup is shown. 121 * 122 * @param gravity the gravity used to align the popup window 123 * 124 * @see #getGravity() 125 */ 126 public void setGravity(int gravity) { 127 mPopup.setGravity(gravity); 128 } 129 130 /** 131 * @return the gravity used to align the popup window to its anchor view 132 * 133 * @see #setGravity(int) 134 */ 135 public int getGravity() { 136 return mPopup.getGravity(); 137 } 138 139 /** 140 * Returns an {@link android.view.View.OnTouchListener} that can be added to the anchor view 141 * to implement drag-to-open behavior. 142 * <p> 143 * When the listener is set on a view, touching that view and dragging 144 * outside of its bounds will open the popup window. Lifting will select the 145 * currently touched list item. 146 * <p> 147 * Example usage: 148 * <pre> 149 * PopupMenu myPopup = new PopupMenu(context, myAnchor); 150 * myAnchor.setOnTouchListener(myPopup.getDragToOpenListener()); 151 * </pre> 152 * 153 * @return a touch listener that controls drag-to-open behavior 154 */ 155 public View.OnTouchListener getDragToOpenListener() { 156 if (mDragListener == null) { 157 mDragListener = new ListPopupWindow.ForwardingListener(mAnchor) { 158 @Override 159 protected boolean onForwardingStarted() { 160 show(); 161 return true; 162 } 163 164 @Override 165 protected boolean onForwardingStopped() { 166 dismiss(); 167 return true; 168 } 169 170 @Override 171 public ListPopupWindow getPopup() { 172 // This will be null until show() is called. 173 return mPopup.getPopup(); 174 } 175 }; 176 } 177 178 return mDragListener; 179 } 180 181 /** 182 * @return the {@link Menu} associated with this popup. Populate the returned Menu with 183 * items before calling {@link #show()}. 184 * 185 * @see #show() 186 * @see #getMenuInflater() 187 */ 188 public Menu getMenu() { 189 return mMenu; 190 } 191 192 /** 193 * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the 194 * menu returned by {@link #getMenu()}. 195 * 196 * @see #getMenu() 197 */ 198 public MenuInflater getMenuInflater() { 199 return new SupportMenuInflater(mContext); 200 } 201 202 /** 203 * Inflate a menu resource into this PopupMenu. This is equivalent to calling 204 * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()). 205 * @param menuRes Menu resource to inflate 206 */ 207 public void inflate(@MenuRes int menuRes) { 208 getMenuInflater().inflate(menuRes, mMenu); 209 } 210 211 /** 212 * Show the menu popup anchored to the view specified during construction. 213 * @see #dismiss() 214 */ 215 public void show() { 216 mPopup.show(); 217 } 218 219 /** 220 * Dismiss the menu popup. 221 * @see #show() 222 */ 223 public void dismiss() { 224 mPopup.dismiss(); 225 } 226 227 /** 228 * Set a listener that will be notified when the user selects an item from the menu. 229 * 230 * @param listener Listener to notify 231 */ 232 public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { 233 mMenuItemClickListener = listener; 234 } 235 236 /** 237 * Set a listener that will be notified when this menu is dismissed. 238 * 239 * @param listener Listener to notify 240 */ 241 public void setOnDismissListener(OnDismissListener listener) { 242 mDismissListener = listener; 243 } 244 245 /** 246 * @hide 247 */ 248 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 249 if (mMenuItemClickListener != null) { 250 return mMenuItemClickListener.onMenuItemClick(item); 251 } 252 return false; 253 } 254 255 /** 256 * @hide 257 */ 258 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 259 if (mDismissListener != null) { 260 mDismissListener.onDismiss(this); 261 } 262 } 263 264 /** 265 * @hide 266 */ 267 public boolean onOpenSubMenu(MenuBuilder subMenu) { 268 if (subMenu == null) return false; 269 270 if (!subMenu.hasVisibleItems()) { 271 return true; 272 } 273 274 // Current menu will be dismissed by the normal helper, submenu will be shown in its place. 275 new MenuPopupHelper(mContext, subMenu, mAnchor).show(); 276 return true; 277 } 278 279 /** 280 * @hide 281 */ 282 public void onCloseSubMenu(SubMenuBuilder menu) { 283 } 284 285 /** 286 * @hide 287 */ 288 public void onMenuModeChange(MenuBuilder menu) { 289 } 290 291 /** 292 * Interface responsible for receiving menu item click events if the items themselves 293 * do not have individual item click listeners. 294 */ 295 public interface OnMenuItemClickListener { 296 /** 297 * This method will be invoked when a menu item is clicked if the item itself did 298 * not already handle the event. 299 * 300 * @param item {@link MenuItem} that was clicked 301 * @return <code>true</code> if the event was handled, <code>false</code> otherwise. 302 */ 303 public boolean onMenuItemClick(MenuItem item); 304 } 305}