blob: adf366a49c8c0aee407f5b510ba6cc6dd8401534 [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;
Adam Powellc3fa6302010-05-18 11:36:27 -070023import android.content.Context;
Alan Viverettef023c252014-08-28 13:55:18 -070024import android.content.res.TypedArray;
Adam Powellc3fa6302010-05-18 11:36:27 -070025import android.database.DataSetObserver;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
Robert Carrc1fdd2a2017-06-21 10:25:05 -070028import android.os.Build;
Adam Powellc3fa6302010-05-18 11:36:27 -070029import android.os.Handler;
30import android.util.AttributeSet;
31import android.util.Log;
Adam Powell54c94de2013-09-26 15:36:34 -070032import android.view.Gravity;
Adam Powellc3fa6302010-05-18 11:36:27 -070033import android.view.KeyEvent;
34import android.view.MotionEvent;
35import android.view.View;
Adam Powellc3fa6302010-05-18 11:36:27 -070036import android.view.View.MeasureSpec;
37import android.view.View.OnTouchListener;
Gilles Debunne711734a2011-02-07 18:26:11 -080038import android.view.ViewGroup;
39import android.view.ViewParent;
Alan Viverette80ebe0d2015-04-30 15:53:11 -070040import android.view.WindowManager;
Alan Viverette02cd0f92016-01-13 13:33:17 -050041import android.widget.AdapterView.OnItemSelectedListener;
Fabrice Di Meglio1d3d7da2012-07-27 15:15:04 -070042
Aurimas Liutikas99441c52016-10-11 16:48:32 -070043import com.android.internal.R;
44import com.android.internal.view.menu.ShowableListMenu;
45
Adam Powellc3fa6302010-05-18 11:36:27 -070046/**
47 * A ListPopupWindow anchors itself to a host view and displays a
Adam Powell65d79fb2010-08-11 22:05:46 -070048 * list of choices.
Aurimas Liutikas99441c52016-10-11 16:48:32 -070049 *
Adam Powellc3fa6302010-05-18 11:36:27 -070050 * <p>ListPopupWindow contains a number of tricky behaviors surrounding
51 * positioning, scrolling parents to fit the dropdown, interacting
52 * sanely with the IME if present, and others.
Aurimas Liutikas99441c52016-10-11 16:48:32 -070053 *
Adam Powellc3fa6302010-05-18 11:36:27 -070054 * @see android.widget.AutoCompleteTextView
55 * @see android.widget.Spinner
56 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -070057public class ListPopupWindow implements ShowableListMenu {
Adam Powellc3fa6302010-05-18 11:36:27 -070058 private static final String TAG = "ListPopupWindow";
59 private static final boolean DEBUG = false;
60
61 /**
62 * This value controls the length of time that the user
63 * must leave a pointer down without scrolling to expand
64 * the autocomplete dropdown list to cover the IME.
65 */
66 private static final int EXPAND_LIST_TIMEOUT = 250;
67
68 private Context mContext;
Adam Powellc3fa6302010-05-18 11:36:27 -070069 private ListAdapter mAdapter;
70 private DropDownListView mDropDownList;
71
72 private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
73 private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
74 private int mDropDownHorizontalOffset;
75 private int mDropDownVerticalOffset;
Alan Viverette80ebe0d2015-04-30 15:53:11 -070076 private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
Adam Powell8132ba52011-07-15 17:37:11 -070077 private boolean mDropDownVerticalOffsetSet;
Alan Viverette91098572016-01-19 14:07:31 -050078 private boolean mIsAnimatedFromAnchor = true;
Vladislav Kaznacheevb40e61b2017-03-07 11:03:35 -080079 private boolean mOverlapAnchor;
Vladislav Kaznacheevadf08d42017-04-05 15:22:10 -070080 private boolean mOverlapAnchorSet;
Adam Powellc3fa6302010-05-18 11:36:27 -070081
Adam Powell54c94de2013-09-26 15:36:34 -070082 private int mDropDownGravity = Gravity.NO_GRAVITY;
83
Adam Powellc3fa6302010-05-18 11:36:27 -070084 private boolean mDropDownAlwaysVisible = false;
85 private boolean mForceIgnoreOutsideTouch = false;
Adam Powell348e69c2011-02-16 16:49:50 -080086 int mListItemExpandMaximum = Integer.MAX_VALUE;
Adam Powellc3fa6302010-05-18 11:36:27 -070087
88 private View mPromptView;
89 private int mPromptPosition = POSITION_PROMPT_ABOVE;
90
91 private DataSetObserver mObserver;
92
93 private View mDropDownAnchorView;
94
95 private Drawable mDropDownListHighlight;
96
97 private AdapterView.OnItemClickListener mItemClickListener;
98 private AdapterView.OnItemSelectedListener mItemSelectedListener;
99
Adam Powell42675342010-07-09 18:02:59 -0700100 private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
Adam Powellc3fa6302010-05-18 11:36:27 -0700101 private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
102 private final PopupScrollListener mScrollListener = new PopupScrollListener();
103 private final ListSelectorHider mHideSelector = new ListSelectorHider();
104 private Runnable mShowDropDownRunnable;
105
Alan Viverette4a507232015-06-18 17:11:19 -0700106 private final Handler mHandler;
Adam Powellc3fa6302010-05-18 11:36:27 -0700107
Alan Viverette91098572016-01-19 14:07:31 -0500108 private final Rect mTempRect = new Rect();
109
110 /**
111 * Optional anchor-relative bounds to be used as the transition epicenter.
112 * When {@code null}, the anchor bounds are used as the epicenter.
113 */
114 private Rect mEpicenterBounds;
Adam Powellc3fa6302010-05-18 11:36:27 -0700115
116 private boolean mModal;
117
Oren Blasberg8e12f8d2015-09-02 14:25:56 -0700118 PopupWindow mPopup;
119
Adam Powellc3fa6302010-05-18 11:36:27 -0700120 /**
121 * The provided prompt view should appear above list content.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700122 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700123 * @see #setPromptPosition(int)
124 * @see #getPromptPosition()
125 * @see #setPromptView(View)
126 */
127 public static final int POSITION_PROMPT_ABOVE = 0;
128
129 /**
130 * The provided prompt view should appear below list content.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700131 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700132 * @see #setPromptPosition(int)
133 * @see #getPromptPosition()
134 * @see #setPromptView(View)
135 */
136 public static final int POSITION_PROMPT_BELOW = 1;
137
138 /**
139 * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}.
140 * If used to specify a popup width, the popup will match the width of the anchor view.
141 * If used to specify a popup height, the popup will fill available space.
142 */
143 public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700144
Adam Powellc3fa6302010-05-18 11:36:27 -0700145 /**
146 * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
147 * If used to specify a popup width, the popup will use the width of its content.
148 */
149 public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700150
Adam Powellc3fa6302010-05-18 11:36:27 -0700151 /**
152 * Mode for {@link #setInputMethodMode(int)}: the requirements for the
153 * input method should be based on the focusability of the popup. That is
154 * if it is focusable than it needs to work with the input method, else
155 * it doesn't.
156 */
157 public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700158
Adam Powellc3fa6302010-05-18 11:36:27 -0700159 /**
160 * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
161 * work with an input method, regardless of whether it is focusable. This
162 * means that it will always be displayed so that the user can also operate
163 * the input method while it is shown.
164 */
165 public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700166
Adam Powellc3fa6302010-05-18 11:36:27 -0700167 /**
168 * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
169 * work with an input method, regardless of whether it is focusable. This
170 * means that it will always be displayed to use as much space on the
171 * screen as needed, regardless of whether this covers the input method.
172 */
173 public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED;
174
175 /**
176 * Create a new, empty popup window capable of displaying items from a ListAdapter.
177 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700178 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700179 * @param context Context used for contained views.
180 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500181 public ListPopupWindow(@NonNull Context context) {
Daniel Lehmannc2238d02010-10-21 11:52:55 -0700182 this(context, null, com.android.internal.R.attr.listPopupWindowStyle, 0);
Adam Powellc3fa6302010-05-18 11:36:27 -0700183 }
184
185 /**
186 * Create a new, empty popup window capable of displaying items from a ListAdapter.
187 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700188 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700189 * @param context Context used for contained views.
190 * @param attrs Attributes from inflating parent views used to style the popup.
191 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500192 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) {
Adam Powell0b2d3062010-09-14 16:15:02 -0700193 this(context, attrs, com.android.internal.R.attr.listPopupWindowStyle, 0);
Adam Powellc3fa6302010-05-18 11:36:27 -0700194 }
195
196 /**
197 * Create a new, empty popup window capable of displaying items from a ListAdapter.
198 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700199 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700200 * @param context Context used for contained views.
201 * @param attrs Attributes from inflating parent views used to style the popup.
202 * @param defStyleAttr Default style attribute to use for popup content.
203 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500204 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
205 @AttrRes int defStyleAttr) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700206 this(context, attrs, defStyleAttr, 0);
207 }
208
209 /**
210 * Create a new, empty popup window capable of displaying items from a ListAdapter.
211 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700212 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700213 * @param context Context used for contained views.
214 * @param attrs Attributes from inflating parent views used to style the popup.
215 * @param defStyleAttr Style attribute to read for default styling of popup content.
216 * @param defStyleRes Style resource ID to use for default styling of popup content.
217 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500218 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
219 @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700220 mContext = context;
Alan Viverette4a507232015-06-18 17:11:19 -0700221 mHandler = new Handler(context.getMainLooper());
Alan Viverettef023c252014-08-28 13:55:18 -0700222
223 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow,
224 defStyleAttr, defStyleRes);
225 mDropDownHorizontalOffset = a.getDimensionPixelOffset(
226 R.styleable.ListPopupWindow_dropDownHorizontalOffset, 0);
227 mDropDownVerticalOffset = a.getDimensionPixelOffset(
228 R.styleable.ListPopupWindow_dropDownVerticalOffset, 0);
229 if (mDropDownVerticalOffset != 0) {
230 mDropDownVerticalOffsetSet = true;
231 }
232 a.recycle();
233
Adam Powellc3fa6302010-05-18 11:36:27 -0700234 mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes);
Adam Powell6f5e9342011-01-27 13:30:55 -0800235 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
Adam Powellc3fa6302010-05-18 11:36:27 -0700236 }
237
238 /**
239 * Sets the adapter that provides the data and the views to represent the data
240 * in this popup window.
241 *
242 * @param adapter The adapter to use to create this window's content.
243 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500244 public void setAdapter(@Nullable ListAdapter adapter) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700245 if (mObserver == null) {
246 mObserver = new PopupDataSetObserver();
247 } else if (mAdapter != null) {
Adam Powell99969da2010-06-16 10:51:30 -0700248 mAdapter.unregisterDataSetObserver(mObserver);
Adam Powellc3fa6302010-05-18 11:36:27 -0700249 }
250 mAdapter = adapter;
251 if (mAdapter != null) {
252 adapter.registerDataSetObserver(mObserver);
253 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700254
Adam Powellc3fa6302010-05-18 11:36:27 -0700255 if (mDropDownList != null) {
256 mDropDownList.setAdapter(mAdapter);
257 }
258 }
259
260 /**
261 * Set where the optional prompt view should appear. The default is
262 * {@link #POSITION_PROMPT_ABOVE}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700263 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700264 * @param position A position constant declaring where the prompt should be displayed.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700265 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700266 * @see #POSITION_PROMPT_ABOVE
267 * @see #POSITION_PROMPT_BELOW
268 */
269 public void setPromptPosition(int position) {
270 mPromptPosition = position;
271 }
272
273 /**
274 * @return Where the optional prompt view should appear.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700275 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700276 * @see #POSITION_PROMPT_ABOVE
277 * @see #POSITION_PROMPT_BELOW
278 */
279 public int getPromptPosition() {
280 return mPromptPosition;
281 }
282
283 /**
284 * Set whether this window should be modal when shown.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700285 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700286 * <p>If a popup window is modal, it will receive all touch and key input.
287 * If the user touches outside the popup window's content area the popup window
288 * will be dismissed.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700289 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700290 * @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
291 */
292 public void setModal(boolean modal) {
Chet Haasee04499a2014-08-05 15:24:43 -0700293 mModal = modal;
Adam Powellc3fa6302010-05-18 11:36:27 -0700294 mPopup.setFocusable(modal);
295 }
296
297 /**
298 * Returns whether the popup window will be modal when shown.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700299 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700300 * @return {@code true} if the popup window will be modal, {@code false} otherwise.
301 */
302 public boolean isModal() {
303 return mModal;
304 }
305
306 /**
307 * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
308 * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
309 * ignore outside touch even when the drop down is not set to always visible.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700310 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700311 * @hide Used only by AutoCompleteTextView to handle some internal special cases.
312 */
313 public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
314 mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
315 }
316
317 /**
318 * Sets whether the drop-down should remain visible under certain conditions.
319 *
320 * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless
321 * of the size or content of the list. {@link #getBackground()} will fill any space
322 * that is not used by the list.
323 *
324 * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
325 *
326 * @hide Only used by AutoCompleteTextView under special conditions.
327 */
328 public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
329 mDropDownAlwaysVisible = dropDownAlwaysVisible;
330 }
331
332 /**
333 * @return Whether the drop-down is visible under special conditions.
334 *
335 * @hide Only used by AutoCompleteTextView under special conditions.
336 */
337 public boolean isDropDownAlwaysVisible() {
338 return mDropDownAlwaysVisible;
339 }
340
341 /**
342 * Sets the operating mode for the soft input area.
343 *
344 * @param mode The desired mode, see
345 * {@link android.view.WindowManager.LayoutParams#softInputMode}
346 * for the full list
347 *
348 * @see android.view.WindowManager.LayoutParams#softInputMode
349 * @see #getSoftInputMode()
350 */
351 public void setSoftInputMode(int mode) {
352 mPopup.setSoftInputMode(mode);
353 }
354
355 /**
356 * Returns the current value in {@link #setSoftInputMode(int)}.
357 *
358 * @see #setSoftInputMode(int)
359 * @see android.view.WindowManager.LayoutParams#softInputMode
360 */
361 public int getSoftInputMode() {
362 return mPopup.getSoftInputMode();
363 }
364
365 /**
366 * Sets a drawable to use as the list item selector.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700367 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700368 * @param selector List selector drawable to use in the popup.
369 */
370 public void setListSelector(Drawable selector) {
371 mDropDownListHighlight = selector;
372 }
373
374 /**
375 * @return The background drawable for the popup window.
376 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500377 public @Nullable Drawable getBackground() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700378 return mPopup.getBackground();
379 }
380
381 /**
382 * Sets a drawable to be the background for the popup window.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700383 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700384 * @param d A drawable to set as the background.
385 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500386 public void setBackgroundDrawable(@Nullable Drawable d) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700387 mPopup.setBackgroundDrawable(d);
388 }
389
390 /**
391 * Set an animation style to use when the popup window is shown or dismissed.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700392 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700393 * @param animationStyle Animation style to use.
394 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500395 public void setAnimationStyle(@StyleRes int animationStyle) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700396 mPopup.setAnimationStyle(animationStyle);
397 }
398
399 /**
400 * Returns the animation style that will be used when the popup window is
401 * shown or dismissed.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700402 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700403 * @return Animation style that will be used.
404 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500405 public @StyleRes int getAnimationStyle() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700406 return mPopup.getAnimationStyle();
407 }
408
409 /**
410 * Returns the view that will be used to anchor this popup.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700411 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700412 * @return The popup's anchor view
413 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500414 public @Nullable View getAnchorView() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700415 return mDropDownAnchorView;
416 }
417
418 /**
419 * Sets the popup's anchor view. This popup will always be positioned relative to
420 * the anchor view when shown.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700421 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700422 * @param anchor The view to use as an anchor.
423 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500424 public void setAnchorView(@Nullable View anchor) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700425 mDropDownAnchorView = anchor;
426 }
427
428 /**
429 * @return The horizontal offset of the popup from its anchor in pixels.
430 */
431 public int getHorizontalOffset() {
432 return mDropDownHorizontalOffset;
433 }
434
435 /**
436 * Set the horizontal offset of this popup from its anchor view in pixels.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700437 *
Adam Powella984b382010-06-04 14:20:10 -0700438 * @param offset The horizontal offset of the popup from its anchor.
Adam Powellc3fa6302010-05-18 11:36:27 -0700439 */
440 public void setHorizontalOffset(int offset) {
441 mDropDownHorizontalOffset = offset;
442 }
443
444 /**
445 * @return The vertical offset of the popup from its anchor in pixels.
446 */
447 public int getVerticalOffset() {
Adam Powell8132ba52011-07-15 17:37:11 -0700448 if (!mDropDownVerticalOffsetSet) {
449 return 0;
450 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700451 return mDropDownVerticalOffset;
452 }
453
454 /**
455 * Set the vertical offset of this popup from its anchor view in pixels.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700456 *
Adam Powella984b382010-06-04 14:20:10 -0700457 * @param offset The vertical offset of the popup from its anchor.
Adam Powellc3fa6302010-05-18 11:36:27 -0700458 */
459 public void setVerticalOffset(int offset) {
460 mDropDownVerticalOffset = offset;
Adam Powell8132ba52011-07-15 17:37:11 -0700461 mDropDownVerticalOffsetSet = true;
Adam Powellc3fa6302010-05-18 11:36:27 -0700462 }
463
464 /**
Alan Viverette91098572016-01-19 14:07:31 -0500465 * Specifies the anchor-relative bounds of the popup's transition
466 * epicenter.
467 *
468 * @param bounds anchor-relative bounds
469 * @hide
470 */
471 public void setEpicenterBounds(Rect bounds) {
472 mEpicenterBounds = bounds;
473 }
474
475 /**
Adam Powell54c94de2013-09-26 15:36:34 -0700476 * Set the gravity of the dropdown list. This is commonly used to
477 * set gravity to START or END for alignment with the anchor.
478 *
479 * @param gravity Gravity value to use
480 */
481 public void setDropDownGravity(int gravity) {
482 mDropDownGravity = gravity;
483 }
484
485 /**
Adam Powellc3fa6302010-05-18 11:36:27 -0700486 * @return The width of the popup window in pixels.
487 */
488 public int getWidth() {
489 return mDropDownWidth;
490 }
491
492 /**
493 * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT}
494 * or {@link #WRAP_CONTENT}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700495 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700496 * @param width Width of the popup window.
497 */
498 public void setWidth(int width) {
499 mDropDownWidth = width;
500 }
501
502 /**
Adam Powell42675342010-07-09 18:02:59 -0700503 * Sets the width of the popup window by the size of its content. The final width may be
504 * larger to accommodate styled window dressing.
505 *
506 * @param width Desired width of content in pixels.
507 */
508 public void setContentWidth(int width) {
509 Drawable popupBackground = mPopup.getBackground();
510 if (popupBackground != null) {
Adam Powella39b9872011-01-05 16:07:54 -0800511 popupBackground.getPadding(mTempRect);
512 mDropDownWidth = mTempRect.left + mTempRect.right + width;
Adam Powell62e2bde2011-08-15 15:50:05 -0700513 } else {
514 setWidth(width);
Adam Powell42675342010-07-09 18:02:59 -0700515 }
516 }
517
518 /**
Adam Powellc3fa6302010-05-18 11:36:27 -0700519 * @return The height of the popup window in pixels.
520 */
521 public int getHeight() {
522 return mDropDownHeight;
523 }
524
525 /**
526 * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700527 *
Shunta Satoec2952b2016-11-04 18:57:39 +0900528 * @param height Height of the popup window must be a positive value,
529 * {@link #MATCH_PARENT}, or {@link #WRAP_CONTENT}.
530 *
531 * @throws IllegalArgumentException if height is set to negative value
Adam Powellc3fa6302010-05-18 11:36:27 -0700532 */
533 public void setHeight(int height) {
Shunta Satoec2952b2016-11-04 18:57:39 +0900534 if (height < 0 && ViewGroup.LayoutParams.WRAP_CONTENT != height
535 && ViewGroup.LayoutParams.MATCH_PARENT != height) {
Robert Carrc1fdd2a2017-06-21 10:25:05 -0700536 if (mContext.getApplicationInfo().targetSdkVersion
537 < Build.VERSION_CODES.O) {
538 Log.e(TAG, "Negative value " + height + " passed to ListPopupWindow#setHeight"
539 + " produces undefined results");
540 } else {
541 throw new IllegalArgumentException(
542 "Invalid height. Must be a positive value, MATCH_PARENT, or WRAP_CONTENT.");
543 }
Shunta Satoec2952b2016-11-04 18:57:39 +0900544 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700545 mDropDownHeight = height;
546 }
547
548 /**
Alan Viverette80ebe0d2015-04-30 15:53:11 -0700549 * Set the layout type for this popup window.
550 * <p>
551 * See {@link WindowManager.LayoutParams#type} for possible values.
552 *
553 * @param layoutType Layout type for this window.
554 *
555 * @see WindowManager.LayoutParams#type
556 */
557 public void setWindowLayoutType(int layoutType) {
558 mDropDownWindowLayoutType = layoutType;
559 }
560
561 /**
Adam Powellc3fa6302010-05-18 11:36:27 -0700562 * Sets a listener to receive events when a list item is clicked.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700563 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700564 * @param clickListener Listener to register
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700565 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700566 * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
567 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500568 public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener clickListener) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700569 mItemClickListener = clickListener;
570 }
571
572 /**
573 * Sets a listener to receive events when a list item is selected.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700574 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700575 * @param selectedListener Listener to register.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700576 *
Alan Viverette02cd0f92016-01-13 13:33:17 -0500577 * @see ListView#setOnItemSelectedListener(OnItemSelectedListener)
Adam Powellc3fa6302010-05-18 11:36:27 -0700578 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500579 public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700580 mItemSelectedListener = selectedListener;
581 }
582
583 /**
584 * Set a view to act as a user prompt for this popup window. Where the prompt view will appear
585 * is controlled by {@link #setPromptPosition(int)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700586 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700587 * @param prompt View to use as an informational prompt.
588 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500589 public void setPromptView(@Nullable View prompt) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700590 boolean showing = isShowing();
591 if (showing) {
592 removePromptView();
593 }
594 mPromptView = prompt;
595 if (showing) {
596 show();
597 }
598 }
599
600 /**
601 * Post a {@link #show()} call to the UI thread.
602 */
603 public void postShow() {
604 mHandler.post(mShowDropDownRunnable);
605 }
606
607 /**
608 * Show the popup list. If the list is already showing, this method
609 * will recalculate the popup's size and position.
610 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700611 @Override
Adam Powellc3fa6302010-05-18 11:36:27 -0700612 public void show() {
613 int height = buildDropDown();
614
Alan Viverette80ebe0d2015-04-30 15:53:11 -0700615 final boolean noInputMethod = isInputMethodNotNeeded();
Adam Powell348e69c2011-02-16 16:49:50 -0800616 mPopup.setAllowScrollingAnchorParent(!noInputMethod);
Alan Viverette80ebe0d2015-04-30 15:53:11 -0700617 mPopup.setWindowLayoutType(mDropDownWindowLayoutType);
Adam Powellc3fa6302010-05-18 11:36:27 -0700618
619 if (mPopup.isShowing()) {
tianran.x.lia31d4ed2016-11-28 15:34:24 +0800620 if (!getAnchorView().isAttachedToWindow()) {
621 //Don't update position if the anchor view is detached from window.
622 return;
623 }
Alan Viverette259c2842015-03-22 17:39:39 -0700624 final int widthSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700625 if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
626 // The call to PopupWindow's update method below can accept -1 for any
627 // value you do not want to update.
628 widthSpec = -1;
629 } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
630 widthSpec = getAnchorView().getWidth();
631 } else {
632 widthSpec = mDropDownWidth;
633 }
634
Alan Viverette259c2842015-03-22 17:39:39 -0700635 final int heightSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700636 if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
637 // The call to PopupWindow's update method below can accept -1 for any
638 // value you do not want to update.
639 heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
640 if (noInputMethod) {
Alan Viverette259c2842015-03-22 17:39:39 -0700641 mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
642 ViewGroup.LayoutParams.MATCH_PARENT : 0);
643 mPopup.setHeight(0);
Adam Powellc3fa6302010-05-18 11:36:27 -0700644 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700645 mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
646 ViewGroup.LayoutParams.MATCH_PARENT : 0);
647 mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
Adam Powellc3fa6302010-05-18 11:36:27 -0700648 }
649 } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
650 heightSpec = height;
651 } else {
652 heightSpec = mDropDownHeight;
653 }
654
655 mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
656
657 mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
Jun Mukai0eaec1f2015-06-09 15:00:28 -0700658 mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
659 (heightSpec < 0)? -1 : heightSpec);
Evan Roskyadf5bec2017-10-19 10:24:07 -0700660 mPopup.getContentView().restoreDefaultFocus();
Adam Powellc3fa6302010-05-18 11:36:27 -0700661 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700662 final int widthSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700663 if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
664 widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
665 } else {
666 if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
Alan Viverette259c2842015-03-22 17:39:39 -0700667 widthSpec = getAnchorView().getWidth();
Adam Powellc3fa6302010-05-18 11:36:27 -0700668 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700669 widthSpec = mDropDownWidth;
Adam Powellc3fa6302010-05-18 11:36:27 -0700670 }
671 }
672
Alan Viverette259c2842015-03-22 17:39:39 -0700673 final int heightSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700674 if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
675 heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
676 } else {
677 if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
Alan Viverette259c2842015-03-22 17:39:39 -0700678 heightSpec = height;
Adam Powellc3fa6302010-05-18 11:36:27 -0700679 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700680 heightSpec = mDropDownHeight;
Adam Powellc3fa6302010-05-18 11:36:27 -0700681 }
682 }
683
Alan Viverette259c2842015-03-22 17:39:39 -0700684 mPopup.setWidth(widthSpec);
685 mPopup.setHeight(heightSpec);
Adam Powell56c2d332010-11-05 20:03:03 -0700686 mPopup.setClipToScreenEnabled(true);
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700687
Adam Powellc3fa6302010-05-18 11:36:27 -0700688 // use outside touchable to dismiss drop down when touching outside of it, so
689 // only set this if the dropdown is not always visible
690 mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
691 mPopup.setTouchInterceptor(mTouchInterceptor);
Alan Viverette91098572016-01-19 14:07:31 -0500692 mPopup.setEpicenterBounds(mEpicenterBounds);
Vladislav Kaznacheevadf08d42017-04-05 15:22:10 -0700693 if (mOverlapAnchorSet) {
694 mPopup.setOverlapAnchor(mOverlapAnchor);
695 }
Alan Viverette560f1702014-05-05 14:40:07 -0700696 mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
697 mDropDownVerticalOffset, mDropDownGravity);
Adam Powellc3fa6302010-05-18 11:36:27 -0700698 mDropDownList.setSelection(ListView.INVALID_POSITION);
Evan Roskyadf5bec2017-10-19 10:24:07 -0700699 mPopup.getContentView().restoreDefaultFocus();
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700700
Adam Powellc3fa6302010-05-18 11:36:27 -0700701 if (!mModal || mDropDownList.isInTouchMode()) {
702 clearListSelection();
703 }
704 if (!mModal) {
705 mHandler.post(mHideSelector);
706 }
707 }
708 }
709
710 /**
711 * Dismiss the popup window.
712 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700713 @Override
Adam Powellc3fa6302010-05-18 11:36:27 -0700714 public void dismiss() {
715 mPopup.dismiss();
716 removePromptView();
717 mPopup.setContentView(null);
718 mDropDownList = null;
Adam Powellca51e872011-02-14 19:54:29 -0800719 mHandler.removeCallbacks(mResizePopupRunnable);
Adam Powellc3fa6302010-05-18 11:36:27 -0700720 }
721
Adam Powell6c6f5752010-08-20 18:34:46 -0700722 /**
723 * Set a listener to receive a callback when the popup is dismissed.
724 *
725 * @param listener Listener that will be notified when the popup is dismissed.
726 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500727 public void setOnDismissListener(@Nullable PopupWindow.OnDismissListener listener) {
Adam Powell6c6f5752010-08-20 18:34:46 -0700728 mPopup.setOnDismissListener(listener);
729 }
730
Adam Powellc3fa6302010-05-18 11:36:27 -0700731 private void removePromptView() {
732 if (mPromptView != null) {
733 final ViewParent parent = mPromptView.getParent();
734 if (parent instanceof ViewGroup) {
735 final ViewGroup group = (ViewGroup) parent;
736 group.removeView(mPromptView);
737 }
738 }
739 }
740
741 /**
742 * Control how the popup operates with an input method: one of
743 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
744 * or {@link #INPUT_METHOD_NOT_NEEDED}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700745 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700746 * <p>If the popup is showing, calling this method will take effect only
747 * the next time the popup is shown or through a manual call to the {@link #show()}
748 * method.</p>
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700749 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700750 * @see #getInputMethodMode()
751 * @see #show()
752 */
753 public void setInputMethodMode(int mode) {
754 mPopup.setInputMethodMode(mode);
755 }
756
757 /**
758 * Return the current value in {@link #setInputMethodMode(int)}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700759 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700760 * @see #setInputMethodMode(int)
761 */
762 public int getInputMethodMode() {
763 return mPopup.getInputMethodMode();
764 }
765
766 /**
767 * Set the selected position of the list.
768 * Only valid when {@link #isShowing()} == {@code true}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700769 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700770 * @param position List position to set as selected.
771 */
772 public void setSelection(int position) {
773 DropDownListView list = mDropDownList;
774 if (isShowing() && list != null) {
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700775 list.setListSelectionHidden(false);
Adam Powellc3fa6302010-05-18 11:36:27 -0700776 list.setSelection(position);
777 if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
778 list.setItemChecked(position, true);
779 }
780 }
781 }
782
783 /**
784 * Clear any current list selection.
785 * Only valid when {@link #isShowing()} == {@code true}.
786 */
787 public void clearListSelection() {
788 final DropDownListView list = mDropDownList;
789 if (list != null) {
790 // WARNING: Please read the comment where mListSelectionHidden is declared
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700791 list.setListSelectionHidden(true);
Adam Powellc3fa6302010-05-18 11:36:27 -0700792 list.hideSelector();
793 list.requestLayout();
794 }
795 }
796
797 /**
798 * @return {@code true} if the popup is currently showing, {@code false} otherwise.
799 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700800 @Override
Adam Powellc3fa6302010-05-18 11:36:27 -0700801 public boolean isShowing() {
802 return mPopup.isShowing();
803 }
804
805 /**
806 * @return {@code true} if this popup is configured to assume the user does not need
807 * to interact with the IME while it is showing, {@code false} otherwise.
808 */
809 public boolean isInputMethodNotNeeded() {
810 return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
811 }
812
813 /**
814 * Perform an item click operation on the specified list adapter position.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700815 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700816 * @param position Adapter position for performing the click
817 * @return true if the click action could be performed, false if not.
818 * (e.g. if the popup was not showing, this method would return false.)
819 */
820 public boolean performItemClick(int position) {
821 if (isShowing()) {
822 if (mItemClickListener != null) {
823 final DropDownListView list = mDropDownList;
824 final View child = list.getChildAt(position - list.getFirstVisiblePosition());
Adam Powellcdee4462010-09-02 17:13:24 -0700825 final ListAdapter adapter = list.getAdapter();
826 mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
Adam Powellc3fa6302010-05-18 11:36:27 -0700827 }
828 return true;
829 }
830 return false;
831 }
832
833 /**
834 * @return The currently selected item or null if the popup is not showing.
835 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500836 public @Nullable Object getSelectedItem() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700837 if (!isShowing()) {
838 return null;
839 }
840 return mDropDownList.getSelectedItem();
841 }
842
843 /**
844 * @return The position of the currently selected item or {@link ListView#INVALID_POSITION}
845 * if {@link #isShowing()} == {@code false}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700846 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700847 * @see ListView#getSelectedItemPosition()
848 */
849 public int getSelectedItemPosition() {
850 if (!isShowing()) {
851 return ListView.INVALID_POSITION;
852 }
853 return mDropDownList.getSelectedItemPosition();
854 }
855
856 /**
857 * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID}
858 * if {@link #isShowing()} == {@code false}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700859 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700860 * @see ListView#getSelectedItemId()
861 */
862 public long getSelectedItemId() {
863 if (!isShowing()) {
864 return ListView.INVALID_ROW_ID;
865 }
866 return mDropDownList.getSelectedItemId();
867 }
868
869 /**
870 * @return The View for the currently selected item or null if
871 * {@link #isShowing()} == {@code false}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700872 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700873 * @see ListView#getSelectedView()
874 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500875 public @Nullable View getSelectedView() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700876 if (!isShowing()) {
877 return null;
878 }
879 return mDropDownList.getSelectedView();
880 }
881
882 /**
883 * @return The {@link ListView} displayed within the popup window.
884 * Only valid when {@link #isShowing()} == {@code true}.
885 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700886 @Override
Alan Viverette02cd0f92016-01-13 13:33:17 -0500887 public @Nullable ListView getListView() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700888 return mDropDownList;
889 }
890
Alan Viverette02cd0f92016-01-13 13:33:17 -0500891 @NonNull DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
Jun Mukai31f581c2015-04-02 13:44:29 -0700892 return new DropDownListView(context, hijackFocus);
893 }
894
Adam Powellc3fa6302010-05-18 11:36:27 -0700895 /**
Adam Powell348e69c2011-02-16 16:49:50 -0800896 * The maximum number of list items that can be visible and still have
897 * the list expand when touched.
898 *
899 * @param max Max number of items that can be visible and still allow the list to expand.
900 */
901 void setListItemExpandMax(int max) {
902 mListItemExpandMaximum = max;
903 }
904
905 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -0800906 * Filter key down events. By forwarding key down events to this function,
Adam Powellc3fa6302010-05-18 11:36:27 -0700907 * views using non-modal ListPopupWindow can have it handle key selection of items.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -0400908 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700909 * @param keyCode keyCode param passed to the host view's onKeyDown
910 * @param event event param passed to the host view's onKeyDown
911 * @return true if the event was handled, false if it was ignored.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -0400912 *
Adam Powellc3fa6302010-05-18 11:36:27 -0700913 * @see #setModal(boolean)
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -0400914 * @see #onKeyUp(int, KeyEvent)
Adam Powellc3fa6302010-05-18 11:36:27 -0700915 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500916 public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700917 // when the drop down is shown, we drive it directly
918 if (isShowing()) {
919 // the key events are forwarded to the list in the drop down view
920 // note that ListView handles space but we don't want that to happen
921 // also if selection is not currently in the drop down, then don't
922 // let center or enter presses go there since that would cause it
923 // to select one of its items
924 if (keyCode != KeyEvent.KEYCODE_SPACE
925 && (mDropDownList.getSelectedItemPosition() >= 0
Michael Wright24d36f52013-07-19 15:55:14 -0700926 || !KeyEvent.isConfirmKey(keyCode))) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700927 int curIndex = mDropDownList.getSelectedItemPosition();
928 boolean consumed;
929
930 final boolean below = !mPopup.isAboveAnchor();
931
932 final ListAdapter adapter = mAdapter;
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700933
Adam Powellc3fa6302010-05-18 11:36:27 -0700934 boolean allEnabled;
935 int firstItem = Integer.MAX_VALUE;
936 int lastItem = Integer.MIN_VALUE;
937
938 if (adapter != null) {
939 allEnabled = adapter.areAllItemsEnabled();
940 firstItem = allEnabled ? 0 :
941 mDropDownList.lookForSelectablePosition(0, true);
942 lastItem = allEnabled ? adapter.getCount() - 1 :
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700943 mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
Adam Powellc3fa6302010-05-18 11:36:27 -0700944 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700945
Adam Powellc3fa6302010-05-18 11:36:27 -0700946 if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
947 (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
948 // When the selection is at the top, we block the key
949 // event to prevent focus from moving.
950 clearListSelection();
951 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
952 show();
953 return true;
954 } else {
955 // WARNING: Please read the comment where mListSelectionHidden
956 // is declared
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700957 mDropDownList.setListSelectionHidden(false);
Adam Powellc3fa6302010-05-18 11:36:27 -0700958 }
959
960 consumed = mDropDownList.onKeyDown(keyCode, event);
961 if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
962
963 if (consumed) {
964 // If it handled the key event, then the user is
965 // navigating in the list, so we should put it in front.
966 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
967 // Here's a little trick we need to do to make sure that
968 // the list view is actually showing its focus indicator,
969 // by ensuring it has focus and getting its window out
970 // of touch mode.
971 mDropDownList.requestFocusFromTouch();
972 show();
973
974 switch (keyCode) {
975 // avoid passing the focus from the text view to the
976 // next component
977 case KeyEvent.KEYCODE_ENTER:
978 case KeyEvent.KEYCODE_DPAD_CENTER:
979 case KeyEvent.KEYCODE_DPAD_DOWN:
980 case KeyEvent.KEYCODE_DPAD_UP:
981 return true;
982 }
983 } else {
984 if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
985 // when the selection is at the bottom, we block the
986 // event to avoid going to the next focusable widget
987 if (curIndex == lastItem) {
988 return true;
989 }
990 } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
991 curIndex == firstItem) {
992 return true;
993 }
994 }
995 }
996 }
997
998 return false;
999 }
1000
1001 /**
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001002 * Filter key up events. By forwarding key up events to this function,
Adam Powellc3fa6302010-05-18 11:36:27 -07001003 * views using non-modal ListPopupWindow can have it handle key selection of items.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001004 *
Adam Powellc3fa6302010-05-18 11:36:27 -07001005 * @param keyCode keyCode param passed to the host view's onKeyUp
1006 * @param event event param passed to the host view's onKeyUp
1007 * @return true if the event was handled, false if it was ignored.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001008 *
Adam Powellc3fa6302010-05-18 11:36:27 -07001009 * @see #setModal(boolean)
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001010 * @see #onKeyDown(int, KeyEvent)
Adam Powellc3fa6302010-05-18 11:36:27 -07001011 */
Alan Viverette02cd0f92016-01-13 13:33:17 -05001012 public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
Adam Powellc3fa6302010-05-18 11:36:27 -07001013 if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
1014 boolean consumed = mDropDownList.onKeyUp(keyCode, event);
Michael Wright24d36f52013-07-19 15:55:14 -07001015 if (consumed && KeyEvent.isConfirmKey(keyCode)) {
1016 // if the list accepts the key events and the key event was a click, the text view
1017 // gets the selected item from the drop down as its content
1018 dismiss();
Adam Powellc3fa6302010-05-18 11:36:27 -07001019 }
1020 return consumed;
1021 }
1022 return false;
1023 }
1024
1025 /**
1026 * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)}
1027 * events to this function, views using ListPopupWindow can have it dismiss the popup
1028 * when the back key is pressed.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001029 *
Adam Powellc3fa6302010-05-18 11:36:27 -07001030 * @param keyCode keyCode param passed to the host view's onKeyPreIme
1031 * @param event event param passed to the host view's onKeyPreIme
1032 * @return true if the event was handled, false if it was ignored.
Kirill Grouchnikov3d25a1f2016-03-16 17:32:56 -04001033 *
Adam Powellc3fa6302010-05-18 11:36:27 -07001034 * @see #setModal(boolean)
1035 */
Alan Viverette02cd0f92016-01-13 13:33:17 -05001036 public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
Adam Powellc3fa6302010-05-18 11:36:27 -07001037 if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
1038 // special case for the back key, we do not even try to send it
1039 // to the drop down list but instead, consume it immediately
1040 final View anchorView = mDropDownAnchorView;
1041 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08001042 KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
1043 if (state != null) {
1044 state.startTracking(event, this);
1045 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001046 return true;
1047 } else if (event.getAction() == KeyEvent.ACTION_UP) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08001048 KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
1049 if (state != null) {
1050 state.handleUpEvent(event);
1051 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001052 if (event.isTracking() && !event.isCanceled()) {
1053 dismiss();
1054 return true;
1055 }
1056 }
1057 }
1058 return false;
1059 }
1060
1061 /**
Alan Viverette1955a5b52013-08-27 15:45:16 -07001062 * Returns an {@link OnTouchListener} that can be added to the source view
1063 * to implement drag-to-open behavior. Generally, the source view should be
1064 * the same view that was passed to {@link #setAnchorView}.
1065 * <p>
1066 * When the listener is set on a view, touching that view and dragging
1067 * outside of its bounds will open the popup window. Lifting will select the
1068 * currently touched list item.
1069 * <p>
1070 * Example usage:
Alan Viverette3f9832d2013-08-30 14:43:25 -07001071 * <pre>
1072 * ListPopupWindow myPopup = new ListPopupWindow(context);
Alan Viverette1955a5b52013-08-27 15:45:16 -07001073 * myPopup.setAnchor(myAnchor);
1074 * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
Alan Viverette3f9832d2013-08-30 14:43:25 -07001075 * myAnchor.setOnTouchListener(dragListener);
1076 * </pre>
Alan Viverette1955a5b52013-08-27 15:45:16 -07001077 *
1078 * @param src the view on which the resulting listener will be set
1079 * @return a touch listener that controls drag-to-open behavior
1080 */
1081 public OnTouchListener createDragToOpenListener(View src) {
1082 return new ForwardingListener(src) {
1083 @Override
Oren Blasbergf44d90b2015-08-31 14:15:26 -07001084 public ShowableListMenu getPopup() {
Alan Viverette1955a5b52013-08-27 15:45:16 -07001085 return ListPopupWindow.this;
1086 }
1087 };
1088 }
1089
1090 /**
Adam Powellc3fa6302010-05-18 11:36:27 -07001091 * <p>Builds the popup window's content and returns the height the popup
1092 * should have. Returns -1 when the content already exists.</p>
1093 *
1094 * @return the content's height or -1 if content already exists
1095 */
1096 private int buildDropDown() {
1097 ViewGroup dropDownView;
1098 int otherHeights = 0;
1099
1100 if (mDropDownList == null) {
1101 Context context = mContext;
1102
1103 /**
1104 * This Runnable exists for the sole purpose of checking if the view layout has got
1105 * completed and if so call showDropDown to display the drop down. This is used to show
1106 * the drop down as soon as possible after user opens up the search dialog, without
1107 * waiting for the normal UI pipeline to do it's job which is slower than this method.
1108 */
1109 mShowDropDownRunnable = new Runnable() {
1110 public void run() {
1111 // View layout should be all done before displaying the drop down.
1112 View view = getAnchorView();
1113 if (view != null && view.getWindowToken() != null) {
1114 show();
1115 }
1116 }
1117 };
1118
Jun Mukai31f581c2015-04-02 13:44:29 -07001119 mDropDownList = createDropDownListView(context, !mModal);
Adam Powellc3fa6302010-05-18 11:36:27 -07001120 if (mDropDownListHighlight != null) {
1121 mDropDownList.setSelector(mDropDownListHighlight);
1122 }
1123 mDropDownList.setAdapter(mAdapter);
Adam Powellc3fa6302010-05-18 11:36:27 -07001124 mDropDownList.setOnItemClickListener(mItemClickListener);
1125 mDropDownList.setFocusable(true);
1126 mDropDownList.setFocusableInTouchMode(true);
1127 mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
1128 public void onItemSelected(AdapterView<?> parent, View view,
1129 int position, long id) {
1130
1131 if (position != -1) {
1132 DropDownListView dropDownList = mDropDownList;
1133
1134 if (dropDownList != null) {
Oren Blasbergf44d90b2015-08-31 14:15:26 -07001135 dropDownList.setListSelectionHidden(false);
Adam Powellc3fa6302010-05-18 11:36:27 -07001136 }
1137 }
1138 }
1139
1140 public void onNothingSelected(AdapterView<?> parent) {
1141 }
1142 });
1143 mDropDownList.setOnScrollListener(mScrollListener);
1144
1145 if (mItemSelectedListener != null) {
1146 mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
1147 }
1148
1149 dropDownView = mDropDownList;
1150
1151 View hintView = mPromptView;
1152 if (hintView != null) {
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001153 // if a hint has been specified, we accomodate more space for it and
Adam Powellc3fa6302010-05-18 11:36:27 -07001154 // add a text view in the drop down menu, at the bottom of the list
1155 LinearLayout hintContainer = new LinearLayout(context);
1156 hintContainer.setOrientation(LinearLayout.VERTICAL);
1157
1158 LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
1159 ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
1160 );
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001161
Adam Powellc3fa6302010-05-18 11:36:27 -07001162 switch (mPromptPosition) {
1163 case POSITION_PROMPT_BELOW:
1164 hintContainer.addView(dropDownView, hintParams);
1165 hintContainer.addView(hintView);
1166 break;
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001167
Adam Powellc3fa6302010-05-18 11:36:27 -07001168 case POSITION_PROMPT_ABOVE:
1169 hintContainer.addView(hintView);
1170 hintContainer.addView(dropDownView, hintParams);
1171 break;
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001172
Adam Powellc3fa6302010-05-18 11:36:27 -07001173 default:
1174 Log.e(TAG, "Invalid hint position " + mPromptPosition);
1175 break;
1176 }
1177
Alan Viveretteba4332d2015-06-25 13:00:03 -07001178 // Measure the hint's height to find how much more vertical
1179 // space we need to add to the drop down's height.
1180 final int widthSize;
1181 final int widthMode;
1182 if (mDropDownWidth >= 0) {
1183 widthMode = MeasureSpec.AT_MOST;
1184 widthSize = mDropDownWidth;
1185 } else {
1186 widthMode = MeasureSpec.UNSPECIFIED;
1187 widthSize = 0;
1188 }
1189 final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
1190 final int heightSpec = MeasureSpec.UNSPECIFIED;
Adam Powellc3fa6302010-05-18 11:36:27 -07001191 hintView.measure(widthSpec, heightSpec);
1192
1193 hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
1194 otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
1195 + hintParams.bottomMargin;
1196
1197 dropDownView = hintContainer;
1198 }
1199
1200 mPopup.setContentView(dropDownView);
1201 } else {
Adam Powellc3fa6302010-05-18 11:36:27 -07001202 final View view = mPromptView;
1203 if (view != null) {
1204 LinearLayout.LayoutParams hintParams =
1205 (LinearLayout.LayoutParams) view.getLayoutParams();
1206 otherHeights = view.getMeasuredHeight() + hintParams.topMargin
1207 + hintParams.bottomMargin;
1208 }
1209 }
1210
Adam Powell8132ba52011-07-15 17:37:11 -07001211 // getMaxAvailableHeight() subtracts the padding, so we put it back
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001212 // to get the available height for the whole window.
1213 final int padding;
1214 final Drawable background = mPopup.getBackground();
Adam Powellc3fa6302010-05-18 11:36:27 -07001215 if (background != null) {
1216 background.getPadding(mTempRect);
1217 padding = mTempRect.top + mTempRect.bottom;
Adam Powell8132ba52011-07-15 17:37:11 -07001218
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001219 // If we don't have an explicit vertical offset, determine one from
1220 // the window background so that content will line up.
Adam Powell8132ba52011-07-15 17:37:11 -07001221 if (!mDropDownVerticalOffsetSet) {
1222 mDropDownVerticalOffset = -mTempRect.top;
1223 }
Adam Powell7507d3d2012-03-08 12:01:16 -08001224 } else {
1225 mTempRect.setEmpty();
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001226 padding = 0;
Adam Powellc3fa6302010-05-18 11:36:27 -07001227 }
1228
Adam Powell8132ba52011-07-15 17:37:11 -07001229 // Max height available on the screen for a popup.
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001230 final boolean ignoreBottomDecorations =
Adam Powell8132ba52011-07-15 17:37:11 -07001231 mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
1232 final int maxHeight = mPopup.getMaxAvailableHeight(
1233 getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
Adam Powellc3fa6302010-05-18 11:36:27 -07001234 if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
1235 return maxHeight + padding;
1236 }
1237
Adam Powell7507d3d2012-03-08 12:01:16 -08001238 final int childWidthSpec;
1239 switch (mDropDownWidth) {
1240 case ViewGroup.LayoutParams.WRAP_CONTENT:
1241 childWidthSpec = MeasureSpec.makeMeasureSpec(
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001242 mContext.getResources().getDisplayMetrics().widthPixels
1243 - (mTempRect.left + mTempRect.right),
Adam Powell7507d3d2012-03-08 12:01:16 -08001244 MeasureSpec.AT_MOST);
1245 break;
1246 case ViewGroup.LayoutParams.MATCH_PARENT:
1247 childWidthSpec = MeasureSpec.makeMeasureSpec(
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001248 mContext.getResources().getDisplayMetrics().widthPixels
1249 - (mTempRect.left + mTempRect.right),
Adam Powell7507d3d2012-03-08 12:01:16 -08001250 MeasureSpec.EXACTLY);
1251 break;
1252 default:
1253 childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
1254 break;
1255 }
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001256
1257 // Add padding only if the list has items in it, that way we don't show
1258 // the popup if it is not needed.
Adam Powell7507d3d2012-03-08 12:01:16 -08001259 final int listContent = mDropDownList.measureHeightOfChildren(childWidthSpec,
Alan Viverette1e2c2d42016-03-21 13:45:33 -04001260 0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1);
1261 if (listContent > 0) {
1262 final int listPadding = mDropDownList.getPaddingTop()
1263 + mDropDownList.getPaddingBottom();
1264 otherHeights += padding + listPadding;
1265 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001266
1267 return listContent + otherHeights;
1268 }
1269
Vladislav Kaznacheevb40e61b2017-03-07 11:03:35 -08001270 /**
1271 * @hide
1272 */
1273 public void setOverlapAnchor(boolean overlap) {
Vladislav Kaznacheevadf08d42017-04-05 15:22:10 -07001274 mOverlapAnchorSet = true;
Vladislav Kaznacheevb40e61b2017-03-07 11:03:35 -08001275 mOverlapAnchor = overlap;
1276 }
1277
Adam Powellc3fa6302010-05-18 11:36:27 -07001278 private class PopupDataSetObserver extends DataSetObserver {
1279 @Override
1280 public void onChanged() {
1281 if (isShowing()) {
1282 // Resize the popup to fit new content
1283 show();
1284 }
1285 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001286
Adam Powellc3fa6302010-05-18 11:36:27 -07001287 @Override
1288 public void onInvalidated() {
1289 dismiss();
1290 }
1291 }
1292
1293 private class ListSelectorHider implements Runnable {
1294 public void run() {
1295 clearListSelection();
1296 }
1297 }
1298
1299 private class ResizePopupRunnable implements Runnable {
1300 public void run() {
Alan Viverettec8bfc682015-05-07 13:00:25 -07001301 if (mDropDownList != null && mDropDownList.isAttachedToWindow()
1302 && mDropDownList.getCount() > mDropDownList.getChildCount()
1303 && mDropDownList.getChildCount() <= mListItemExpandMaximum) {
Adam Powell348e69c2011-02-16 16:49:50 -08001304 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1305 show();
1306 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001307 }
1308 }
1309
1310 private class PopupTouchInterceptor implements OnTouchListener {
1311 public boolean onTouch(View v, MotionEvent event) {
1312 final int action = event.getAction();
1313 final int x = (int) event.getX();
1314 final int y = (int) event.getY();
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001315
Adam Powellc3fa6302010-05-18 11:36:27 -07001316 if (action == MotionEvent.ACTION_DOWN &&
1317 mPopup != null && mPopup.isShowing() &&
Gilles Debunne711734a2011-02-07 18:26:11 -08001318 (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
Adam Powellc3fa6302010-05-18 11:36:27 -07001319 mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
1320 } else if (action == MotionEvent.ACTION_UP) {
1321 mHandler.removeCallbacks(mResizePopupRunnable);
1322 }
1323 return false;
1324 }
1325 }
1326
1327 private class PopupScrollListener implements ListView.OnScrollListener {
1328 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1329 int totalItemCount) {
1330
1331 }
1332
1333 public void onScrollStateChanged(AbsListView view, int scrollState) {
1334 if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
1335 !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
1336 mHandler.removeCallbacks(mResizePopupRunnable);
1337 mResizePopupRunnable.run();
1338 }
1339 }
1340 }
1341}