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