blob: 044ee6e9bd825de1a409f7659dbc986567111846 [file] [log] [blame]
Adam Powell42675342010-07-09 18:02:59 -07001/*
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 com.android.internal.view.menu;
18
Oren Blasberg99162822015-09-10 14:37:26 -070019import com.android.internal.view.menu.MenuPresenter.Callback;
20
Alan Viverette708aa9d2015-11-20 15:21:30 -050021import android.annotation.AttrRes;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.annotation.StyleRes;
Adam Powell42675342010-07-09 18:02:59 -070025import android.content.Context;
Adam Powell54c94de2013-09-26 15:36:34 -070026import android.view.Gravity;
Adam Powell42675342010-07-09 18:02:59 -070027import android.view.View;
Alan Viverette708aa9d2015-11-20 15:21:30 -050028import android.widget.PopupWindow.OnDismissListener;
Adam Powell42675342010-07-09 18:02:59 -070029
30/**
Adam Powell696cba52011-03-29 10:38:16 -070031 * Presents a menu as a small, simple popup anchored to another view.
Adam Powell42675342010-07-09 18:02:59 -070032 */
Alan Viverette021627e2015-11-25 14:22:00 -050033public class MenuPopupHelper implements MenuHelper {
Alan Viverette0bce6ab2013-06-26 17:46:16 -070034 private final Context mContext;
Alan Viverette708aa9d2015-11-20 15:21:30 -050035
36 // Immutable cached popup menu properties.
Alan Viverette0bce6ab2013-06-26 17:46:16 -070037 private final MenuBuilder mMenu;
Alan Viverette0bce6ab2013-06-26 17:46:16 -070038 private final boolean mOverflowOnly;
Alan Viverette560f1702014-05-05 14:40:07 -070039 private final int mPopupStyleAttr;
Alan Viverette29632522014-10-15 17:19:30 -070040 private final int mPopupStyleRes;
Alan Viverette0bce6ab2013-06-26 17:46:16 -070041
Alan Viverette708aa9d2015-11-20 15:21:30 -050042 // Mutable cached popup menu properties.
Adam Powell4afd62b2011-02-18 15:02:18 -080043 private View mAnchorView;
Alan Viveretted6443f62015-11-20 13:57:15 -050044 private int mDropDownGravity = Gravity.START;
Oren Blasberged391262015-09-01 12:12:51 -070045 private boolean mForceShowIcon;
Oren Blasberg99162822015-09-10 14:37:26 -070046 private Callback mPresenterCallback;
Alan Viveretted6443f62015-11-20 13:57:15 -050047
Alan Viverette708aa9d2015-11-20 15:21:30 -050048 private MenuPopup mPopup;
49 private OnDismissListener mOnDismissListener;
Adam Powell54c94de2013-09-26 15:36:34 -070050
Alan Viverette708aa9d2015-11-20 15:21:30 -050051 public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
Alan Viverette29632522014-10-15 17:19:30 -070052 this(context, menu, null, false, com.android.internal.R.attr.popupMenuStyle, 0);
Adam Powell8028dd32010-07-15 10:16:33 -070053 }
54
Alan Viverette708aa9d2015-11-20 15:21:30 -050055 public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
56 @NonNull View anchorView) {
Alan Viverette29632522014-10-15 17:19:30 -070057 this(context, menu, anchorView, false, com.android.internal.R.attr.popupMenuStyle, 0);
Adam Powell8028dd32010-07-15 10:16:33 -070058 }
59
Alan Viverette708aa9d2015-11-20 15:21:30 -050060 public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
61 @NonNull View anchorView,
62 boolean overflowOnly, @AttrRes int popupStyleAttr) {
Alan Viverette29632522014-10-15 17:19:30 -070063 this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
64 }
65
Alan Viverette708aa9d2015-11-20 15:21:30 -050066 public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
67 @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
68 @StyleRes int popupStyleRes) {
Adam Powell42675342010-07-09 18:02:59 -070069 mContext = context;
Adam Powell8028dd32010-07-15 10:16:33 -070070 mMenu = menu;
Alan Viverette708aa9d2015-11-20 15:21:30 -050071 mAnchorView = anchorView;
Adam Powell8028dd32010-07-15 10:16:33 -070072 mOverflowOnly = overflowOnly;
Alan Viverette560f1702014-05-05 14:40:07 -070073 mPopupStyleAttr = popupStyleAttr;
Alan Viverette29632522014-10-15 17:19:30 -070074 mPopupStyleRes = popupStyleRes;
Oren Blasbergb23976e2015-09-01 14:55:42 -070075 }
Adam Powell696cba52011-03-29 10:38:16 -070076
Alan Viverette708aa9d2015-11-20 15:21:30 -050077 public void setOnDismissListener(@Nullable OnDismissListener listener) {
78 mOnDismissListener = listener;
Adam Powell42675342010-07-09 18:02:59 -070079 }
80
Alan Viverette708aa9d2015-11-20 15:21:30 -050081 /**
82 * Sets the view to which the popup window is anchored.
83 * <p>
84 * Changes take effect on the next call to show().
85 *
86 * @param anchor the view to which the popup window should be anchored
87 */
88 public void setAnchorView(@NonNull View anchor) {
Adam Powell4afd62b2011-02-18 15:02:18 -080089 mAnchorView = anchor;
Adam Powellf0ad6e62011-01-10 17:14:06 -080090 }
91
Alan Viverette708aa9d2015-11-20 15:21:30 -050092 /**
93 * Sets whether the popup menu's adapter is forced to show icons in the
94 * menu item views.
95 * <p>
96 * Changes take effect on the next call to show().
97 *
98 * @param forceShowIcon {@code true} to force icons to be shown, or
99 * {@code false} for icons to be optionally shown
100 */
101 public void setForceShowIcon(boolean forceShowIcon) {
102 mForceShowIcon = forceShowIcon;
Adam Powell91511032011-07-13 10:24:06 -0700103 }
104
Alan Viverette708aa9d2015-11-20 15:21:30 -0500105 /**
106 * Sets the alignment of the popup window relative to the anchor view.
107 * <p>
108 * Changes take effect on the next call to show().
109 *
110 * @param gravity alignment of the popup relative to the anchor
111 */
Adam Powell54c94de2013-09-26 15:36:34 -0700112 public void setGravity(int gravity) {
113 mDropDownGravity = gravity;
114 }
115
Alan Viverette708aa9d2015-11-20 15:21:30 -0500116 /**
117 * @return alignment of the popup relative to the anchor
118 */
Alan Viverette75d83792015-01-07 15:51:54 -0800119 public int getGravity() {
120 return mDropDownGravity;
121 }
122
Adam Powell42675342010-07-09 18:02:59 -0700123 public void show() {
Adam Powell5e3f2842011-01-07 17:16:56 -0800124 if (!tryShow()) {
125 throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
126 }
127 }
128
Oren Blasberged391262015-09-01 12:12:51 -0700129 public void show(int x, int y) {
130 if (!tryShow(x, y)) {
131 throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
132 }
133 }
134
Alan Viverette708aa9d2015-11-20 15:21:30 -0500135 @NonNull
136 public MenuPopup getPopup() {
137 if (mPopup == null) {
138 mPopup = createPopup();
139 }
Alan Viveretteca6a36112013-08-16 14:41:06 -0700140 return mPopup;
141 }
142
Alan Viverette8fd949e2015-03-11 12:21:30 -0700143 /**
Oren Blasbergb23976e2015-09-01 14:55:42 -0700144 * Attempts to show the popup anchored to the view specified by {@link #setAnchorView(View)}.
Alan Viverette8fd949e2015-03-11 12:21:30 -0700145 *
Oren Blasbergb23976e2015-09-01 14:55:42 -0700146 * @return {@code true} if the popup was shown or was already showing prior to calling this
147 * method, {@code false} otherwise
Alan Viverette8fd949e2015-03-11 12:21:30 -0700148 */
Adam Powell5e3f2842011-01-07 17:16:56 -0800149 public boolean tryShow() {
Alan Viverette8fd949e2015-03-11 12:21:30 -0700150 if (isShowing()) {
151 return true;
152 }
153
Oren Blasberg99162822015-09-10 14:37:26 -0700154 if (mAnchorView == null) {
Adam Powell5e3f2842011-01-07 17:16:56 -0800155 return false;
Adam Powell8028dd32010-07-15 10:16:33 -0700156 }
Adam Powell42675342010-07-09 18:02:59 -0700157
Alan Viverette708aa9d2015-11-20 15:21:30 -0500158 showPopup(0, 0, false, false);
Oren Blasberged391262015-09-01 12:12:51 -0700159 return true;
160 }
161
Alan Viveretted6443f62015-11-20 13:57:15 -0500162 /**
163 * Shows the popup menu and makes a best-effort to anchor it to the
164 * specified (x,y) coordinate relative to the anchor view.
165 * <p>
166 * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
167 * display the popup with its top-left corner at (x,y) relative to the
168 * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
169 * popup's top-right corner will be at (x,y).
170 * <p>
171 * If the popup cannot be displayed fully on-screen, this method will
172 * attempt to scroll the anchor view's ancestors and/or offset the popup
173 * such that it may be displayed fully on-screen.
174 *
175 * @param x x coordinate relative to the anchor view
176 * @param y y coordinate relative to the anchor view
177 * @return {@code true} if the popup was shown or was already showing prior
178 * to calling this method, {@code false} otherwise
179 */
Oren Blasberged391262015-09-01 12:12:51 -0700180 public boolean tryShow(int x, int y) {
181 if (isShowing()) {
182 return true;
183 }
184
185 if (mAnchorView == null) {
186 return false;
187 }
188
Alan Viverette708aa9d2015-11-20 15:21:30 -0500189 showPopup(x, y, true, true);
Oren Blasberged391262015-09-01 12:12:51 -0700190 return true;
191 }
192
Alan Viverette708aa9d2015-11-20 15:21:30 -0500193 /**
194 * Creates the popup and assigns cached properties.
195 *
196 * @return an initialized popup
197 */
198 @NonNull
199 private MenuPopup createPopup() {
200 final boolean enableCascadingSubmenus = mContext.getResources().getBoolean(
201 com.android.internal.R.bool.config_enableCascadingSubmenus);
Alan Viveretted6443f62015-11-20 13:57:15 -0500202
Alan Viverette708aa9d2015-11-20 15:21:30 -0500203 final MenuPopup popup;
204 if (enableCascadingSubmenus) {
205 popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
206 mPopupStyleRes, mOverflowOnly);
207 } else {
208 popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
209 mPopupStyleRes, mOverflowOnly);
210 }
211
212 // Assign immutable properties.
213 popup.addMenu(mMenu);
214 popup.setOnDismissListener(mInternalOnDismissListener);
215
216 // Assign mutable properties. These may be reassigned later.
217 popup.setAnchorView(mAnchorView);
218 popup.setCallback(mPresenterCallback);
219 popup.setForceShowIcon(mForceShowIcon);
220 popup.setGravity(mDropDownGravity);
221
222 return popup;
223 }
224
225 private void showPopup(int xOffset, int yOffset, boolean resolveOffsets, boolean showTitle) {
226 if (resolveOffsets) {
Alan Viveretted6443f62015-11-20 13:57:15 -0500227 // If the resolved drop-down gravity is RIGHT, the popup's right
228 // edge will be aligned with the anchor view. Adjust by the anchor
229 // width such that the top-right corner is at the X offset.
230 final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
231 mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
Alan Viveretted6443f62015-11-20 13:57:15 -0500232 if (hgrav == Gravity.RIGHT) {
Alan Viverette708aa9d2015-11-20 15:21:30 -0500233 xOffset -= mAnchorView.getWidth();
Alan Viveretted6443f62015-11-20 13:57:15 -0500234 }
Alan Viveretted6443f62015-11-20 13:57:15 -0500235 }
Oren Blasberg99162822015-09-10 14:37:26 -0700236
Alan Viverette708aa9d2015-11-20 15:21:30 -0500237 final MenuPopup popup = getPopup();
238 popup.setHorizontalOffset(xOffset);
239 popup.setVerticalOffset(yOffset);
240 popup.setShowTitle(showTitle);
241 popup.show();
Adam Powell42675342010-07-09 18:02:59 -0700242 }
243
Alan Viverette708aa9d2015-11-20 15:21:30 -0500244 /**
245 * Dismisses the popup, if showing.
246 */
Alan Viverette021627e2015-11-25 14:22:00 -0500247 @Override
Adam Powell42675342010-07-09 18:02:59 -0700248 public void dismiss() {
Adam Powell3d3da272010-08-11 18:06:17 -0700249 if (isShowing()) {
250 mPopup.dismiss();
251 }
Adam Powell8515ee82010-11-30 14:09:55 -0800252 }
253
Alan Viverette708aa9d2015-11-20 15:21:30 -0500254 /**
255 * Called after the popup has been dismissed.
256 * <p>
257 * <strong>Note:</strong> Subclasses should call the super implementation
258 * last to ensure that any necessary tear down has occurred before the
259 * listener specified by {@link #setOnDismissListener(OnDismissListener)}
260 * is called.
261 */
262 protected void onDismiss() {
Adam Powell8515ee82010-11-30 14:09:55 -0800263 mPopup = null;
Alan Viverette708aa9d2015-11-20 15:21:30 -0500264
265 if (mOnDismissListener != null) {
266 mOnDismissListener.onDismiss();
267 }
Adam Powell42675342010-07-09 18:02:59 -0700268 }
269
Adam Powell8028dd32010-07-15 10:16:33 -0700270 public boolean isShowing() {
271 return mPopup != null && mPopup.isShowing();
272 }
273
Alan Viverette021627e2015-11-25 14:22:00 -0500274 @Override
275 public void setPresenterCallback(@Nullable MenuPresenter.Callback cb) {
Oren Blasberg99162822015-09-10 14:37:26 -0700276 mPresenterCallback = cb;
Alan Viverette708aa9d2015-11-20 15:21:30 -0500277 if (mPopup != null) {
278 mPopup.setCallback(cb);
279 }
Adam Powell696cba52011-03-29 10:38:16 -0700280 }
Alan Viverette708aa9d2015-11-20 15:21:30 -0500281
282 /**
283 * Listener used to proxy dismiss callbacks to the helper's owner.
284 */
285 private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
286 @Override
287 public void onDismiss() {
288 MenuPopupHelper.this.onDismiss();
289 }
290 };
Adam Powell42675342010-07-09 18:02:59 -0700291}