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