blob: 595adc218e65aba1bb5b2cc9a75edc93605cb972 [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 com.android.internal.R;
20import com.android.internal.view.menu.ShowableListMenu;
21
22import android.annotation.AttrRes;
23import android.annotation.NonNull;
24import android.annotation.Nullable;
25import android.annotation.StyleRes;
Adam Powellc3fa6302010-05-18 11:36:27 -070026import android.content.Context;
Alan Viverettef023c252014-08-28 13:55:18 -070027import android.content.res.TypedArray;
Adam Powellc3fa6302010-05-18 11:36:27 -070028import android.database.DataSetObserver;
29import android.graphics.Rect;
30import android.graphics.drawable.Drawable;
31import android.os.Handler;
32import android.util.AttributeSet;
33import android.util.Log;
Adam Powell54c94de2013-09-26 15:36:34 -070034import android.view.Gravity;
Adam Powellc3fa6302010-05-18 11:36:27 -070035import android.view.KeyEvent;
36import android.view.MotionEvent;
37import android.view.View;
Adam Powellc3fa6302010-05-18 11:36:27 -070038import android.view.View.MeasureSpec;
39import android.view.View.OnTouchListener;
Gilles Debunne711734a2011-02-07 18:26:11 -080040import android.view.ViewGroup;
41import android.view.ViewParent;
Alan Viverette80ebe0d2015-04-30 15:53:11 -070042import android.view.WindowManager;
Alan Viverette02cd0f92016-01-13 13:33:17 -050043import android.widget.AdapterView.OnItemSelectedListener;
Fabrice Di Meglio1d3d7da2012-07-27 15:15:04 -070044
Adam Powellc3fa6302010-05-18 11:36:27 -070045/**
46 * A ListPopupWindow anchors itself to a host view and displays a
Adam Powell65d79fb2010-08-11 22:05:46 -070047 * list of choices.
Adam Powellc3fa6302010-05-18 11:36:27 -070048 *
49 * <p>ListPopupWindow contains a number of tricky behaviors surrounding
50 * positioning, scrolling parents to fit the dropdown, interacting
51 * sanely with the IME if present, and others.
52 *
53 * @see android.widget.AutoCompleteTextView
54 * @see android.widget.Spinner
55 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -070056public class ListPopupWindow implements ShowableListMenu {
Adam Powellc3fa6302010-05-18 11:36:27 -070057 private static final String TAG = "ListPopupWindow";
58 private static final boolean DEBUG = false;
59
60 /**
61 * This value controls the length of time that the user
62 * must leave a pointer down without scrolling to expand
63 * the autocomplete dropdown list to cover the IME.
64 */
65 private static final int EXPAND_LIST_TIMEOUT = 250;
66
67 private Context mContext;
Adam Powellc3fa6302010-05-18 11:36:27 -070068 private ListAdapter mAdapter;
69 private DropDownListView mDropDownList;
70
71 private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
72 private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
73 private int mDropDownHorizontalOffset;
74 private int mDropDownVerticalOffset;
Alan Viverette80ebe0d2015-04-30 15:53:11 -070075 private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
Adam Powell8132ba52011-07-15 17:37:11 -070076 private boolean mDropDownVerticalOffsetSet;
Adam Powellc3fa6302010-05-18 11:36:27 -070077
Adam Powell54c94de2013-09-26 15:36:34 -070078 private int mDropDownGravity = Gravity.NO_GRAVITY;
79
Adam Powellc3fa6302010-05-18 11:36:27 -070080 private boolean mDropDownAlwaysVisible = false;
81 private boolean mForceIgnoreOutsideTouch = false;
Adam Powell348e69c2011-02-16 16:49:50 -080082 int mListItemExpandMaximum = Integer.MAX_VALUE;
Adam Powellc3fa6302010-05-18 11:36:27 -070083
84 private View mPromptView;
85 private int mPromptPosition = POSITION_PROMPT_ABOVE;
86
87 private DataSetObserver mObserver;
88
89 private View mDropDownAnchorView;
90
91 private Drawable mDropDownListHighlight;
92
93 private AdapterView.OnItemClickListener mItemClickListener;
94 private AdapterView.OnItemSelectedListener mItemSelectedListener;
95
Adam Powell42675342010-07-09 18:02:59 -070096 private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
Adam Powellc3fa6302010-05-18 11:36:27 -070097 private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
98 private final PopupScrollListener mScrollListener = new PopupScrollListener();
99 private final ListSelectorHider mHideSelector = new ListSelectorHider();
100 private Runnable mShowDropDownRunnable;
101
Alan Viverette4a507232015-06-18 17:11:19 -0700102 private final Handler mHandler;
Adam Powellc3fa6302010-05-18 11:36:27 -0700103
104 private Rect mTempRect = new Rect();
105
106 private boolean mModal;
107
Oren Blasberg8e12f8d2015-09-02 14:25:56 -0700108 PopupWindow mPopup;
109
Adam Powellc3fa6302010-05-18 11:36:27 -0700110 /**
111 * The provided prompt view should appear above list content.
112 *
113 * @see #setPromptPosition(int)
114 * @see #getPromptPosition()
115 * @see #setPromptView(View)
116 */
117 public static final int POSITION_PROMPT_ABOVE = 0;
118
119 /**
120 * The provided prompt view should appear below list content.
121 *
122 * @see #setPromptPosition(int)
123 * @see #getPromptPosition()
124 * @see #setPromptView(View)
125 */
126 public static final int POSITION_PROMPT_BELOW = 1;
127
128 /**
129 * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}.
130 * If used to specify a popup width, the popup will match the width of the anchor view.
131 * If used to specify a popup height, the popup will fill available space.
132 */
133 public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
134
135 /**
136 * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
137 * If used to specify a popup width, the popup will use the width of its content.
138 */
139 public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
140
141 /**
142 * Mode for {@link #setInputMethodMode(int)}: the requirements for the
143 * input method should be based on the focusability of the popup. That is
144 * if it is focusable than it needs to work with the input method, else
145 * it doesn't.
146 */
147 public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
148
149 /**
150 * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
151 * work with an input method, regardless of whether it is focusable. This
152 * means that it will always be displayed so that the user can also operate
153 * the input method while it is shown.
154 */
155 public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;
156
157 /**
158 * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
159 * work with an input method, regardless of whether it is focusable. This
160 * means that it will always be displayed to use as much space on the
161 * screen as needed, regardless of whether this covers the input method.
162 */
163 public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED;
164
165 /**
166 * Create a new, empty popup window capable of displaying items from a ListAdapter.
167 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
168 *
169 * @param context Context used for contained views.
170 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500171 public ListPopupWindow(@NonNull Context context) {
Daniel Lehmannc2238d02010-10-21 11:52:55 -0700172 this(context, null, com.android.internal.R.attr.listPopupWindowStyle, 0);
Adam Powellc3fa6302010-05-18 11:36:27 -0700173 }
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)}.
178 *
179 * @param context Context used for contained views.
180 * @param attrs Attributes from inflating parent views used to style the popup.
181 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500182 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) {
Adam Powell0b2d3062010-09-14 16:15:02 -0700183 this(context, attrs, com.android.internal.R.attr.listPopupWindowStyle, 0);
Adam Powellc3fa6302010-05-18 11:36:27 -0700184 }
185
186 /**
187 * Create a new, empty popup window capable of displaying items from a ListAdapter.
188 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
189 *
190 * @param context Context used for contained views.
191 * @param attrs Attributes from inflating parent views used to style the popup.
192 * @param defStyleAttr Default style attribute to use for popup content.
193 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500194 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
195 @AttrRes int defStyleAttr) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700196 this(context, attrs, defStyleAttr, 0);
197 }
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)}.
202 *
203 * @param context Context used for contained views.
204 * @param attrs Attributes from inflating parent views used to style the popup.
205 * @param defStyleAttr Style attribute to read for default styling of popup content.
206 * @param defStyleRes Style resource ID to use for default styling of popup content.
207 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500208 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
209 @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700210 mContext = context;
Alan Viverette4a507232015-06-18 17:11:19 -0700211 mHandler = new Handler(context.getMainLooper());
Alan Viverettef023c252014-08-28 13:55:18 -0700212
213 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow,
214 defStyleAttr, defStyleRes);
215 mDropDownHorizontalOffset = a.getDimensionPixelOffset(
216 R.styleable.ListPopupWindow_dropDownHorizontalOffset, 0);
217 mDropDownVerticalOffset = a.getDimensionPixelOffset(
218 R.styleable.ListPopupWindow_dropDownVerticalOffset, 0);
219 if (mDropDownVerticalOffset != 0) {
220 mDropDownVerticalOffsetSet = true;
221 }
222 a.recycle();
223
Adam Powellc3fa6302010-05-18 11:36:27 -0700224 mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes);
Adam Powell6f5e9342011-01-27 13:30:55 -0800225 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
Adam Powellc3fa6302010-05-18 11:36:27 -0700226 }
227
228 /**
229 * Sets the adapter that provides the data and the views to represent the data
230 * in this popup window.
231 *
232 * @param adapter The adapter to use to create this window's content.
233 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500234 public void setAdapter(@Nullable ListAdapter adapter) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700235 if (mObserver == null) {
236 mObserver = new PopupDataSetObserver();
237 } else if (mAdapter != null) {
Adam Powell99969da2010-06-16 10:51:30 -0700238 mAdapter.unregisterDataSetObserver(mObserver);
Adam Powellc3fa6302010-05-18 11:36:27 -0700239 }
240 mAdapter = adapter;
241 if (mAdapter != null) {
242 adapter.registerDataSetObserver(mObserver);
243 }
244
245 if (mDropDownList != null) {
246 mDropDownList.setAdapter(mAdapter);
247 }
248 }
249
250 /**
251 * Set where the optional prompt view should appear. The default is
252 * {@link #POSITION_PROMPT_ABOVE}.
253 *
254 * @param position A position constant declaring where the prompt should be displayed.
255 *
256 * @see #POSITION_PROMPT_ABOVE
257 * @see #POSITION_PROMPT_BELOW
258 */
259 public void setPromptPosition(int position) {
260 mPromptPosition = position;
261 }
262
263 /**
264 * @return Where the optional prompt view should appear.
265 *
266 * @see #POSITION_PROMPT_ABOVE
267 * @see #POSITION_PROMPT_BELOW
268 */
269 public int getPromptPosition() {
270 return mPromptPosition;
271 }
272
273 /**
274 * Set whether this window should be modal when shown.
275 *
276 * <p>If a popup window is modal, it will receive all touch and key input.
277 * If the user touches outside the popup window's content area the popup window
278 * will be dismissed.
279 *
280 * @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
281 */
282 public void setModal(boolean modal) {
Chet Haasee04499a2014-08-05 15:24:43 -0700283 mModal = modal;
Adam Powellc3fa6302010-05-18 11:36:27 -0700284 mPopup.setFocusable(modal);
285 }
286
287 /**
288 * Returns whether the popup window will be modal when shown.
289 *
290 * @return {@code true} if the popup window will be modal, {@code false} otherwise.
291 */
292 public boolean isModal() {
293 return mModal;
294 }
295
296 /**
297 * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
298 * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
299 * ignore outside touch even when the drop down is not set to always visible.
300 *
301 * @hide Used only by AutoCompleteTextView to handle some internal special cases.
302 */
303 public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
304 mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
305 }
306
307 /**
308 * Sets whether the drop-down should remain visible under certain conditions.
309 *
310 * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless
311 * of the size or content of the list. {@link #getBackground()} will fill any space
312 * that is not used by the list.
313 *
314 * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
315 *
316 * @hide Only used by AutoCompleteTextView under special conditions.
317 */
318 public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
319 mDropDownAlwaysVisible = dropDownAlwaysVisible;
320 }
321
322 /**
323 * @return Whether the drop-down is visible under special conditions.
324 *
325 * @hide Only used by AutoCompleteTextView under special conditions.
326 */
327 public boolean isDropDownAlwaysVisible() {
328 return mDropDownAlwaysVisible;
329 }
330
331 /**
332 * Sets the operating mode for the soft input area.
333 *
334 * @param mode The desired mode, see
335 * {@link android.view.WindowManager.LayoutParams#softInputMode}
336 * for the full list
337 *
338 * @see android.view.WindowManager.LayoutParams#softInputMode
339 * @see #getSoftInputMode()
340 */
341 public void setSoftInputMode(int mode) {
342 mPopup.setSoftInputMode(mode);
343 }
344
345 /**
346 * Returns the current value in {@link #setSoftInputMode(int)}.
347 *
348 * @see #setSoftInputMode(int)
349 * @see android.view.WindowManager.LayoutParams#softInputMode
350 */
351 public int getSoftInputMode() {
352 return mPopup.getSoftInputMode();
353 }
354
355 /**
356 * Sets a drawable to use as the list item selector.
357 *
358 * @param selector List selector drawable to use in the popup.
359 */
360 public void setListSelector(Drawable selector) {
361 mDropDownListHighlight = selector;
362 }
363
364 /**
365 * @return The background drawable for the popup window.
366 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500367 public @Nullable Drawable getBackground() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700368 return mPopup.getBackground();
369 }
370
371 /**
372 * Sets a drawable to be the background for the popup window.
373 *
374 * @param d A drawable to set as the background.
375 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500376 public void setBackgroundDrawable(@Nullable Drawable d) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700377 mPopup.setBackgroundDrawable(d);
378 }
379
380 /**
381 * Set an animation style to use when the popup window is shown or dismissed.
382 *
383 * @param animationStyle Animation style to use.
384 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500385 public void setAnimationStyle(@StyleRes int animationStyle) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700386 mPopup.setAnimationStyle(animationStyle);
387 }
388
389 /**
390 * Returns the animation style that will be used when the popup window is
391 * shown or dismissed.
392 *
393 * @return Animation style that will be used.
394 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500395 public @StyleRes int getAnimationStyle() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700396 return mPopup.getAnimationStyle();
397 }
398
399 /**
400 * Returns the view that will be used to anchor this popup.
401 *
402 * @return The popup's anchor view
403 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500404 public @Nullable View getAnchorView() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700405 return mDropDownAnchorView;
406 }
407
408 /**
409 * Sets the popup's anchor view. This popup will always be positioned relative to
410 * the anchor view when shown.
411 *
412 * @param anchor The view to use as an anchor.
413 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500414 public void setAnchorView(@Nullable View anchor) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700415 mDropDownAnchorView = anchor;
416 }
417
418 /**
419 * @return The horizontal offset of the popup from its anchor in pixels.
420 */
421 public int getHorizontalOffset() {
422 return mDropDownHorizontalOffset;
423 }
424
425 /**
426 * Set the horizontal offset of this popup from its anchor view in pixels.
427 *
Adam Powella984b382010-06-04 14:20:10 -0700428 * @param offset The horizontal offset of the popup from its anchor.
Adam Powellc3fa6302010-05-18 11:36:27 -0700429 */
430 public void setHorizontalOffset(int offset) {
431 mDropDownHorizontalOffset = offset;
432 }
433
434 /**
435 * @return The vertical offset of the popup from its anchor in pixels.
436 */
437 public int getVerticalOffset() {
Adam Powell8132ba52011-07-15 17:37:11 -0700438 if (!mDropDownVerticalOffsetSet) {
439 return 0;
440 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700441 return mDropDownVerticalOffset;
442 }
443
444 /**
445 * Set the vertical offset of this popup from its anchor view in pixels.
446 *
Adam Powella984b382010-06-04 14:20:10 -0700447 * @param offset The vertical offset of the popup from its anchor.
Adam Powellc3fa6302010-05-18 11:36:27 -0700448 */
449 public void setVerticalOffset(int offset) {
450 mDropDownVerticalOffset = offset;
Adam Powell8132ba52011-07-15 17:37:11 -0700451 mDropDownVerticalOffsetSet = true;
Adam Powellc3fa6302010-05-18 11:36:27 -0700452 }
453
454 /**
Adam Powell54c94de2013-09-26 15:36:34 -0700455 * Set the gravity of the dropdown list. This is commonly used to
456 * set gravity to START or END for alignment with the anchor.
457 *
458 * @param gravity Gravity value to use
459 */
460 public void setDropDownGravity(int gravity) {
461 mDropDownGravity = gravity;
462 }
463
464 /**
Adam Powellc3fa6302010-05-18 11:36:27 -0700465 * @return The width of the popup window in pixels.
466 */
467 public int getWidth() {
468 return mDropDownWidth;
469 }
470
471 /**
472 * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT}
473 * or {@link #WRAP_CONTENT}.
474 *
475 * @param width Width of the popup window.
476 */
477 public void setWidth(int width) {
478 mDropDownWidth = width;
479 }
480
481 /**
Adam Powell42675342010-07-09 18:02:59 -0700482 * Sets the width of the popup window by the size of its content. The final width may be
483 * larger to accommodate styled window dressing.
484 *
485 * @param width Desired width of content in pixels.
486 */
487 public void setContentWidth(int width) {
488 Drawable popupBackground = mPopup.getBackground();
489 if (popupBackground != null) {
Adam Powella39b9872011-01-05 16:07:54 -0800490 popupBackground.getPadding(mTempRect);
491 mDropDownWidth = mTempRect.left + mTempRect.right + width;
Adam Powell62e2bde2011-08-15 15:50:05 -0700492 } else {
493 setWidth(width);
Adam Powell42675342010-07-09 18:02:59 -0700494 }
495 }
496
497 /**
Adam Powellc3fa6302010-05-18 11:36:27 -0700498 * @return The height of the popup window in pixels.
499 */
500 public int getHeight() {
501 return mDropDownHeight;
502 }
503
504 /**
505 * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
506 *
507 * @param height Height of the popup window.
508 */
509 public void setHeight(int height) {
510 mDropDownHeight = height;
511 }
512
513 /**
Alan Viverette80ebe0d2015-04-30 15:53:11 -0700514 * Set the layout type for this popup window.
515 * <p>
516 * See {@link WindowManager.LayoutParams#type} for possible values.
517 *
518 * @param layoutType Layout type for this window.
519 *
520 * @see WindowManager.LayoutParams#type
521 */
522 public void setWindowLayoutType(int layoutType) {
523 mDropDownWindowLayoutType = layoutType;
524 }
525
526 /**
Adam Powellc3fa6302010-05-18 11:36:27 -0700527 * Sets a listener to receive events when a list item is clicked.
528 *
529 * @param clickListener Listener to register
530 *
531 * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
532 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500533 public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener clickListener) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700534 mItemClickListener = clickListener;
535 }
536
537 /**
538 * Sets a listener to receive events when a list item is selected.
539 *
540 * @param selectedListener Listener to register.
541 *
Alan Viverette02cd0f92016-01-13 13:33:17 -0500542 * @see ListView#setOnItemSelectedListener(OnItemSelectedListener)
Adam Powellc3fa6302010-05-18 11:36:27 -0700543 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500544 public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700545 mItemSelectedListener = selectedListener;
546 }
547
548 /**
549 * Set a view to act as a user prompt for this popup window. Where the prompt view will appear
550 * is controlled by {@link #setPromptPosition(int)}.
551 *
552 * @param prompt View to use as an informational prompt.
553 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500554 public void setPromptView(@Nullable View prompt) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700555 boolean showing = isShowing();
556 if (showing) {
557 removePromptView();
558 }
559 mPromptView = prompt;
560 if (showing) {
561 show();
562 }
563 }
564
565 /**
566 * Post a {@link #show()} call to the UI thread.
567 */
568 public void postShow() {
569 mHandler.post(mShowDropDownRunnable);
570 }
571
572 /**
573 * Show the popup list. If the list is already showing, this method
574 * will recalculate the popup's size and position.
575 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700576 @Override
Adam Powellc3fa6302010-05-18 11:36:27 -0700577 public void show() {
578 int height = buildDropDown();
579
Alan Viverette80ebe0d2015-04-30 15:53:11 -0700580 final boolean noInputMethod = isInputMethodNotNeeded();
Adam Powell348e69c2011-02-16 16:49:50 -0800581 mPopup.setAllowScrollingAnchorParent(!noInputMethod);
Alan Viverette80ebe0d2015-04-30 15:53:11 -0700582 mPopup.setWindowLayoutType(mDropDownWindowLayoutType);
Adam Powellc3fa6302010-05-18 11:36:27 -0700583
584 if (mPopup.isShowing()) {
Alan Viverette259c2842015-03-22 17:39:39 -0700585 final int widthSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700586 if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
587 // The call to PopupWindow's update method below can accept -1 for any
588 // value you do not want to update.
589 widthSpec = -1;
590 } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
591 widthSpec = getAnchorView().getWidth();
592 } else {
593 widthSpec = mDropDownWidth;
594 }
595
Alan Viverette259c2842015-03-22 17:39:39 -0700596 final int heightSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700597 if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
598 // The call to PopupWindow's update method below can accept -1 for any
599 // value you do not want to update.
600 heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
601 if (noInputMethod) {
Alan Viverette259c2842015-03-22 17:39:39 -0700602 mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
603 ViewGroup.LayoutParams.MATCH_PARENT : 0);
604 mPopup.setHeight(0);
Adam Powellc3fa6302010-05-18 11:36:27 -0700605 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700606 mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
607 ViewGroup.LayoutParams.MATCH_PARENT : 0);
608 mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
Adam Powellc3fa6302010-05-18 11:36:27 -0700609 }
610 } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
611 heightSpec = height;
612 } else {
613 heightSpec = mDropDownHeight;
614 }
615
616 mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
617
618 mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
Jun Mukai0eaec1f2015-06-09 15:00:28 -0700619 mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
620 (heightSpec < 0)? -1 : heightSpec);
Adam Powellc3fa6302010-05-18 11:36:27 -0700621 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700622 final int widthSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700623 if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
624 widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
625 } else {
626 if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
Alan Viverette259c2842015-03-22 17:39:39 -0700627 widthSpec = getAnchorView().getWidth();
Adam Powellc3fa6302010-05-18 11:36:27 -0700628 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700629 widthSpec = mDropDownWidth;
Adam Powellc3fa6302010-05-18 11:36:27 -0700630 }
631 }
632
Alan Viverette259c2842015-03-22 17:39:39 -0700633 final int heightSpec;
Adam Powellc3fa6302010-05-18 11:36:27 -0700634 if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
635 heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
636 } else {
637 if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
Alan Viverette259c2842015-03-22 17:39:39 -0700638 heightSpec = height;
Adam Powellc3fa6302010-05-18 11:36:27 -0700639 } else {
Alan Viverette259c2842015-03-22 17:39:39 -0700640 heightSpec = mDropDownHeight;
Adam Powellc3fa6302010-05-18 11:36:27 -0700641 }
642 }
643
Alan Viverette259c2842015-03-22 17:39:39 -0700644 mPopup.setWidth(widthSpec);
645 mPopup.setHeight(heightSpec);
Adam Powell56c2d332010-11-05 20:03:03 -0700646 mPopup.setClipToScreenEnabled(true);
Adam Powellc3fa6302010-05-18 11:36:27 -0700647
648 // use outside touchable to dismiss drop down when touching outside of it, so
649 // only set this if the dropdown is not always visible
650 mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
651 mPopup.setTouchInterceptor(mTouchInterceptor);
Alan Viverette560f1702014-05-05 14:40:07 -0700652 mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
653 mDropDownVerticalOffset, mDropDownGravity);
Adam Powellc3fa6302010-05-18 11:36:27 -0700654 mDropDownList.setSelection(ListView.INVALID_POSITION);
655
656 if (!mModal || mDropDownList.isInTouchMode()) {
657 clearListSelection();
658 }
659 if (!mModal) {
660 mHandler.post(mHideSelector);
661 }
662 }
663 }
664
665 /**
666 * Dismiss the popup window.
667 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700668 @Override
Adam Powellc3fa6302010-05-18 11:36:27 -0700669 public void dismiss() {
670 mPopup.dismiss();
671 removePromptView();
672 mPopup.setContentView(null);
673 mDropDownList = null;
Adam Powellca51e872011-02-14 19:54:29 -0800674 mHandler.removeCallbacks(mResizePopupRunnable);
Adam Powellc3fa6302010-05-18 11:36:27 -0700675 }
676
Adam Powell6c6f5752010-08-20 18:34:46 -0700677 /**
678 * Set a listener to receive a callback when the popup is dismissed.
679 *
680 * @param listener Listener that will be notified when the popup is dismissed.
681 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500682 public void setOnDismissListener(@Nullable PopupWindow.OnDismissListener listener) {
Adam Powell6c6f5752010-08-20 18:34:46 -0700683 mPopup.setOnDismissListener(listener);
684 }
685
Adam Powellc3fa6302010-05-18 11:36:27 -0700686 private void removePromptView() {
687 if (mPromptView != null) {
688 final ViewParent parent = mPromptView.getParent();
689 if (parent instanceof ViewGroup) {
690 final ViewGroup group = (ViewGroup) parent;
691 group.removeView(mPromptView);
692 }
693 }
694 }
695
696 /**
697 * Control how the popup operates with an input method: one of
698 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
699 * or {@link #INPUT_METHOD_NOT_NEEDED}.
700 *
701 * <p>If the popup is showing, calling this method will take effect only
702 * the next time the popup is shown or through a manual call to the {@link #show()}
703 * method.</p>
704 *
705 * @see #getInputMethodMode()
706 * @see #show()
707 */
708 public void setInputMethodMode(int mode) {
709 mPopup.setInputMethodMode(mode);
710 }
711
712 /**
713 * Return the current value in {@link #setInputMethodMode(int)}.
714 *
715 * @see #setInputMethodMode(int)
716 */
717 public int getInputMethodMode() {
718 return mPopup.getInputMethodMode();
719 }
720
721 /**
722 * Set the selected position of the list.
723 * Only valid when {@link #isShowing()} == {@code true}.
724 *
725 * @param position List position to set as selected.
726 */
727 public void setSelection(int position) {
728 DropDownListView list = mDropDownList;
729 if (isShowing() && list != null) {
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700730 list.setListSelectionHidden(false);
Adam Powellc3fa6302010-05-18 11:36:27 -0700731 list.setSelection(position);
732 if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
733 list.setItemChecked(position, true);
734 }
735 }
736 }
737
738 /**
739 * Clear any current list selection.
740 * Only valid when {@link #isShowing()} == {@code true}.
741 */
742 public void clearListSelection() {
743 final DropDownListView list = mDropDownList;
744 if (list != null) {
745 // WARNING: Please read the comment where mListSelectionHidden is declared
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700746 list.setListSelectionHidden(true);
Adam Powellc3fa6302010-05-18 11:36:27 -0700747 list.hideSelector();
748 list.requestLayout();
749 }
750 }
751
752 /**
753 * @return {@code true} if the popup is currently showing, {@code false} otherwise.
754 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700755 @Override
Adam Powellc3fa6302010-05-18 11:36:27 -0700756 public boolean isShowing() {
757 return mPopup.isShowing();
758 }
759
760 /**
761 * @return {@code true} if this popup is configured to assume the user does not need
762 * to interact with the IME while it is showing, {@code false} otherwise.
763 */
764 public boolean isInputMethodNotNeeded() {
765 return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
766 }
767
768 /**
769 * Perform an item click operation on the specified list adapter position.
770 *
771 * @param position Adapter position for performing the click
772 * @return true if the click action could be performed, false if not.
773 * (e.g. if the popup was not showing, this method would return false.)
774 */
775 public boolean performItemClick(int position) {
776 if (isShowing()) {
777 if (mItemClickListener != null) {
778 final DropDownListView list = mDropDownList;
779 final View child = list.getChildAt(position - list.getFirstVisiblePosition());
Adam Powellcdee4462010-09-02 17:13:24 -0700780 final ListAdapter adapter = list.getAdapter();
781 mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
Adam Powellc3fa6302010-05-18 11:36:27 -0700782 }
783 return true;
784 }
785 return false;
786 }
787
788 /**
789 * @return The currently selected item or null if the popup is not showing.
790 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500791 public @Nullable Object getSelectedItem() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700792 if (!isShowing()) {
793 return null;
794 }
795 return mDropDownList.getSelectedItem();
796 }
797
798 /**
799 * @return The position of the currently selected item or {@link ListView#INVALID_POSITION}
800 * if {@link #isShowing()} == {@code false}.
801 *
802 * @see ListView#getSelectedItemPosition()
803 */
804 public int getSelectedItemPosition() {
805 if (!isShowing()) {
806 return ListView.INVALID_POSITION;
807 }
808 return mDropDownList.getSelectedItemPosition();
809 }
810
811 /**
812 * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID}
813 * if {@link #isShowing()} == {@code false}.
814 *
815 * @see ListView#getSelectedItemId()
816 */
817 public long getSelectedItemId() {
818 if (!isShowing()) {
819 return ListView.INVALID_ROW_ID;
820 }
821 return mDropDownList.getSelectedItemId();
822 }
823
824 /**
825 * @return The View for the currently selected item or null if
826 * {@link #isShowing()} == {@code false}.
827 *
828 * @see ListView#getSelectedView()
829 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500830 public @Nullable View getSelectedView() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700831 if (!isShowing()) {
832 return null;
833 }
834 return mDropDownList.getSelectedView();
835 }
836
837 /**
838 * @return The {@link ListView} displayed within the popup window.
839 * Only valid when {@link #isShowing()} == {@code true}.
840 */
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700841 @Override
Alan Viverette02cd0f92016-01-13 13:33:17 -0500842 public @Nullable ListView getListView() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700843 return mDropDownList;
844 }
845
Alan Viverette02cd0f92016-01-13 13:33:17 -0500846 @NonNull DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
Jun Mukai31f581c2015-04-02 13:44:29 -0700847 return new DropDownListView(context, hijackFocus);
848 }
849
Adam Powellc3fa6302010-05-18 11:36:27 -0700850 /**
Adam Powell348e69c2011-02-16 16:49:50 -0800851 * The maximum number of list items that can be visible and still have
852 * the list expand when touched.
853 *
854 * @param max Max number of items that can be visible and still allow the list to expand.
855 */
856 void setListItemExpandMax(int max) {
857 mListItemExpandMaximum = max;
858 }
859
860 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -0800861 * Filter key down events. By forwarding key down events to this function,
Adam Powellc3fa6302010-05-18 11:36:27 -0700862 * views using non-modal ListPopupWindow can have it handle key selection of items.
863 *
864 * @param keyCode keyCode param passed to the host view's onKeyDown
865 * @param event event param passed to the host view's onKeyDown
866 * @return true if the event was handled, false if it was ignored.
867 *
868 * @see #setModal(boolean)
869 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500870 public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700871 // when the drop down is shown, we drive it directly
872 if (isShowing()) {
873 // the key events are forwarded to the list in the drop down view
874 // note that ListView handles space but we don't want that to happen
875 // also if selection is not currently in the drop down, then don't
876 // let center or enter presses go there since that would cause it
877 // to select one of its items
878 if (keyCode != KeyEvent.KEYCODE_SPACE
879 && (mDropDownList.getSelectedItemPosition() >= 0
Michael Wright24d36f52013-07-19 15:55:14 -0700880 || !KeyEvent.isConfirmKey(keyCode))) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700881 int curIndex = mDropDownList.getSelectedItemPosition();
882 boolean consumed;
883
884 final boolean below = !mPopup.isAboveAnchor();
885
886 final ListAdapter adapter = mAdapter;
887
888 boolean allEnabled;
889 int firstItem = Integer.MAX_VALUE;
890 int lastItem = Integer.MIN_VALUE;
891
892 if (adapter != null) {
893 allEnabled = adapter.areAllItemsEnabled();
894 firstItem = allEnabled ? 0 :
895 mDropDownList.lookForSelectablePosition(0, true);
896 lastItem = allEnabled ? adapter.getCount() - 1 :
897 mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
898 }
899
900 if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
901 (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
902 // When the selection is at the top, we block the key
903 // event to prevent focus from moving.
904 clearListSelection();
905 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
906 show();
907 return true;
908 } else {
909 // WARNING: Please read the comment where mListSelectionHidden
910 // is declared
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700911 mDropDownList.setListSelectionHidden(false);
Adam Powellc3fa6302010-05-18 11:36:27 -0700912 }
913
914 consumed = mDropDownList.onKeyDown(keyCode, event);
915 if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
916
917 if (consumed) {
918 // If it handled the key event, then the user is
919 // navigating in the list, so we should put it in front.
920 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
921 // Here's a little trick we need to do to make sure that
922 // the list view is actually showing its focus indicator,
923 // by ensuring it has focus and getting its window out
924 // of touch mode.
925 mDropDownList.requestFocusFromTouch();
926 show();
927
928 switch (keyCode) {
929 // avoid passing the focus from the text view to the
930 // next component
931 case KeyEvent.KEYCODE_ENTER:
932 case KeyEvent.KEYCODE_DPAD_CENTER:
933 case KeyEvent.KEYCODE_DPAD_DOWN:
934 case KeyEvent.KEYCODE_DPAD_UP:
935 return true;
936 }
937 } else {
938 if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
939 // when the selection is at the bottom, we block the
940 // event to avoid going to the next focusable widget
941 if (curIndex == lastItem) {
942 return true;
943 }
944 } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
945 curIndex == firstItem) {
946 return true;
947 }
948 }
949 }
950 }
951
952 return false;
953 }
954
955 /**
956 * Filter key down events. By forwarding key up events to this function,
957 * views using non-modal ListPopupWindow can have it handle key selection of items.
958 *
959 * @param keyCode keyCode param passed to the host view's onKeyUp
960 * @param event event param passed to the host view's onKeyUp
961 * @return true if the event was handled, false if it was ignored.
962 *
963 * @see #setModal(boolean)
964 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500965 public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700966 if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
967 boolean consumed = mDropDownList.onKeyUp(keyCode, event);
Michael Wright24d36f52013-07-19 15:55:14 -0700968 if (consumed && KeyEvent.isConfirmKey(keyCode)) {
969 // if the list accepts the key events and the key event was a click, the text view
970 // gets the selected item from the drop down as its content
971 dismiss();
Adam Powellc3fa6302010-05-18 11:36:27 -0700972 }
973 return consumed;
974 }
975 return false;
976 }
977
978 /**
979 * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)}
980 * events to this function, views using ListPopupWindow can have it dismiss the popup
981 * when the back key is pressed.
982 *
983 * @param keyCode keyCode param passed to the host view's onKeyPreIme
984 * @param event event param passed to the host view's onKeyPreIme
985 * @return true if the event was handled, false if it was ignored.
986 *
987 * @see #setModal(boolean)
988 */
Alan Viverette02cd0f92016-01-13 13:33:17 -0500989 public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700990 if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
991 // special case for the back key, we do not even try to send it
992 // to the drop down list but instead, consume it immediately
993 final View anchorView = mDropDownAnchorView;
994 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -0800995 KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
996 if (state != null) {
997 state.startTracking(event, this);
998 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700999 return true;
1000 } else if (event.getAction() == KeyEvent.ACTION_UP) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08001001 KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
1002 if (state != null) {
1003 state.handleUpEvent(event);
1004 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001005 if (event.isTracking() && !event.isCanceled()) {
1006 dismiss();
1007 return true;
1008 }
1009 }
1010 }
1011 return false;
1012 }
1013
1014 /**
Alan Viverette1955a5b52013-08-27 15:45:16 -07001015 * Returns an {@link OnTouchListener} that can be added to the source view
1016 * to implement drag-to-open behavior. Generally, the source view should be
1017 * the same view that was passed to {@link #setAnchorView}.
1018 * <p>
1019 * When the listener is set on a view, touching that view and dragging
1020 * outside of its bounds will open the popup window. Lifting will select the
1021 * currently touched list item.
1022 * <p>
1023 * Example usage:
Alan Viverette3f9832d2013-08-30 14:43:25 -07001024 * <pre>
1025 * ListPopupWindow myPopup = new ListPopupWindow(context);
Alan Viverette1955a5b52013-08-27 15:45:16 -07001026 * myPopup.setAnchor(myAnchor);
1027 * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
Alan Viverette3f9832d2013-08-30 14:43:25 -07001028 * myAnchor.setOnTouchListener(dragListener);
1029 * </pre>
Alan Viverette1955a5b52013-08-27 15:45:16 -07001030 *
1031 * @param src the view on which the resulting listener will be set
1032 * @return a touch listener that controls drag-to-open behavior
1033 */
1034 public OnTouchListener createDragToOpenListener(View src) {
1035 return new ForwardingListener(src) {
1036 @Override
Oren Blasbergf44d90b2015-08-31 14:15:26 -07001037 public ShowableListMenu getPopup() {
Alan Viverette1955a5b52013-08-27 15:45:16 -07001038 return ListPopupWindow.this;
1039 }
1040 };
1041 }
1042
1043 /**
Adam Powellc3fa6302010-05-18 11:36:27 -07001044 * <p>Builds the popup window's content and returns the height the popup
1045 * should have. Returns -1 when the content already exists.</p>
1046 *
1047 * @return the content's height or -1 if content already exists
1048 */
1049 private int buildDropDown() {
1050 ViewGroup dropDownView;
1051 int otherHeights = 0;
1052
1053 if (mDropDownList == null) {
1054 Context context = mContext;
1055
1056 /**
1057 * This Runnable exists for the sole purpose of checking if the view layout has got
1058 * completed and if so call showDropDown to display the drop down. This is used to show
1059 * the drop down as soon as possible after user opens up the search dialog, without
1060 * waiting for the normal UI pipeline to do it's job which is slower than this method.
1061 */
1062 mShowDropDownRunnable = new Runnable() {
1063 public void run() {
1064 // View layout should be all done before displaying the drop down.
1065 View view = getAnchorView();
1066 if (view != null && view.getWindowToken() != null) {
1067 show();
1068 }
1069 }
1070 };
1071
Jun Mukai31f581c2015-04-02 13:44:29 -07001072 mDropDownList = createDropDownListView(context, !mModal);
Adam Powellc3fa6302010-05-18 11:36:27 -07001073 if (mDropDownListHighlight != null) {
1074 mDropDownList.setSelector(mDropDownListHighlight);
1075 }
1076 mDropDownList.setAdapter(mAdapter);
Adam Powellc3fa6302010-05-18 11:36:27 -07001077 mDropDownList.setOnItemClickListener(mItemClickListener);
1078 mDropDownList.setFocusable(true);
1079 mDropDownList.setFocusableInTouchMode(true);
1080 mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
1081 public void onItemSelected(AdapterView<?> parent, View view,
1082 int position, long id) {
1083
1084 if (position != -1) {
1085 DropDownListView dropDownList = mDropDownList;
1086
1087 if (dropDownList != null) {
Oren Blasbergf44d90b2015-08-31 14:15:26 -07001088 dropDownList.setListSelectionHidden(false);
Adam Powellc3fa6302010-05-18 11:36:27 -07001089 }
1090 }
1091 }
1092
1093 public void onNothingSelected(AdapterView<?> parent) {
1094 }
1095 });
1096 mDropDownList.setOnScrollListener(mScrollListener);
1097
1098 if (mItemSelectedListener != null) {
1099 mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
1100 }
1101
1102 dropDownView = mDropDownList;
1103
1104 View hintView = mPromptView;
1105 if (hintView != null) {
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001106 // if a hint has been specified, we accomodate more space for it and
Adam Powellc3fa6302010-05-18 11:36:27 -07001107 // add a text view in the drop down menu, at the bottom of the list
1108 LinearLayout hintContainer = new LinearLayout(context);
1109 hintContainer.setOrientation(LinearLayout.VERTICAL);
1110
1111 LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
1112 ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
1113 );
1114
1115 switch (mPromptPosition) {
1116 case POSITION_PROMPT_BELOW:
1117 hintContainer.addView(dropDownView, hintParams);
1118 hintContainer.addView(hintView);
1119 break;
1120
1121 case POSITION_PROMPT_ABOVE:
1122 hintContainer.addView(hintView);
1123 hintContainer.addView(dropDownView, hintParams);
1124 break;
1125
1126 default:
1127 Log.e(TAG, "Invalid hint position " + mPromptPosition);
1128 break;
1129 }
1130
Alan Viveretteba4332d2015-06-25 13:00:03 -07001131 // Measure the hint's height to find how much more vertical
1132 // space we need to add to the drop down's height.
1133 final int widthSize;
1134 final int widthMode;
1135 if (mDropDownWidth >= 0) {
1136 widthMode = MeasureSpec.AT_MOST;
1137 widthSize = mDropDownWidth;
1138 } else {
1139 widthMode = MeasureSpec.UNSPECIFIED;
1140 widthSize = 0;
1141 }
1142 final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
1143 final int heightSpec = MeasureSpec.UNSPECIFIED;
Adam Powellc3fa6302010-05-18 11:36:27 -07001144 hintView.measure(widthSpec, heightSpec);
1145
1146 hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
1147 otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
1148 + hintParams.bottomMargin;
1149
1150 dropDownView = hintContainer;
1151 }
1152
1153 mPopup.setContentView(dropDownView);
1154 } else {
Adam Powellc3fa6302010-05-18 11:36:27 -07001155 final View view = mPromptView;
1156 if (view != null) {
1157 LinearLayout.LayoutParams hintParams =
1158 (LinearLayout.LayoutParams) view.getLayoutParams();
1159 otherHeights = view.getMeasuredHeight() + hintParams.topMargin
1160 + hintParams.bottomMargin;
1161 }
1162 }
1163
Adam Powell8132ba52011-07-15 17:37:11 -07001164 // getMaxAvailableHeight() subtracts the padding, so we put it back
Adam Powellc3fa6302010-05-18 11:36:27 -07001165 // to get the available height for the whole window
1166 int padding = 0;
1167 Drawable background = mPopup.getBackground();
1168 if (background != null) {
1169 background.getPadding(mTempRect);
1170 padding = mTempRect.top + mTempRect.bottom;
Adam Powell8132ba52011-07-15 17:37:11 -07001171
1172 // If we don't have an explicit vertical offset, determine one from the window
1173 // background so that content will line up.
1174 if (!mDropDownVerticalOffsetSet) {
1175 mDropDownVerticalOffset = -mTempRect.top;
1176 }
Adam Powell7507d3d2012-03-08 12:01:16 -08001177 } else {
1178 mTempRect.setEmpty();
Adam Powellc3fa6302010-05-18 11:36:27 -07001179 }
1180
Adam Powell8132ba52011-07-15 17:37:11 -07001181 // Max height available on the screen for a popup.
1182 boolean ignoreBottomDecorations =
1183 mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
1184 final int maxHeight = mPopup.getMaxAvailableHeight(
1185 getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
1186
Adam Powellc3fa6302010-05-18 11:36:27 -07001187 if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
1188 return maxHeight + padding;
1189 }
1190
Adam Powell7507d3d2012-03-08 12:01:16 -08001191 final int childWidthSpec;
1192 switch (mDropDownWidth) {
1193 case ViewGroup.LayoutParams.WRAP_CONTENT:
1194 childWidthSpec = MeasureSpec.makeMeasureSpec(
1195 mContext.getResources().getDisplayMetrics().widthPixels -
1196 (mTempRect.left + mTempRect.right),
1197 MeasureSpec.AT_MOST);
1198 break;
1199 case ViewGroup.LayoutParams.MATCH_PARENT:
1200 childWidthSpec = MeasureSpec.makeMeasureSpec(
1201 mContext.getResources().getDisplayMetrics().widthPixels -
1202 (mTempRect.left + mTempRect.right),
1203 MeasureSpec.EXACTLY);
1204 break;
1205 default:
1206 childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
1207 break;
1208 }
1209 final int listContent = mDropDownList.measureHeightOfChildren(childWidthSpec,
Adam Powella7845ed2011-08-07 15:48:03 -07001210 0, ListView.NO_POSITION, maxHeight - otherHeights, -1);
Adam Powellc3fa6302010-05-18 11:36:27 -07001211 // add padding only if the list has items in it, that way we don't show
1212 // the popup if it is not needed
1213 if (listContent > 0) otherHeights += padding;
1214
1215 return listContent + otherHeights;
1216 }
1217
Adam Powellc3fa6302010-05-18 11:36:27 -07001218 private class PopupDataSetObserver extends DataSetObserver {
1219 @Override
1220 public void onChanged() {
1221 if (isShowing()) {
1222 // Resize the popup to fit new content
1223 show();
1224 }
1225 }
1226
1227 @Override
1228 public void onInvalidated() {
1229 dismiss();
1230 }
1231 }
1232
1233 private class ListSelectorHider implements Runnable {
1234 public void run() {
1235 clearListSelection();
1236 }
1237 }
1238
1239 private class ResizePopupRunnable implements Runnable {
1240 public void run() {
Alan Viverettec8bfc682015-05-07 13:00:25 -07001241 if (mDropDownList != null && mDropDownList.isAttachedToWindow()
1242 && mDropDownList.getCount() > mDropDownList.getChildCount()
1243 && mDropDownList.getChildCount() <= mListItemExpandMaximum) {
Adam Powell348e69c2011-02-16 16:49:50 -08001244 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1245 show();
1246 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001247 }
1248 }
1249
1250 private class PopupTouchInterceptor implements OnTouchListener {
1251 public boolean onTouch(View v, MotionEvent event) {
1252 final int action = event.getAction();
1253 final int x = (int) event.getX();
1254 final int y = (int) event.getY();
1255
1256 if (action == MotionEvent.ACTION_DOWN &&
1257 mPopup != null && mPopup.isShowing() &&
Gilles Debunne711734a2011-02-07 18:26:11 -08001258 (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
Adam Powellc3fa6302010-05-18 11:36:27 -07001259 mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
1260 } else if (action == MotionEvent.ACTION_UP) {
1261 mHandler.removeCallbacks(mResizePopupRunnable);
1262 }
1263 return false;
1264 }
1265 }
1266
1267 private class PopupScrollListener implements ListView.OnScrollListener {
1268 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1269 int totalItemCount) {
1270
1271 }
1272
1273 public void onScrollStateChanged(AbsListView view, int scrollState) {
1274 if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
1275 !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
1276 mHandler.removeCallbacks(mResizePopupRunnable);
1277 mResizePopupRunnable.run();
1278 }
1279 }
1280 }
1281}