blob: 16b903d4da9e6407a458a43722dbe21f2a782c72 [file] [log] [blame]
Adam Powellc3fa6302010-05-18 11:36:27 -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 android.widget;
18
Alan Viverette02cd0f92016-01-13 13:33:17 -050019import android.annotation.AttrRes;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.StyleRes;
Mathew Inwood978c6e22018-08-21 15:58:55 +010023import android.annotation.UnsupportedAppUsage;
Adam Powellc3fa6302010-05-18 11:36:27 -070024import android.content.Context;
Alan Viverettef023c252014-08-28 13:55:18 -070025import android.content.res.TypedArray;
Adam Powellc3fa6302010-05-18 11:36:27 -070026import android.database.DataSetObserver;
27import android.graphics.Rect;
28import android.graphics.drawable.Drawable;
Robert Carrc1fdd2a2017-06-21 10:25:05 -070029import android.os.Build;
Adam Powellc3fa6302010-05-18 11:36:27 -070030import android.os.Handler;
31import android.util.AttributeSet;
32import android.util.Log;
Adam Powell54c94de2013-09-26 15:36:34 -070033import android.view.Gravity;
Adam Powellc3fa6302010-05-18 11:36:27 -070034import android.view.KeyEvent;
35import android.view.MotionEvent;
36import android.view.View;
Adam Powellc3fa6302010-05-18 11:36:27 -070037import android.view.View.MeasureSpec;
38import android.view.View.OnTouchListener;
Gilles Debunne711734a2011-02-07 18:26:11 -080039import android.view.ViewGroup;
40import android.view.ViewParent;
Alan Viverette80ebe0d2015-04-30 15:53:11 -070041import android.view.WindowManager;
Alan Viverette02cd0f92016-01-13 13:33:17 -050042import android.widget.AdapterView.OnItemSelectedListener;
Fabrice Di Meglio1d3d7da2012-07-27 15:15:04 -070043
Aurimas Liutikas99441c52016-10-11 16:48:32 -070044import com.android.internal.R;
45import com.android.internal.view.menu.ShowableListMenu;
46
Adam Powellc3fa6302010-05-18 11:36:27 -070047/**
48 * A ListPopupWindow anchors itself to a host view and displays a
Adam Powell65d79fb2010-08-11 22:05:46 -070049 * list of choices.
Aurimas Liutikas99441c52016-10-11 16:48:32 -070050 *
Adam Powellc3fa6302010-05-18 11:36:27 -070051 * <p>ListPopupWindow contains a number of tricky behaviors surrounding
52 * positioning, scrolling parents to fit the dropdown, interacting
53 * sanely with the IME if present, and others.
Aurimas Liutikas99441c52016-10-11 16:48:32 -070054 *
Adam Powellc3fa6302010-05-18 11:36:27 -070055 * @see android.widget.AutoCompleteTextView
56 * @see android.widget.Spinner
57 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -070058public class ListPopupWindow implements ShowableListMenu {
Adam Powellc3fa6302010-05-18 11:36:27 -070059 private static final String TAG = "ListPopupWindow";
60 private static final boolean DEBUG = false;
61
62 /**
63 * This value controls the length of time that the user
64 * must leave a pointer down without scrolling to expand
65 * the autocomplete dropdown list to cover the IME.
66 */
67 private static final int EXPAND_LIST_TIMEOUT = 250;
68
69 private Context mContext;
Adam Powellc3fa6302010-05-18 11:36:27 -070070 private ListAdapter mAdapter;
Mathew Inwood978c6e22018-08-21 15:58:55 +010071 @UnsupportedAppUsage
Adam Powellc3fa6302010-05-18 11:36:27 -070072 private DropDownListView mDropDownList;
73
74 private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
75 private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
76 private int mDropDownHorizontalOffset;
77 private int mDropDownVerticalOffset;
Alan Viverette80ebe0d2015-04-30 15:53:11 -070078 private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
Adam Powell8132ba52011-07-15 17:37:11 -070079 private boolean mDropDownVerticalOffsetSet;
Alan Viverette91098572016-01-19 14:07:31 -050080 private boolean mIsAnimatedFromAnchor = true;
Vladislav Kaznacheevb40e61b2017-03-07 11:03:35 -080081 private boolean mOverlapAnchor;
Vladislav Kaznacheevadf08d42017-04-05 15:22:10 -070082 private boolean mOverlapAnchorSet;
Adam Powellc3fa6302010-05-18 11:36:27 -070083
Adam Powell54c94de2013-09-26 15:36:34 -070084 private int mDropDownGravity = Gravity.NO_GRAVITY;
85
Adam Powellc3fa6302010-05-18 11:36:27 -070086 private boolean mDropDownAlwaysVisible = false;
87 private boolean mForceIgnoreOutsideTouch = false;
Adam Powell348e69c2011-02-16 16:49:50 -080088 int mListItemExpandMaximum = Integer.MAX_VALUE;
Adam Powellc3fa6302010-05-18 11:36:27 -070089
90 private View mPromptView;
91 private int mPromptPosition = POSITION_PROMPT_ABOVE;
92
93 private DataSetObserver mObserver;
94
95 private View mDropDownAnchorView;
96
97 private Drawable mDropDownListHighlight;
98
99 private AdapterView.OnItemClickListener mItemClickListener;
100 private AdapterView.OnItemSelectedListener mItemSelectedListener;
101
Adam Powell42675342010-07-09 18:02:59 -0700102 private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
Adam Powellc3fa6302010-05-18 11:36:27 -0700103 private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
104 private final PopupScrollListener mScrollListener = new PopupScrollListener();
105 private final ListSelectorHider mHideSelector = new ListSelectorHider();
106 private Runnable mShowDropDownRunnable;
107
Alan Viverette4a507232015-06-18 17:11:19 -0700108 private final Handler mHandler;
Adam Powellc3fa6302010-05-18 11:36:27 -0700109
Alan Viverette91098572016-01-19 14:07:31 -0500110 private final Rect mTempRect = new Rect();
111
112 /**
113 * Optional anchor-relative bounds to be used as the transition epicenter.
114 * When {@code null}, the anchor bounds are used as the epicenter.
115 */
116 private Rect mEpicenterBounds;
Adam Powellc3fa6302010-05-18 11:36:27 -0700117
118 private boolean mModal;
119
Mathew Inwood978c6e22018-08-21 15:58:55 +0100120 @UnsupportedAppUsage
Oren Blasberg8e12f8d2015-09-02 14:25:56 -0700121 PopupWindow mPopup;
122
Adam Powellc3fa6302010-05-18 11:36:27 -0700123 /**
124 * The provided prompt view should appear above list content.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700125 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700126 * @see #setPromptPosition(int)
127 * @see #getPromptPosition()
128 * @see #setPromptView(View)
129 */
130 public static final int POSITION_PROMPT_ABOVE = 0;
131
132 /**
133 * The provided prompt view should appear below list content.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700134 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700135 * @see #setPromptPosition(int)
136 * @see #getPromptPosition()
137 * @see #setPromptView(View)
138 */
139 public static final int POSITION_PROMPT_BELOW = 1;
140
141 /**
142 * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}.
143 * If used to specify a popup width, the popup will match the width of the anchor view.
144 * If used to specify a popup height, the popup will fill available space.
145 */
146 public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700147
Adam Powellc3fa6302010-05-18 11:36:27 -0700148 /**
149 * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
150 * If used to specify a popup width, the popup will use the width of its content.
151 */
152 public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700153
Adam Powellc3fa6302010-05-18 11:36:27 -0700154 /**
155 * Mode for {@link #setInputMethodMode(int)}: the requirements for the
156 * input method should be based on the focusability of the popup. That is
157 * if it is focusable than it needs to work with the input method, else
158 * it doesn't.
159 */
160 public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700161
Adam Powellc3fa6302010-05-18 11:36:27 -0700162 /**
163 * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
164 * work with an input method, regardless of whether it is focusable. This
165 * means that it will always be displayed so that the user can also operate
166 * the input method while it is shown.
167 */
168 public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700169
Adam Powellc3fa6302010-05-18 11:36:27 -0700170 /**
171 * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
172 * work with an input method, regardless of whether it is focusable. This
173 * means that it will always be displayed to use as much space on the
174 * screen as needed, regardless of whether this covers the input method.
175 */
176 public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED;
177
178 /**
179 * Create a new, empty popup window capable of displaying items from a ListAdapter.
180 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700181 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700182 * @param context Context used for contained views.
183 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500184 public ListPopupWindow(@NonNull Context context) {
Daniel Lehmannc2238d02010-10-21 11:52:55 -0700185 this(context, null, com.android.internal.R.attr.listPopupWindowStyle, 0);
Adam Powellc3fa6302010-05-18 11:36:27 -0700186 }
187
188 /**
189 * Create a new, empty popup window capable of displaying items from a ListAdapter.
190 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700191 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700192 * @param context Context used for contained views.
193 * @param attrs Attributes from inflating parent views used to style the popup.
194 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500195 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) {
Adam Powell0b2d3062010-09-14 16:15:02 -0700196 this(context, attrs, com.android.internal.R.attr.listPopupWindowStyle, 0);
Adam Powellc3fa6302010-05-18 11:36:27 -0700197 }
198
199 /**
200 * Create a new, empty popup window capable of displaying items from a ListAdapter.
201 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700202 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700203 * @param context Context used for contained views.
204 * @param attrs Attributes from inflating parent views used to style the popup.
205 * @param defStyleAttr Default style attribute to use for popup content.
206 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500207 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
208 @AttrRes int defStyleAttr) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700209 this(context, attrs, defStyleAttr, 0);
210 }
211
212 /**
213 * Create a new, empty popup window capable of displaying items from a ListAdapter.
214 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700215 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700216 * @param context Context used for contained views.
217 * @param attrs Attributes from inflating parent views used to style the popup.
218 * @param defStyleAttr Style attribute to read for default styling of popup content.
219 * @param defStyleRes Style resource ID to use for default styling of popup content.
220 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500221 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
222 @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700223 mContext = context;
Alan Viverette4a507232015-06-18 17:11:19 -0700224 mHandler = new Handler(context.getMainLooper());
Alan Viverettef023c252014-08-28 13:55:18 -0700225
226 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow,
227 defStyleAttr, defStyleRes);
228 mDropDownHorizontalOffset = a.getDimensionPixelOffset(
229 R.styleable.ListPopupWindow_dropDownHorizontalOffset, 0);
230 mDropDownVerticalOffset = a.getDimensionPixelOffset(
231 R.styleable.ListPopupWindow_dropDownVerticalOffset, 0);
232 if (mDropDownVerticalOffset != 0) {
233 mDropDownVerticalOffsetSet = true;
234 }
235 a.recycle();
236
Adam Powellc3fa6302010-05-18 11:36:27 -0700237 mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes);
Adam Powell6f5e9342011-01-27 13:30:55 -0800238 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
Adam Powellc3fa6302010-05-18 11:36:27 -0700239 }
240
241 /**
242 * Sets the adapter that provides the data and the views to represent the data
243 * in this popup window.
244 *
245 * @param adapter The adapter to use to create this window's content.
246 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500247 public void setAdapter(@Nullable ListAdapter adapter) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700248 if (mObserver == null) {
249 mObserver = new PopupDataSetObserver();
250 } else if (mAdapter != null) {
Adam Powell99969da2010-06-16 10:51:30 -0700251 mAdapter.unregisterDataSetObserver(mObserver);
Adam Powellc3fa6302010-05-18 11:36:27 -0700252 }
253 mAdapter = adapter;
254 if (mAdapter != null) {
255 adapter.registerDataSetObserver(mObserver);
256 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700257
Adam Powellc3fa6302010-05-18 11:36:27 -0700258 if (mDropDownList != null) {
259 mDropDownList.setAdapter(mAdapter);
260 }
261 }
262
263 /**
264 * Set where the optional prompt view should appear. The default is
265 * {@link #POSITION_PROMPT_ABOVE}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700266 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700267 * @param position A position constant declaring where the prompt should be displayed.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700268 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700269 * @see #POSITION_PROMPT_ABOVE
270 * @see #POSITION_PROMPT_BELOW
271 */
272 public void setPromptPosition(int position) {
273 mPromptPosition = position;
274 }
275
276 /**
277 * @return Where the optional prompt view should appear.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700278 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700279 * @see #POSITION_PROMPT_ABOVE
280 * @see #POSITION_PROMPT_BELOW
281 */
282 public int getPromptPosition() {
283 return mPromptPosition;
284 }
285
286 /**
287 * Set whether this window should be modal when shown.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700288 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700289 * <p>If a popup window is modal, it will receive all touch and key input.
290 * If the user touches outside the popup window's content area the popup window
291 * will be dismissed.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700292 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700293 * @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
294 */
295 public void setModal(boolean modal) {
Chet Haasee04499a2014-08-05 15:24:43 -0700296 mModal = modal;
Adam Powellc3fa6302010-05-18 11:36:27 -0700297 mPopup.setFocusable(modal);
298 }
299
300 /**
301 * Returns whether the popup window will be modal when shown.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700302 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700303 * @return {@code true} if the popup window will be modal, {@code false} otherwise.
304 */
305 public boolean isModal() {
306 return mModal;
307 }
308
309 /**
310 * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
311 * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
312 * ignore outside touch even when the drop down is not set to always visible.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700313 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700314 * @hide Used only by AutoCompleteTextView to handle some internal special cases.
315 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100316 @UnsupportedAppUsage
Adam Powellc3fa6302010-05-18 11:36:27 -0700317 public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
318 mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
319 }
320
321 /**
322 * Sets whether the drop-down should remain visible under certain conditions.
323 *
324 * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless
325 * of the size or content of the list. {@link #getBackground()} will fill any space
326 * that is not used by the list.
327 *
328 * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
329 *
330 * @hide Only used by AutoCompleteTextView under special conditions.
331 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100332 @UnsupportedAppUsage
Adam Powellc3fa6302010-05-18 11:36:27 -0700333 public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
334 mDropDownAlwaysVisible = dropDownAlwaysVisible;
335 }
336
337 /**
338 * @return Whether the drop-down is visible under special conditions.
339 *
340 * @hide Only used by AutoCompleteTextView under special conditions.
341 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100342 @UnsupportedAppUsage
Adam Powellc3fa6302010-05-18 11:36:27 -0700343 public boolean isDropDownAlwaysVisible() {
344 return mDropDownAlwaysVisible;
345 }
346
347 /**
348 * Sets the operating mode for the soft input area.
349 *
350 * @param mode The desired mode, see
351 * {@link android.view.WindowManager.LayoutParams#softInputMode}
352 * for the full list
353 *
354 * @see android.view.WindowManager.LayoutParams#softInputMode
355 * @see #getSoftInputMode()
356 */
357 public void setSoftInputMode(int mode) {
358 mPopup.setSoftInputMode(mode);
359 }
360
361 /**
362 * Returns the current value in {@link #setSoftInputMode(int)}.
363 *
364 * @see #setSoftInputMode(int)
365 * @see android.view.WindowManager.LayoutParams#softInputMode
366 */
367 public int getSoftInputMode() {
368 return mPopup.getSoftInputMode();
369 }
370
371 /**
372 * Sets a drawable to use as the list item selector.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700373 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700374 * @param selector List selector drawable to use in the popup.
375 */
376 public void setListSelector(Drawable selector) {
377 mDropDownListHighlight = selector;
378 }
379
380 /**
381 * @return The background drawable for the popup window.
382 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500383 public @Nullable Drawable getBackground() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700384 return mPopup.getBackground();
385 }
386
387 /**
388 * Sets a drawable to be the background for the popup window.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700389 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700390 * @param d A drawable to set as the background.
391 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500392 public void setBackgroundDrawable(@Nullable Drawable d) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700393 mPopup.setBackgroundDrawable(d);
394 }
395
396 /**
397 * Set an animation style to use when the popup window is shown or dismissed.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700398 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700399 * @param animationStyle Animation style to use.
400 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500401 public void setAnimationStyle(@StyleRes int animationStyle) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700402 mPopup.setAnimationStyle(animationStyle);
403 }
404
405 /**
406 * Returns the animation style that will be used when the popup window is
407 * shown or dismissed.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700408 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700409 * @return Animation style that will be used.
410 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500411 public @StyleRes int getAnimationStyle() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700412 return mPopup.getAnimationStyle();
413 }
414
415 /**
416 * Returns the view that will be used to anchor this popup.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700417 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700418 * @return The popup's anchor view
419 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500420 public @Nullable View getAnchorView() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700421 return mDropDownAnchorView;
422 }
423
424 /**
425 * Sets the popup's anchor view. This popup will always be positioned relative to
426 * the anchor view when shown.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700427 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700428 * @param anchor The view to use as an anchor.
429 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500430 public void setAnchorView(@Nullable View anchor) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700431 mDropDownAnchorView = anchor;
432 }
433
434 /**
435 * @return The horizontal offset of the popup from its anchor in pixels.
436 */
437 public int getHorizontalOffset() {
438 return mDropDownHorizontalOffset;
439 }
440
441 /**
442 * Set the horizontal offset of this popup from its anchor view in pixels.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700443 *
Adam Powella984b382010-06-04 14:20:10 -0700444 * @param offset The horizontal offset of the popup from its anchor.
Adam Powellc3fa6302010-05-18 11:36:27 -0700445 */
446 public void setHorizontalOffset(int offset) {
447 mDropDownHorizontalOffset = offset;
448 }
449
450 /**
451 * @return The vertical offset of the popup from its anchor in pixels.
452 */
453 public int getVerticalOffset() {
Adam Powell8132ba52011-07-15 17:37:11 -0700454 if (!mDropDownVerticalOffsetSet) {
455 return 0;
456 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700457 return mDropDownVerticalOffset;
458 }
459
460 /**
461 * Set the vertical offset of this popup from its anchor view in pixels.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700462 *
Adam Powella984b382010-06-04 14:20:10 -0700463 * @param offset The vertical offset of the popup from its anchor.
Adam Powellc3fa6302010-05-18 11:36:27 -0700464 */
465 public void setVerticalOffset(int offset) {
466 mDropDownVerticalOffset = offset;
Adam Powell8132ba52011-07-15 17:37:11 -0700467 mDropDownVerticalOffsetSet = true;
Adam Powellc3fa6302010-05-18 11:36:27 -0700468 }
469
470 /**
Alan Viverette91098572016-01-19 14:07:31 -0500471 * Specifies the anchor-relative bounds of the popup's transition
472 * epicenter.
473 *
Matvei Malkovb0bd3e72019-02-08 16:16:44 +0000474 * @param bounds anchor-relative bounds, or {@code null} to use default epicenter
475 *
476 * @see #getEpicenterBounds()
Alan Viverette91098572016-01-19 14:07:31 -0500477 */
Matvei Malkovb0bd3e72019-02-08 16:16:44 +0000478 public void setEpicenterBounds(@Nullable Rect bounds) {
479 mEpicenterBounds = bounds != null ? new Rect(bounds) : null;
480 }
481
482 /**
483 * Returns bounds which are used as a popup's epicenter
484 * of the enter and exit transitions.
485 *
486 * @return bounds relative to anchor view, or {@code null} if not set
487 * @see #setEpicenterBounds(Rect)
488 */
489 @Nullable
490 public Rect getEpicenterBounds() {
491 return mEpicenterBounds != null ? new Rect(mEpicenterBounds) : null;
Alan Viverette91098572016-01-19 14:07:31 -0500492 }
493
494 /**
Adam Powell54c94de2013-09-26 15:36:34 -0700495 * Set the gravity of the dropdown list. This is commonly used to
496 * set gravity to START or END for alignment with the anchor.
497 *
498 * @param gravity Gravity value to use
499 */
500 public void setDropDownGravity(int gravity) {
501 mDropDownGravity = gravity;
502 }
503
504 /**
Adam Powellc3fa6302010-05-18 11:36:27 -0700505 * @return The width of the popup window in pixels.
506 */
507 public int getWidth() {
508 return mDropDownWidth;
509 }
510
511 /**
512 * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT}
513 * or {@link #WRAP_CONTENT}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700514 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700515 * @param width Width of the popup window.
516 */
517 public void setWidth(int width) {
518 mDropDownWidth = width;
519 }
520
521 /**
Adam Powell42675342010-07-09 18:02:59 -0700522 * Sets the width of the popup window by the size of its content. The final width may be
523 * larger to accommodate styled window dressing.
524 *
525 * @param width Desired width of content in pixels.
526 */
527 public void setContentWidth(int width) {
528 Drawable popupBackground = mPopup.getBackground();
529 if (popupBackground != null) {
Adam Powella39b9872011-01-05 16:07:54 -0800530 popupBackground.getPadding(mTempRect);
531 mDropDownWidth = mTempRect.left + mTempRect.right + width;
Adam Powell62e2bde2011-08-15 15:50:05 -0700532 } else {
533 setWidth(width);
Adam Powell42675342010-07-09 18:02:59 -0700534 }
535 }
536
537 /**
Adam Powellc3fa6302010-05-18 11:36:27 -0700538 * @return The height of the popup window in pixels.
539 */
540 public int getHeight() {
541 return mDropDownHeight;
542 }
543
544 /**
545 * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700546 *
Shunta Satoec2952b2016-11-04 18:57:39 +0900547 * @param height Height of the popup window must be a positive value,
548 * {@link #MATCH_PARENT}, or {@link #WRAP_CONTENT}.
549 *
550 * @throws IllegalArgumentException if height is set to negative value
Adam Powellc3fa6302010-05-18 11:36:27 -0700551 */
552 public void setHeight(int height) {
Shunta Satoec2952b2016-11-04 18:57:39 +0900553 if (height < 0 && ViewGroup.LayoutParams.WRAP_CONTENT != height
554 && ViewGroup.LayoutParams.MATCH_PARENT != height) {
Robert Carrc1fdd2a2017-06-21 10:25:05 -0700555 if (mContext.getApplicationInfo().targetSdkVersion
556 < Build.VERSION_CODES.O) {
557 Log.e(TAG, "Negative value " + height + " passed to ListPopupWindow#setHeight"
558 + " produces undefined results");
559 } else {
560 throw new IllegalArgumentException(
561 "Invalid height. Must be a positive value, MATCH_PARENT, or WRAP_CONTENT.");
562 }
Shunta Satoec2952b2016-11-04 18:57:39 +0900563 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700564 mDropDownHeight = height;
565 }
566
567 /**
Alan Viverette80ebe0d2015-04-30 15:53:11 -0700568 * Set the layout type for this popup window.
569 * <p>
570 * See {@link WindowManager.LayoutParams#type} for possible values.
571 *
572 * @param layoutType Layout type for this window.
573 *
574 * @see WindowManager.LayoutParams#type
575 */
576 public void setWindowLayoutType(int layoutType) {
577 mDropDownWindowLayoutType = layoutType;
578 }
579
580 /**
Adam Powellc3fa6302010-05-18 11:36:27 -0700581 * Sets a listener to receive events when a list item is clicked.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700582 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700583 * @param clickListener Listener to register
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700584 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700585 * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
586 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500587 public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener clickListener) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700588 mItemClickListener = clickListener;
589 }
590
591 /**
592 * Sets a listener to receive events when a list item is selected.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700593 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700594 * @param selectedListener Listener to register.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700595 *
Alan Viverette02cd0f92016-01-13 13:33:17 -0500596 * @see ListView#setOnItemSelectedListener(OnItemSelectedListener)
Adam Powellc3fa6302010-05-18 11:36:27 -0700597 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500598 public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700599 mItemSelectedListener = selectedListener;
600 }
601
602 /**
603 * Set a view to act as a user prompt for this popup window. Where the prompt view will appear
604 * is controlled by {@link #setPromptPosition(int)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700605 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700606 * @param prompt View to use as an informational prompt.
607 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500608 public void setPromptView(@Nullable View prompt) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700609 boolean showing = isShowing();
610 if (showing) {
611 removePromptView();
612 }
613 mPromptView = prompt;
614 if (showing) {
615 show();
616 }
617 }
618
619 /**
620 * Post a {@link #show()} call to the UI thread.
621 */
622 public void postShow() {
623 mHandler.post(mShowDropDownRunnable);
624 }
625
626 /**
627 * Show the popup list. If the list is already showing, this method
628 * will recalculate the popup's size and position.
629 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700630 @Override
Adam Powellc3fa6302010-05-18 11:36:27 -0700631 public void show() {
632 int height = buildDropDown();
633
Alan Viverette80ebe0d2015-04-30 15:53:11 -0700634 final boolean noInputMethod = isInputMethodNotNeeded();
Adam Powell348e69c2011-02-16 16:49:50 -0800635 mPopup.setAllowScrollingAnchorParent(!noInputMethod);
Alan Viverette80ebe0d2015-04-30 15:53:11 -0700636 mPopup.setWindowLayoutType(mDropDownWindowLayoutType);
Adam Powellc3fa6302010-05-18 11:36:27 -0700637
638 if (mPopup.isShowing()) {
tianran.x.lia31d4ed2016-11-28 15:34:24 +0800639 if (!getAnchorView().isAttachedToWindow()) {
640 //Don't update position if the anchor view is detached from window.
641 return;
642 }
Alan Viverette259c2842015-03-22 17:39:39 -0700643 final int widthSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700644 if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
645 // The call to PopupWindow's update method below can accept -1 for any
646 // value you do not want to update.
647 widthSpec = -1;
648 } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
649 widthSpec = getAnchorView().getWidth();
650 } else {
651 widthSpec = mDropDownWidth;
652 }
653
Alan Viverette259c2842015-03-22 17:39:39 -0700654 final int heightSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700655 if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
656 // The call to PopupWindow's update method below can accept -1 for any
657 // value you do not want to update.
658 heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
659 if (noInputMethod) {
Alan Viverette259c2842015-03-22 17:39:39 -0700660 mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
661 ViewGroup.LayoutParams.MATCH_PARENT : 0);
662 mPopup.setHeight(0);
Adam Powellc3fa6302010-05-18 11:36:27 -0700663 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700664 mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
665 ViewGroup.LayoutParams.MATCH_PARENT : 0);
666 mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
Adam Powellc3fa6302010-05-18 11:36:27 -0700667 }
668 } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
669 heightSpec = height;
670 } else {
671 heightSpec = mDropDownHeight;
672 }
673
674 mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
675
676 mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
Jun Mukai0eaec1f2015-06-09 15:00:28 -0700677 mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
678 (heightSpec < 0)? -1 : heightSpec);
Evan Roskyadf5bec2017-10-19 10:24:07 -0700679 mPopup.getContentView().restoreDefaultFocus();
Adam Powellc3fa6302010-05-18 11:36:27 -0700680 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700681 final int widthSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700682 if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
683 widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
684 } else {
685 if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
Alan Viverette259c2842015-03-22 17:39:39 -0700686 widthSpec = getAnchorView().getWidth();
Adam Powellc3fa6302010-05-18 11:36:27 -0700687 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700688 widthSpec = mDropDownWidth;
Adam Powellc3fa6302010-05-18 11:36:27 -0700689 }
690 }
691
Alan Viverette259c2842015-03-22 17:39:39 -0700692 final int heightSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700693 if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
694 heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
695 } else {
696 if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
Alan Viverette259c2842015-03-22 17:39:39 -0700697 heightSpec = height;
Adam Powellc3fa6302010-05-18 11:36:27 -0700698 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700699 heightSpec = mDropDownHeight;
Adam Powellc3fa6302010-05-18 11:36:27 -0700700 }
701 }
702
Alan Viverette259c2842015-03-22 17:39:39 -0700703 mPopup.setWidth(widthSpec);
704 mPopup.setHeight(heightSpec);
Matvei Malkov25c9ac32019-03-12 12:26:40 +0000705 mPopup.setIsClippedToScreen(true);
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700706
Adam Powellc3fa6302010-05-18 11:36:27 -0700707 // use outside touchable to dismiss drop down when touching outside of it, so
708 // only set this if the dropdown is not always visible
709 mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
710 mPopup.setTouchInterceptor(mTouchInterceptor);
Alan Viverette91098572016-01-19 14:07:31 -0500711 mPopup.setEpicenterBounds(mEpicenterBounds);
Vladislav Kaznacheevadf08d42017-04-05 15:22:10 -0700712 if (mOverlapAnchorSet) {
713 mPopup.setOverlapAnchor(mOverlapAnchor);
714 }
Alan Viverette560f1702014-05-05 14:40:07 -0700715 mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
716 mDropDownVerticalOffset, mDropDownGravity);
Adam Powellc3fa6302010-05-18 11:36:27 -0700717 mDropDownList.setSelection(ListView.INVALID_POSITION);
Evan Roskyadf5bec2017-10-19 10:24:07 -0700718 mPopup.getContentView().restoreDefaultFocus();
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700719
Adam Powellc3fa6302010-05-18 11:36:27 -0700720 if (!mModal || mDropDownList.isInTouchMode()) {
721 clearListSelection();
722 }
723 if (!mModal) {
724 mHandler.post(mHideSelector);
725 }
726 }
727 }
728
729 /**
730 * Dismiss the popup window.
731 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700732 @Override
Adam Powellc3fa6302010-05-18 11:36:27 -0700733 public void dismiss() {
734 mPopup.dismiss();
735 removePromptView();
736 mPopup.setContentView(null);
737 mDropDownList = null;
Adam Powellca51e872011-02-14 19:54:29 -0800738 mHandler.removeCallbacks(mResizePopupRunnable);
Adam Powellc3fa6302010-05-18 11:36:27 -0700739 }
740
Adam Powell6c6f5752010-08-20 18:34:46 -0700741 /**
742 * Set a listener to receive a callback when the popup is dismissed.
743 *
744 * @param listener Listener that will be notified when the popup is dismissed.
745 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500746 public void setOnDismissListener(@Nullable PopupWindow.OnDismissListener listener) {
Adam Powell6c6f5752010-08-20 18:34:46 -0700747 mPopup.setOnDismissListener(listener);
748 }
749
Adam Powellc3fa6302010-05-18 11:36:27 -0700750 private void removePromptView() {
751 if (mPromptView != null) {
752 final ViewParent parent = mPromptView.getParent();
753 if (parent instanceof ViewGroup) {
754 final ViewGroup group = (ViewGroup) parent;
755 group.removeView(mPromptView);
756 }
757 }
758 }
759
760 /**
761 * Control how the popup operates with an input method: one of
762 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
763 * or {@link #INPUT_METHOD_NOT_NEEDED}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700764 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700765 * <p>If the popup is showing, calling this method will take effect only
766 * the next time the popup is shown or through a manual call to the {@link #show()}
767 * method.</p>
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700768 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700769 * @see #getInputMethodMode()
770 * @see #show()
771 */
772 public void setInputMethodMode(int mode) {
773 mPopup.setInputMethodMode(mode);
774 }
775
776 /**
777 * Return the current value in {@link #setInputMethodMode(int)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700778 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700779 * @see #setInputMethodMode(int)
780 */
781 public int getInputMethodMode() {
782 return mPopup.getInputMethodMode();
783 }
784
785 /**
786 * Set the selected position of the list.
787 * Only valid when {@link #isShowing()} == {@code true}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700788 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700789 * @param position List position to set as selected.
790 */
791 public void setSelection(int position) {
792 DropDownListView list = mDropDownList;
793 if (isShowing() && list != null) {
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700794 list.setListSelectionHidden(false);
Adam Powellc3fa6302010-05-18 11:36:27 -0700795 list.setSelection(position);
796 if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
797 list.setItemChecked(position, true);
798 }
799 }
800 }
801
802 /**
803 * Clear any current list selection.
804 * Only valid when {@link #isShowing()} == {@code true}.
805 */
806 public void clearListSelection() {
807 final DropDownListView list = mDropDownList;
808 if (list != null) {
809 // WARNING: Please read the comment where mListSelectionHidden is declared
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700810 list.setListSelectionHidden(true);
Adam Powellc3fa6302010-05-18 11:36:27 -0700811 list.hideSelector();
812 list.requestLayout();
813 }
814 }
815
816 /**
817 * @return {@code true} if the popup is currently showing, {@code false} otherwise.
818 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700819 @Override
Adam Powellc3fa6302010-05-18 11:36:27 -0700820 public boolean isShowing() {
821 return mPopup.isShowing();
822 }
823
824 /**
825 * @return {@code true} if this popup is configured to assume the user does not need
826 * to interact with the IME while it is showing, {@code false} otherwise.
827 */
828 public boolean isInputMethodNotNeeded() {
829 return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
830 }
831
832 /**
833 * Perform an item click operation on the specified list adapter position.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700834 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700835 * @param position Adapter position for performing the click
836 * @return true if the click action could be performed, false if not.
837 * (e.g. if the popup was not showing, this method would return false.)
838 */
839 public boolean performItemClick(int position) {
840 if (isShowing()) {
841 if (mItemClickListener != null) {
842 final DropDownListView list = mDropDownList;
843 final View child = list.getChildAt(position - list.getFirstVisiblePosition());
Adam Powellcdee4462010-09-02 17:13:24 -0700844 final ListAdapter adapter = list.getAdapter();
845 mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
Adam Powellc3fa6302010-05-18 11:36:27 -0700846 }
847 return true;
848 }
849 return false;
850 }
851
852 /**
853 * @return The currently selected item or null if the popup is not showing.
854 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500855 public @Nullable Object getSelectedItem() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700856 if (!isShowing()) {
857 return null;
858 }
859 return mDropDownList.getSelectedItem();
860 }
861
862 /**
863 * @return The position of the currently selected item or {@link ListView#INVALID_POSITION}
864 * if {@link #isShowing()} == {@code false}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700865 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700866 * @see ListView#getSelectedItemPosition()
867 */
868 public int getSelectedItemPosition() {
869 if (!isShowing()) {
870 return ListView.INVALID_POSITION;
871 }
872 return mDropDownList.getSelectedItemPosition();
873 }
874
875 /**
876 * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID}
877 * if {@link #isShowing()} == {@code false}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700878 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700879 * @see ListView#getSelectedItemId()
880 */
881 public long getSelectedItemId() {
882 if (!isShowing()) {
883 return ListView.INVALID_ROW_ID;
884 }
885 return mDropDownList.getSelectedItemId();
886 }
887
888 /**
889 * @return The View for the currently selected item or null if
890 * {@link #isShowing()} == {@code false}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700891 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700892 * @see ListView#getSelectedView()
893 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500894 public @Nullable View getSelectedView() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700895 if (!isShowing()) {
896 return null;
897 }
898 return mDropDownList.getSelectedView();
899 }
900
901 /**
902 * @return The {@link ListView} displayed within the popup window.
903 * Only valid when {@link #isShowing()} == {@code true}.
904 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700905 @Override
Alan Viverette02cd0f92016-01-13 13:33:17 -0500906 public @Nullable ListView getListView() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700907 return mDropDownList;
908 }
909
Alan Viverette02cd0f92016-01-13 13:33:17 -0500910 @NonNull DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
Jun Mukai31f581c2015-04-02 13:44:29 -0700911 return new DropDownListView(context, hijackFocus);
912 }
913
Adam Powellc3fa6302010-05-18 11:36:27 -0700914 /**
Adam Powell348e69c2011-02-16 16:49:50 -0800915 * The maximum number of list items that can be visible and still have
916 * the list expand when touched.
917 *
918 * @param max Max number of items that can be visible and still allow the list to expand.
919 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100920 @UnsupportedAppUsage
Adam Powell348e69c2011-02-16 16:49:50 -0800921 void setListItemExpandMax(int max) {
922 mListItemExpandMaximum = max;
923 }
924
925 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -0800926 * Filter key down events. By forwarding key down events to this function,
Adam Powellc3fa6302010-05-18 11:36:27 -0700927 * views using non-modal ListPopupWindow can have it handle key selection of items.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -0400928 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700929 * @param keyCode keyCode param passed to the host view's onKeyDown
930 * @param event event param passed to the host view's onKeyDown
931 * @return true if the event was handled, false if it was ignored.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -0400932 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700933 * @see #setModal(boolean)
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -0400934 * @see #onKeyUp(int, KeyEvent)
Adam Powellc3fa6302010-05-18 11:36:27 -0700935 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500936 public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700937 // when the drop down is shown, we drive it directly
938 if (isShowing()) {
939 // the key events are forwarded to the list in the drop down view
940 // note that ListView handles space but we don't want that to happen
941 // also if selection is not currently in the drop down, then don't
942 // let center or enter presses go there since that would cause it
943 // to select one of its items
944 if (keyCode != KeyEvent.KEYCODE_SPACE
945 && (mDropDownList.getSelectedItemPosition() >= 0
Michael Wright24d36f52013-07-19 15:55:14 -0700946 || !KeyEvent.isConfirmKey(keyCode))) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700947 int curIndex = mDropDownList.getSelectedItemPosition();
948 boolean consumed;
949
950 final boolean below = !mPopup.isAboveAnchor();
951
952 final ListAdapter adapter = mAdapter;
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700953
Adam Powellc3fa6302010-05-18 11:36:27 -0700954 boolean allEnabled;
955 int firstItem = Integer.MAX_VALUE;
956 int lastItem = Integer.MIN_VALUE;
957
958 if (adapter != null) {
959 allEnabled = adapter.areAllItemsEnabled();
960 firstItem = allEnabled ? 0 :
961 mDropDownList.lookForSelectablePosition(0, true);
962 lastItem = allEnabled ? adapter.getCount() - 1 :
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700963 mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
Adam Powellc3fa6302010-05-18 11:36:27 -0700964 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700965
Adam Powellc3fa6302010-05-18 11:36:27 -0700966 if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
967 (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
968 // When the selection is at the top, we block the key
969 // event to prevent focus from moving.
970 clearListSelection();
971 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
972 show();
973 return true;
974 } else {
975 // WARNING: Please read the comment where mListSelectionHidden
976 // is declared
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700977 mDropDownList.setListSelectionHidden(false);
Adam Powellc3fa6302010-05-18 11:36:27 -0700978 }
979
980 consumed = mDropDownList.onKeyDown(keyCode, event);
981 if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
982
983 if (consumed) {
984 // If it handled the key event, then the user is
985 // navigating in the list, so we should put it in front.
986 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
987 // Here's a little trick we need to do to make sure that
988 // the list view is actually showing its focus indicator,
989 // by ensuring it has focus and getting its window out
990 // of touch mode.
991 mDropDownList.requestFocusFromTouch();
992 show();
993
994 switch (keyCode) {
995 // avoid passing the focus from the text view to the
996 // next component
997 case KeyEvent.KEYCODE_ENTER:
998 case KeyEvent.KEYCODE_DPAD_CENTER:
999 case KeyEvent.KEYCODE_DPAD_DOWN:
1000 case KeyEvent.KEYCODE_DPAD_UP:
1001 return true;
1002 }
1003 } else {
1004 if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
1005 // when the selection is at the bottom, we block the
1006 // event to avoid going to the next focusable widget
1007 if (curIndex == lastItem) {
1008 return true;
1009 }
1010 } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
1011 curIndex == firstItem) {
1012 return true;
1013 }
1014 }
1015 }
1016 }
1017
1018 return false;
1019 }
1020
1021 /**
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001022 * Filter key up events. By forwarding key up events to this function,
Adam Powellc3fa6302010-05-18 11:36:27 -07001023 * views using non-modal ListPopupWindow can have it handle key selection of items.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001024 *
Adam Powellc3fa6302010-05-18 11:36:27 -07001025 * @param keyCode keyCode param passed to the host view's onKeyUp
1026 * @param event event param passed to the host view's onKeyUp
1027 * @return true if the event was handled, false if it was ignored.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001028 *
Adam Powellc3fa6302010-05-18 11:36:27 -07001029 * @see #setModal(boolean)
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001030 * @see #onKeyDown(int, KeyEvent)
Adam Powellc3fa6302010-05-18 11:36:27 -07001031 */
Alan Viverette02cd0f92016-01-13 13:33:17 -05001032 public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
Adam Powellc3fa6302010-05-18 11:36:27 -07001033 if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
1034 boolean consumed = mDropDownList.onKeyUp(keyCode, event);
Michael Wright24d36f52013-07-19 15:55:14 -07001035 if (consumed && KeyEvent.isConfirmKey(keyCode)) {
1036 // if the list accepts the key events and the key event was a click, the text view
1037 // gets the selected item from the drop down as its content
1038 dismiss();
Adam Powellc3fa6302010-05-18 11:36:27 -07001039 }
1040 return consumed;
1041 }
1042 return false;
1043 }
1044
1045 /**
1046 * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)}
1047 * events to this function, views using ListPopupWindow can have it dismiss the popup
1048 * when the back key is pressed.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001049 *
Adam Powellc3fa6302010-05-18 11:36:27 -07001050 * @param keyCode keyCode param passed to the host view's onKeyPreIme
1051 * @param event event param passed to the host view's onKeyPreIme
1052 * @return true if the event was handled, false if it was ignored.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001053 *
Adam Powellc3fa6302010-05-18 11:36:27 -07001054 * @see #setModal(boolean)
1055 */
Alan Viverette02cd0f92016-01-13 13:33:17 -05001056 public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
Adam Powellc3fa6302010-05-18 11:36:27 -07001057 if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
1058 // special case for the back key, we do not even try to send it
1059 // to the drop down list but instead, consume it immediately
1060 final View anchorView = mDropDownAnchorView;
1061 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08001062 KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
1063 if (state != null) {
1064 state.startTracking(event, this);
1065 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001066 return true;
1067 } else if (event.getAction() == KeyEvent.ACTION_UP) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08001068 KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
1069 if (state != null) {
1070 state.handleUpEvent(event);
1071 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001072 if (event.isTracking() && !event.isCanceled()) {
1073 dismiss();
1074 return true;
1075 }
1076 }
1077 }
1078 return false;
1079 }
1080
1081 /**
Alan Viverette1955a5b52013-08-27 15:45:16 -07001082 * Returns an {@link OnTouchListener} that can be added to the source view
1083 * to implement drag-to-open behavior. Generally, the source view should be
1084 * the same view that was passed to {@link #setAnchorView}.
1085 * <p>
1086 * When the listener is set on a view, touching that view and dragging
1087 * outside of its bounds will open the popup window. Lifting will select the
1088 * currently touched list item.
1089 * <p>
1090 * Example usage:
Alan Viverette3f9832d2013-08-30 14:43:25 -07001091 * <pre>
1092 * ListPopupWindow myPopup = new ListPopupWindow(context);
Alan Viverette1955a5b52013-08-27 15:45:16 -07001093 * myPopup.setAnchor(myAnchor);
1094 * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
Alan Viverette3f9832d2013-08-30 14:43:25 -07001095 * myAnchor.setOnTouchListener(dragListener);
1096 * </pre>
Alan Viverette1955a5b52013-08-27 15:45:16 -07001097 *
1098 * @param src the view on which the resulting listener will be set
1099 * @return a touch listener that controls drag-to-open behavior
1100 */
1101 public OnTouchListener createDragToOpenListener(View src) {
1102 return new ForwardingListener(src) {
1103 @Override
Oren Blasbergf44d90b2015-08-31 14:15:26 -07001104 public ShowableListMenu getPopup() {
Alan Viverette1955a5b52013-08-27 15:45:16 -07001105 return ListPopupWindow.this;
1106 }
1107 };
1108 }
1109
1110 /**
Adam Powellc3fa6302010-05-18 11:36:27 -07001111 * <p>Builds the popup window's content and returns the height the popup
1112 * should have. Returns -1 when the content already exists.</p>
1113 *
1114 * @return the content's height or -1 if content already exists
1115 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01001116 @UnsupportedAppUsage
Adam Powellc3fa6302010-05-18 11:36:27 -07001117 private int buildDropDown() {
1118 ViewGroup dropDownView;
1119 int otherHeights = 0;
1120
1121 if (mDropDownList == null) {
1122 Context context = mContext;
1123
1124 /**
1125 * This Runnable exists for the sole purpose of checking if the view layout has got
1126 * completed and if so call showDropDown to display the drop down. This is used to show
1127 * the drop down as soon as possible after user opens up the search dialog, without
1128 * waiting for the normal UI pipeline to do it's job which is slower than this method.
1129 */
1130 mShowDropDownRunnable = new Runnable() {
1131 public void run() {
1132 // View layout should be all done before displaying the drop down.
1133 View view = getAnchorView();
1134 if (view != null && view.getWindowToken() != null) {
1135 show();
1136 }
1137 }
1138 };
1139
Jun Mukai31f581c2015-04-02 13:44:29 -07001140 mDropDownList = createDropDownListView(context, !mModal);
Adam Powellc3fa6302010-05-18 11:36:27 -07001141 if (mDropDownListHighlight != null) {
1142 mDropDownList.setSelector(mDropDownListHighlight);
1143 }
1144 mDropDownList.setAdapter(mAdapter);
Adam Powellc3fa6302010-05-18 11:36:27 -07001145 mDropDownList.setOnItemClickListener(mItemClickListener);
1146 mDropDownList.setFocusable(true);
1147 mDropDownList.setFocusableInTouchMode(true);
1148 mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
1149 public void onItemSelected(AdapterView<?> parent, View view,
1150 int position, long id) {
1151
1152 if (position != -1) {
1153 DropDownListView dropDownList = mDropDownList;
1154
1155 if (dropDownList != null) {
Oren Blasbergf44d90b2015-08-31 14:15:26 -07001156 dropDownList.setListSelectionHidden(false);
Adam Powellc3fa6302010-05-18 11:36:27 -07001157 }
1158 }
1159 }
1160
1161 public void onNothingSelected(AdapterView<?> parent) {
1162 }
1163 });
1164 mDropDownList.setOnScrollListener(mScrollListener);
1165
1166 if (mItemSelectedListener != null) {
1167 mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
1168 }
1169
1170 dropDownView = mDropDownList;
1171
1172 View hintView = mPromptView;
1173 if (hintView != null) {
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001174 // if a hint has been specified, we accomodate more space for it and
Adam Powellc3fa6302010-05-18 11:36:27 -07001175 // add a text view in the drop down menu, at the bottom of the list
1176 LinearLayout hintContainer = new LinearLayout(context);
1177 hintContainer.setOrientation(LinearLayout.VERTICAL);
1178
1179 LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
1180 ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
1181 );
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001182
Adam Powellc3fa6302010-05-18 11:36:27 -07001183 switch (mPromptPosition) {
1184 case POSITION_PROMPT_BELOW:
1185 hintContainer.addView(dropDownView, hintParams);
1186 hintContainer.addView(hintView);
1187 break;
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001188
Adam Powellc3fa6302010-05-18 11:36:27 -07001189 case POSITION_PROMPT_ABOVE:
1190 hintContainer.addView(hintView);
1191 hintContainer.addView(dropDownView, hintParams);
1192 break;
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001193
Adam Powellc3fa6302010-05-18 11:36:27 -07001194 default:
1195 Log.e(TAG, "Invalid hint position " + mPromptPosition);
1196 break;
1197 }
1198
Alan Viveretteba4332d2015-06-25 13:00:03 -07001199 // Measure the hint's height to find how much more vertical
1200 // space we need to add to the drop down's height.
1201 final int widthSize;
1202 final int widthMode;
1203 if (mDropDownWidth >= 0) {
1204 widthMode = MeasureSpec.AT_MOST;
1205 widthSize = mDropDownWidth;
1206 } else {
1207 widthMode = MeasureSpec.UNSPECIFIED;
1208 widthSize = 0;
1209 }
1210 final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
1211 final int heightSpec = MeasureSpec.UNSPECIFIED;
Adam Powellc3fa6302010-05-18 11:36:27 -07001212 hintView.measure(widthSpec, heightSpec);
1213
1214 hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
1215 otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
1216 + hintParams.bottomMargin;
1217
1218 dropDownView = hintContainer;
1219 }
1220
1221 mPopup.setContentView(dropDownView);
1222 } else {
Adam Powellc3fa6302010-05-18 11:36:27 -07001223 final View view = mPromptView;
1224 if (view != null) {
1225 LinearLayout.LayoutParams hintParams =
1226 (LinearLayout.LayoutParams) view.getLayoutParams();
1227 otherHeights = view.getMeasuredHeight() + hintParams.topMargin
1228 + hintParams.bottomMargin;
1229 }
1230 }
1231
Adam Powell8132ba52011-07-15 17:37:11 -07001232 // getMaxAvailableHeight() subtracts the padding, so we put it back
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001233 // to get the available height for the whole window.
1234 final int padding;
1235 final Drawable background = mPopup.getBackground();
Adam Powellc3fa6302010-05-18 11:36:27 -07001236 if (background != null) {
1237 background.getPadding(mTempRect);
1238 padding = mTempRect.top + mTempRect.bottom;
Adam Powell8132ba52011-07-15 17:37:11 -07001239
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001240 // If we don't have an explicit vertical offset, determine one from
1241 // the window background so that content will line up.
Adam Powell8132ba52011-07-15 17:37:11 -07001242 if (!mDropDownVerticalOffsetSet) {
1243 mDropDownVerticalOffset = -mTempRect.top;
1244 }
Adam Powell7507d3d2012-03-08 12:01:16 -08001245 } else {
1246 mTempRect.setEmpty();
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001247 padding = 0;
Adam Powellc3fa6302010-05-18 11:36:27 -07001248 }
1249
Adam Powell8132ba52011-07-15 17:37:11 -07001250 // Max height available on the screen for a popup.
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001251 final boolean ignoreBottomDecorations =
Adam Powell8132ba52011-07-15 17:37:11 -07001252 mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
1253 final int maxHeight = mPopup.getMaxAvailableHeight(
1254 getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
Adam Powellc3fa6302010-05-18 11:36:27 -07001255 if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
1256 return maxHeight + padding;
1257 }
1258
Adam Powell7507d3d2012-03-08 12:01:16 -08001259 final int childWidthSpec;
1260 switch (mDropDownWidth) {
1261 case ViewGroup.LayoutParams.WRAP_CONTENT:
1262 childWidthSpec = MeasureSpec.makeMeasureSpec(
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001263 mContext.getResources().getDisplayMetrics().widthPixels
1264 - (mTempRect.left + mTempRect.right),
Adam Powell7507d3d2012-03-08 12:01:16 -08001265 MeasureSpec.AT_MOST);
1266 break;
1267 case ViewGroup.LayoutParams.MATCH_PARENT:
1268 childWidthSpec = MeasureSpec.makeMeasureSpec(
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001269 mContext.getResources().getDisplayMetrics().widthPixels
1270 - (mTempRect.left + mTempRect.right),
Adam Powell7507d3d2012-03-08 12:01:16 -08001271 MeasureSpec.EXACTLY);
1272 break;
1273 default:
1274 childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
1275 break;
1276 }
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001277
1278 // Add padding only if the list has items in it, that way we don't show
1279 // the popup if it is not needed.
Adam Powell7507d3d2012-03-08 12:01:16 -08001280 final int listContent = mDropDownList.measureHeightOfChildren(childWidthSpec,
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001281 0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1);
1282 if (listContent > 0) {
1283 final int listPadding = mDropDownList.getPaddingTop()
1284 + mDropDownList.getPaddingBottom();
1285 otherHeights += padding + listPadding;
1286 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001287
1288 return listContent + otherHeights;
1289 }
1290
Vladislav Kaznacheevb40e61b2017-03-07 11:03:35 -08001291 /**
1292 * @hide
1293 */
1294 public void setOverlapAnchor(boolean overlap) {
Vladislav Kaznacheevadf08d42017-04-05 15:22:10 -07001295 mOverlapAnchorSet = true;
Vladislav Kaznacheevb40e61b2017-03-07 11:03:35 -08001296 mOverlapAnchor = overlap;
1297 }
1298
Adam Powellc3fa6302010-05-18 11:36:27 -07001299 private class PopupDataSetObserver extends DataSetObserver {
1300 @Override
1301 public void onChanged() {
1302 if (isShowing()) {
1303 // Resize the popup to fit new content
1304 show();
1305 }
1306 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001307
Adam Powellc3fa6302010-05-18 11:36:27 -07001308 @Override
1309 public void onInvalidated() {
1310 dismiss();
1311 }
1312 }
1313
1314 private class ListSelectorHider implements Runnable {
1315 public void run() {
1316 clearListSelection();
1317 }
1318 }
1319
1320 private class ResizePopupRunnable implements Runnable {
1321 public void run() {
Alan Viverettec8bfc682015-05-07 13:00:25 -07001322 if (mDropDownList != null && mDropDownList.isAttachedToWindow()
1323 && mDropDownList.getCount() > mDropDownList.getChildCount()
1324 && mDropDownList.getChildCount() <= mListItemExpandMaximum) {
Adam Powell348e69c2011-02-16 16:49:50 -08001325 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1326 show();
1327 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001328 }
1329 }
1330
1331 private class PopupTouchInterceptor implements OnTouchListener {
1332 public boolean onTouch(View v, MotionEvent event) {
1333 final int action = event.getAction();
1334 final int x = (int) event.getX();
1335 final int y = (int) event.getY();
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001336
Adam Powellc3fa6302010-05-18 11:36:27 -07001337 if (action == MotionEvent.ACTION_DOWN &&
1338 mPopup != null && mPopup.isShowing() &&
Gilles Debunne711734a2011-02-07 18:26:11 -08001339 (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
Adam Powellc3fa6302010-05-18 11:36:27 -07001340 mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
1341 } else if (action == MotionEvent.ACTION_UP) {
1342 mHandler.removeCallbacks(mResizePopupRunnable);
1343 }
1344 return false;
1345 }
1346 }
1347
1348 private class PopupScrollListener implements ListView.OnScrollListener {
1349 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1350 int totalItemCount) {
1351
1352 }
1353
1354 public void onScrollStateChanged(AbsListView view, int scrollState) {
1355 if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
1356 !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
1357 mHandler.removeCallbacks(mResizePopupRunnable);
1358 mResizePopupRunnable.run();
1359 }
1360 }
1361 }
1362}