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