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