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}