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