1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget; 18 19 import android.annotation.AttrRes; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.StyleRes; 23 import android.compat.annotation.UnsupportedAppUsage; 24 import android.content.Context; 25 import android.content.res.TypedArray; 26 import android.database.DataSetObserver; 27 import android.graphics.Rect; 28 import android.graphics.drawable.Drawable; 29 import android.os.Build; 30 import android.os.Handler; 31 import android.util.AttributeSet; 32 import android.util.Log; 33 import android.view.Gravity; 34 import android.view.KeyEvent; 35 import android.view.MotionEvent; 36 import android.view.View; 37 import android.view.View.MeasureSpec; 38 import android.view.View.OnTouchListener; 39 import android.view.ViewGroup; 40 import android.view.ViewParent; 41 import android.view.WindowManager; 42 import android.widget.AdapterView.OnItemSelectedListener; 43 44 import com.android.internal.R; 45 import com.android.internal.view.menu.ShowableListMenu; 46 47 /** 48 * A ListPopupWindow anchors itself to a host view and displays a 49 * list of choices. 50 * 51 * <p>ListPopupWindow contains a number of tricky behaviors surrounding 52 * positioning, scrolling parents to fit the dropdown, interacting 53 * sanely with the IME if present, and others. 54 * 55 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset 56 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset 57 * 58 * @see android.widget.AutoCompleteTextView 59 * @see android.widget.Spinner 60 */ 61 public class ListPopupWindow implements ShowableListMenu { 62 private static final String TAG = "ListPopupWindow"; 63 private static final boolean DEBUG = false; 64 65 /** 66 * This value controls the length of time that the user 67 * must leave a pointer down without scrolling to expand 68 * the autocomplete dropdown list to cover the IME. 69 */ 70 private static final int EXPAND_LIST_TIMEOUT = 250; 71 72 private Context mContext; 73 private ListAdapter mAdapter; 74 @UnsupportedAppUsage 75 private DropDownListView mDropDownList; 76 77 private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT; 78 private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT; 79 private int mDropDownHorizontalOffset; 80 private int mDropDownVerticalOffset; 81 private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; 82 private boolean mDropDownVerticalOffsetSet; 83 private boolean mIsAnimatedFromAnchor = true; 84 private boolean mOverlapAnchor; 85 private boolean mOverlapAnchorSet; 86 87 private int mDropDownGravity = Gravity.NO_GRAVITY; 88 89 private boolean mDropDownAlwaysVisible = false; 90 private boolean mForceIgnoreOutsideTouch = false; 91 int mListItemExpandMaximum = Integer.MAX_VALUE; 92 93 private View mPromptView; 94 private int mPromptPosition = POSITION_PROMPT_ABOVE; 95 96 private DataSetObserver mObserver; 97 98 private View mDropDownAnchorView; 99 100 private Drawable mDropDownListHighlight; 101 102 private AdapterView.OnItemClickListener mItemClickListener; 103 private AdapterView.OnItemSelectedListener mItemSelectedListener; 104 105 private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable(); 106 private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor(); 107 private final PopupScrollListener mScrollListener = new PopupScrollListener(); 108 private final ListSelectorHider mHideSelector = new ListSelectorHider(); 109 private Runnable mShowDropDownRunnable; 110 111 private final Handler mHandler; 112 113 private final Rect mTempRect = new Rect(); 114 115 /** 116 * Optional anchor-relative bounds to be used as the transition epicenter. 117 * When {@code null}, the anchor bounds are used as the epicenter. 118 */ 119 private Rect mEpicenterBounds; 120 121 private boolean mModal; 122 123 @UnsupportedAppUsage 124 PopupWindow mPopup; 125 126 /** 127 * The provided prompt view should appear above list content. 128 * 129 * @see #setPromptPosition(int) 130 * @see #getPromptPosition() 131 * @see #setPromptView(View) 132 */ 133 public static final int POSITION_PROMPT_ABOVE = 0; 134 135 /** 136 * The provided prompt view should appear below list content. 137 * 138 * @see #setPromptPosition(int) 139 * @see #getPromptPosition() 140 * @see #setPromptView(View) 141 */ 142 public static final int POSITION_PROMPT_BELOW = 1; 143 144 /** 145 * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}. 146 * If used to specify a popup width, the popup will match the width of the anchor view. 147 * If used to specify a popup height, the popup will fill available space. 148 */ 149 public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT; 150 151 /** 152 * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}. 153 * If used to specify a popup width, the popup will use the width of its content. 154 */ 155 public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT; 156 157 /** 158 * Mode for {@link #setInputMethodMode(int)}: the requirements for the 159 * input method should be based on the focusability of the popup. That is 160 * if it is focusable than it needs to work with the input method, else 161 * it doesn't. 162 */ 163 public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE; 164 165 /** 166 * Mode for {@link #setInputMethodMode(int)}: this popup always needs to 167 * work with an input method, regardless of whether it is focusable. This 168 * means that it will always be displayed so that the user can also operate 169 * the input method while it is shown. 170 */ 171 public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED; 172 173 /** 174 * Mode for {@link #setInputMethodMode(int)}: this popup never needs to 175 * work with an input method, regardless of whether it is focusable. This 176 * means that it will always be displayed to use as much space on the 177 * screen as needed, regardless of whether this covers the input method. 178 */ 179 public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED; 180 181 /** 182 * Create a new, empty popup window capable of displaying items from a ListAdapter. 183 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. 184 * 185 * @param context Context used for contained views. 186 */ ListPopupWindow(@onNull Context context)187 public ListPopupWindow(@NonNull Context context) { 188 this(context, null, com.android.internal.R.attr.listPopupWindowStyle, 0); 189 } 190 191 /** 192 * Create a new, empty popup window capable of displaying items from a ListAdapter. 193 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. 194 * 195 * @param context Context used for contained views. 196 * @param attrs Attributes from inflating parent views used to style the popup. 197 */ ListPopupWindow(@onNull Context context, @Nullable AttributeSet attrs)198 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) { 199 this(context, attrs, com.android.internal.R.attr.listPopupWindowStyle, 0); 200 } 201 202 /** 203 * Create a new, empty popup window capable of displaying items from a ListAdapter. 204 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. 205 * 206 * @param context Context used for contained views. 207 * @param attrs Attributes from inflating parent views used to style the popup. 208 * @param defStyleAttr Default style attribute to use for popup content. 209 */ ListPopupWindow(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr)210 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs, 211 @AttrRes int defStyleAttr) { 212 this(context, attrs, defStyleAttr, 0); 213 } 214 215 /** 216 * Create a new, empty popup window capable of displaying items from a ListAdapter. 217 * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. 218 * 219 * @param context Context used for contained views. 220 * @param attrs Attributes from inflating parent views used to style the popup. 221 * @param defStyleAttr Style attribute to read for default styling of popup content. 222 * @param defStyleRes Style resource ID to use for default styling of popup content. 223 */ ListPopupWindow(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)224 public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs, 225 @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { 226 mContext = context; 227 mHandler = new Handler(context.getMainLooper()); 228 229 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow, 230 defStyleAttr, defStyleRes); 231 mDropDownHorizontalOffset = a.getDimensionPixelOffset( 232 R.styleable.ListPopupWindow_dropDownHorizontalOffset, 0); 233 mDropDownVerticalOffset = a.getDimensionPixelOffset( 234 R.styleable.ListPopupWindow_dropDownVerticalOffset, 0); 235 if (mDropDownVerticalOffset != 0) { 236 mDropDownVerticalOffsetSet = true; 237 } 238 a.recycle(); 239 240 mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes); 241 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); 242 } 243 244 /** 245 * Sets the adapter that provides the data and the views to represent the data 246 * in this popup window. 247 * 248 * @param adapter The adapter to use to create this window's content. 249 */ setAdapter(@ullable ListAdapter adapter)250 public void setAdapter(@Nullable ListAdapter adapter) { 251 if (mObserver == null) { 252 mObserver = new PopupDataSetObserver(); 253 } else if (mAdapter != null) { 254 mAdapter.unregisterDataSetObserver(mObserver); 255 } 256 mAdapter = adapter; 257 if (mAdapter != null) { 258 adapter.registerDataSetObserver(mObserver); 259 } 260 261 if (mDropDownList != null) { 262 mDropDownList.setAdapter(mAdapter); 263 } 264 } 265 266 /** 267 * Set where the optional prompt view should appear. The default is 268 * {@link #POSITION_PROMPT_ABOVE}. 269 * 270 * @param position A position constant declaring where the prompt should be displayed. 271 * 272 * @see #POSITION_PROMPT_ABOVE 273 * @see #POSITION_PROMPT_BELOW 274 */ setPromptPosition(int position)275 public void setPromptPosition(int position) { 276 mPromptPosition = position; 277 } 278 279 /** 280 * @return Where the optional prompt view should appear. 281 * 282 * @see #POSITION_PROMPT_ABOVE 283 * @see #POSITION_PROMPT_BELOW 284 */ getPromptPosition()285 public int getPromptPosition() { 286 return mPromptPosition; 287 } 288 289 /** 290 * Set whether this window should be modal when shown. 291 * 292 * <p>If a popup window is modal, it will receive all touch and key input. 293 * If the user touches outside the popup window's content area the popup window 294 * will be dismissed. 295 * 296 * @param modal {@code true} if the popup window should be modal, {@code false} otherwise. 297 */ setModal(boolean modal)298 public void setModal(boolean modal) { 299 mModal = modal; 300 mPopup.setFocusable(modal); 301 } 302 303 /** 304 * Returns whether the popup window will be modal when shown. 305 * 306 * @return {@code true} if the popup window will be modal, {@code false} otherwise. 307 */ isModal()308 public boolean isModal() { 309 return mModal; 310 } 311 312 /** 313 * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is 314 * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we 315 * ignore outside touch even when the drop down is not set to always visible. 316 * 317 * @hide Used only by AutoCompleteTextView to handle some internal special cases. 318 */ 319 @UnsupportedAppUsage setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch)320 public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) { 321 mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch; 322 } 323 324 /** 325 * Sets whether the drop-down should remain visible under certain conditions. 326 * 327 * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless 328 * of the size or content of the list. {@link #getBackground()} will fill any space 329 * that is not used by the list. 330 * 331 * @param dropDownAlwaysVisible Whether to keep the drop-down visible. 332 * 333 * @hide Only used by AutoCompleteTextView under special conditions. 334 */ 335 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setDropDownAlwaysVisible(boolean dropDownAlwaysVisible)336 public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) { 337 mDropDownAlwaysVisible = dropDownAlwaysVisible; 338 } 339 340 /** 341 * @return Whether the drop-down is visible under special conditions. 342 * 343 * @hide Only used by AutoCompleteTextView under special conditions. 344 */ 345 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) isDropDownAlwaysVisible()346 public boolean isDropDownAlwaysVisible() { 347 return mDropDownAlwaysVisible; 348 } 349 350 /** 351 * Sets the operating mode for the soft input area. 352 * 353 * @param mode The desired mode, see 354 * {@link android.view.WindowManager.LayoutParams#softInputMode} 355 * for the full list 356 * 357 * @see android.view.WindowManager.LayoutParams#softInputMode 358 * @see #getSoftInputMode() 359 */ setSoftInputMode(int mode)360 public void setSoftInputMode(int mode) { 361 mPopup.setSoftInputMode(mode); 362 } 363 364 /** 365 * Returns the current value in {@link #setSoftInputMode(int)}. 366 * 367 * @see #setSoftInputMode(int) 368 * @see android.view.WindowManager.LayoutParams#softInputMode 369 */ getSoftInputMode()370 public int getSoftInputMode() { 371 return mPopup.getSoftInputMode(); 372 } 373 374 /** 375 * Sets a drawable to use as the list item selector. 376 * 377 * @param selector List selector drawable to use in the popup. 378 */ setListSelector(Drawable selector)379 public void setListSelector(Drawable selector) { 380 mDropDownListHighlight = selector; 381 } 382 383 /** 384 * @return The background drawable for the popup window. 385 */ getBackground()386 public @Nullable Drawable getBackground() { 387 return mPopup.getBackground(); 388 } 389 390 /** 391 * Sets a drawable to be the background for the popup window. 392 * 393 * @param d A drawable to set as the background. 394 */ setBackgroundDrawable(@ullable Drawable d)395 public void setBackgroundDrawable(@Nullable Drawable d) { 396 mPopup.setBackgroundDrawable(d); 397 } 398 399 /** 400 * Set an animation style to use when the popup window is shown or dismissed. 401 * 402 * @param animationStyle Animation style to use. 403 */ setAnimationStyle(@tyleRes int animationStyle)404 public void setAnimationStyle(@StyleRes int animationStyle) { 405 mPopup.setAnimationStyle(animationStyle); 406 } 407 408 /** 409 * Returns the animation style that will be used when the popup window is 410 * shown or dismissed. 411 * 412 * @return Animation style that will be used. 413 */ getAnimationStyle()414 public @StyleRes int getAnimationStyle() { 415 return mPopup.getAnimationStyle(); 416 } 417 418 /** 419 * Returns the view that will be used to anchor this popup. 420 * 421 * @return The popup's anchor view 422 */ getAnchorView()423 public @Nullable View getAnchorView() { 424 return mDropDownAnchorView; 425 } 426 427 /** 428 * Sets the popup's anchor view. This popup will always be positioned relative to 429 * the anchor view when shown. 430 * 431 * @param anchor The view to use as an anchor. 432 */ setAnchorView(@ullable View anchor)433 public void setAnchorView(@Nullable View anchor) { 434 mDropDownAnchorView = anchor; 435 } 436 437 /** 438 * @return The horizontal offset of the popup from its anchor in pixels. 439 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset 440 */ getHorizontalOffset()441 public int getHorizontalOffset() { 442 return mDropDownHorizontalOffset; 443 } 444 445 /** 446 * Set the horizontal offset of this popup from its anchor view in pixels. 447 * 448 * @param offset The horizontal offset of the popup from its anchor. 449 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset 450 */ setHorizontalOffset(int offset)451 public void setHorizontalOffset(int offset) { 452 mDropDownHorizontalOffset = offset; 453 } 454 455 /** 456 * @return The vertical offset of the popup from its anchor in pixels. 457 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset 458 */ getVerticalOffset()459 public int getVerticalOffset() { 460 if (!mDropDownVerticalOffsetSet) { 461 return 0; 462 } 463 return mDropDownVerticalOffset; 464 } 465 466 /** 467 * Set the vertical offset of this popup from its anchor view in pixels. 468 * 469 * @param offset The vertical offset of the popup from its anchor. 470 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset 471 */ setVerticalOffset(int offset)472 public void setVerticalOffset(int offset) { 473 mDropDownVerticalOffset = offset; 474 mDropDownVerticalOffsetSet = true; 475 } 476 477 /** 478 * Specifies the anchor-relative bounds of the popup's transition 479 * epicenter. 480 * 481 * @param bounds anchor-relative bounds, or {@code null} to use default epicenter 482 * 483 * @see #getEpicenterBounds() 484 */ setEpicenterBounds(@ullable Rect bounds)485 public void setEpicenterBounds(@Nullable Rect bounds) { 486 mEpicenterBounds = bounds != null ? new Rect(bounds) : null; 487 } 488 489 /** 490 * Returns bounds which are used as a popup's epicenter 491 * of the enter and exit transitions. 492 * 493 * @return bounds relative to anchor view, or {@code null} if not set 494 * @see #setEpicenterBounds(Rect) 495 */ 496 @Nullable getEpicenterBounds()497 public Rect getEpicenterBounds() { 498 return mEpicenterBounds != null ? new Rect(mEpicenterBounds) : null; 499 } 500 501 /** 502 * Set the gravity of the dropdown list. This is commonly used to 503 * set gravity to START or END for alignment with the anchor. 504 * 505 * @param gravity Gravity value to use 506 */ setDropDownGravity(int gravity)507 public void setDropDownGravity(int gravity) { 508 mDropDownGravity = gravity; 509 } 510 511 /** 512 * @return The width of the popup window in pixels. 513 */ getWidth()514 public int getWidth() { 515 return mDropDownWidth; 516 } 517 518 /** 519 * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT} 520 * or {@link #WRAP_CONTENT}. 521 * 522 * @param width Width of the popup window. 523 */ setWidth(int width)524 public void setWidth(int width) { 525 mDropDownWidth = width; 526 } 527 528 /** 529 * Sets the width of the popup window by the size of its content. The final width may be 530 * larger to accommodate styled window dressing. 531 * 532 * @param width Desired width of content in pixels. 533 */ setContentWidth(int width)534 public void setContentWidth(int width) { 535 Drawable popupBackground = mPopup.getBackground(); 536 if (popupBackground != null) { 537 popupBackground.getPadding(mTempRect); 538 mDropDownWidth = mTempRect.left + mTempRect.right + width; 539 } else { 540 setWidth(width); 541 } 542 } 543 544 /** 545 * @return The height of the popup window in pixels. 546 */ getHeight()547 public int getHeight() { 548 return mDropDownHeight; 549 } 550 551 /** 552 * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}. 553 * 554 * @param height Height of the popup window must be a positive value, 555 * {@link #MATCH_PARENT}, or {@link #WRAP_CONTENT}. 556 * 557 * @throws IllegalArgumentException if height is set to negative value 558 */ setHeight(int height)559 public void setHeight(int height) { 560 if (height < 0 && ViewGroup.LayoutParams.WRAP_CONTENT != height 561 && ViewGroup.LayoutParams.MATCH_PARENT != height) { 562 if (mContext.getApplicationInfo().targetSdkVersion 563 < Build.VERSION_CODES.O) { 564 Log.e(TAG, "Negative value " + height + " passed to ListPopupWindow#setHeight" 565 + " produces undefined results"); 566 } else { 567 throw new IllegalArgumentException( 568 "Invalid height. Must be a positive value, MATCH_PARENT, or WRAP_CONTENT."); 569 } 570 } 571 mDropDownHeight = height; 572 } 573 574 /** 575 * Set the layout type for this popup window. 576 * <p> 577 * See {@link WindowManager.LayoutParams#type} for possible values. 578 * 579 * @param layoutType Layout type for this window. 580 * 581 * @see WindowManager.LayoutParams#type 582 */ setWindowLayoutType(int layoutType)583 public void setWindowLayoutType(int layoutType) { 584 mDropDownWindowLayoutType = layoutType; 585 } 586 587 /** 588 * Sets a listener to receive events when a list item is clicked. 589 * 590 * @param clickListener Listener to register 591 * 592 * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener) 593 */ setOnItemClickListener(@ullable AdapterView.OnItemClickListener clickListener)594 public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener clickListener) { 595 mItemClickListener = clickListener; 596 } 597 598 /** 599 * Sets a listener to receive events when a list item is selected. 600 * 601 * @param selectedListener Listener to register. 602 * 603 * @see ListView#setOnItemSelectedListener(OnItemSelectedListener) 604 */ setOnItemSelectedListener(@ullable OnItemSelectedListener selectedListener)605 public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) { 606 mItemSelectedListener = selectedListener; 607 } 608 609 /** 610 * Set a view to act as a user prompt for this popup window. Where the prompt view will appear 611 * is controlled by {@link #setPromptPosition(int)}. 612 * 613 * @param prompt View to use as an informational prompt. 614 */ setPromptView(@ullable View prompt)615 public void setPromptView(@Nullable View prompt) { 616 boolean showing = isShowing(); 617 if (showing) { 618 removePromptView(); 619 } 620 mPromptView = prompt; 621 if (showing) { 622 show(); 623 } 624 } 625 626 /** 627 * Post a {@link #show()} call to the UI thread. 628 */ postShow()629 public void postShow() { 630 mHandler.post(mShowDropDownRunnable); 631 } 632 633 /** 634 * Show the popup list. If the list is already showing, this method 635 * will recalculate the popup's size and position. 636 */ 637 @Override show()638 public void show() { 639 int height = buildDropDown(); 640 641 final boolean noInputMethod = isInputMethodNotNeeded(); 642 mPopup.setAllowScrollingAnchorParent(!noInputMethod); 643 mPopup.setWindowLayoutType(mDropDownWindowLayoutType); 644 645 if (mPopup.isShowing()) { 646 if (!getAnchorView().isAttachedToWindow()) { 647 //Don't update position if the anchor view is detached from window. 648 return; 649 } 650 final int widthSpec; 651 if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { 652 // The call to PopupWindow's update method below can accept -1 for any 653 // value you do not want to update. 654 widthSpec = -1; 655 } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { 656 widthSpec = getAnchorView().getWidth(); 657 } else { 658 widthSpec = mDropDownWidth; 659 } 660 661 final int heightSpec; 662 if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { 663 // The call to PopupWindow's update method below can accept -1 for any 664 // value you do not want to update. 665 heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT; 666 if (noInputMethod) { 667 mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? 668 ViewGroup.LayoutParams.MATCH_PARENT : 0); 669 mPopup.setHeight(0); 670 } else { 671 mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? 672 ViewGroup.LayoutParams.MATCH_PARENT : 0); 673 mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT); 674 } 675 } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { 676 heightSpec = height; 677 } else { 678 heightSpec = mDropDownHeight; 679 } 680 681 mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); 682 683 mPopup.update(getAnchorView(), mDropDownHorizontalOffset, 684 mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec, 685 (heightSpec < 0)? -1 : heightSpec); 686 mPopup.getContentView().restoreDefaultFocus(); 687 } else { 688 final int widthSpec; 689 if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { 690 widthSpec = ViewGroup.LayoutParams.MATCH_PARENT; 691 } else { 692 if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { 693 widthSpec = getAnchorView().getWidth(); 694 } else { 695 widthSpec = mDropDownWidth; 696 } 697 } 698 699 final int heightSpec; 700 if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { 701 heightSpec = ViewGroup.LayoutParams.MATCH_PARENT; 702 } else { 703 if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { 704 heightSpec = height; 705 } else { 706 heightSpec = mDropDownHeight; 707 } 708 } 709 710 mPopup.setWidth(widthSpec); 711 mPopup.setHeight(heightSpec); 712 mPopup.setIsClippedToScreen(true); 713 714 // use outside touchable to dismiss drop down when touching outside of it, so 715 // only set this if the dropdown is not always visible 716 mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); 717 mPopup.setTouchInterceptor(mTouchInterceptor); 718 mPopup.setEpicenterBounds(mEpicenterBounds); 719 if (mOverlapAnchorSet) { 720 mPopup.setOverlapAnchor(mOverlapAnchor); 721 } 722 mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset, 723 mDropDownVerticalOffset, mDropDownGravity); 724 mDropDownList.setSelection(ListView.INVALID_POSITION); 725 mPopup.getContentView().restoreDefaultFocus(); 726 727 if (!mModal || mDropDownList.isInTouchMode()) { 728 clearListSelection(); 729 } 730 if (!mModal) { 731 mHandler.post(mHideSelector); 732 } 733 } 734 } 735 736 /** 737 * Dismiss the popup window. 738 */ 739 @Override 740 public void dismiss() { 741 mPopup.dismiss(); 742 removePromptView(); 743 mPopup.setContentView(null); 744 mDropDownList = null; 745 mHandler.removeCallbacks(mResizePopupRunnable); 746 } 747 748 /** 749 * Remove existing exit transition from PopupWindow and force immediate dismissal. 750 * @hide 751 */ 752 public void dismissImmediate() { 753 mPopup.setExitTransition(null); 754 dismiss(); 755 } 756 757 /** 758 * Set a listener to receive a callback when the popup is dismissed. 759 * 760 * @param listener Listener that will be notified when the popup is dismissed. 761 */ 762 public void setOnDismissListener(@Nullable PopupWindow.OnDismissListener listener) { 763 mPopup.setOnDismissListener(listener); 764 } 765 766 private void removePromptView() { 767 if (mPromptView != null) { 768 final ViewParent parent = mPromptView.getParent(); 769 if (parent instanceof ViewGroup) { 770 final ViewGroup group = (ViewGroup) parent; 771 group.removeView(mPromptView); 772 } 773 } 774 } 775 776 /** 777 * Control how the popup operates with an input method: one of 778 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, 779 * or {@link #INPUT_METHOD_NOT_NEEDED}. 780 * 781 * <p>If the popup is showing, calling this method will take effect only 782 * the next time the popup is shown or through a manual call to the {@link #show()} 783 * method.</p> 784 * 785 * @see #getInputMethodMode() 786 * @see #show() 787 */ 788 public void setInputMethodMode(int mode) { 789 mPopup.setInputMethodMode(mode); 790 } 791 792 /** 793 * Return the current value in {@link #setInputMethodMode(int)}. 794 * 795 * @see #setInputMethodMode(int) 796 */ 797 public int getInputMethodMode() { 798 return mPopup.getInputMethodMode(); 799 } 800 801 /** 802 * Set the selected position of the list. 803 * Only valid when {@link #isShowing()} == {@code true}. 804 * 805 * @param position List position to set as selected. 806 */ 807 public void setSelection(int position) { 808 DropDownListView list = mDropDownList; 809 if (isShowing() && list != null) { 810 list.setListSelectionHidden(false); 811 list.setSelection(position); 812 if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) { 813 list.setItemChecked(position, true); 814 } 815 } 816 } 817 818 /** 819 * Clear any current list selection. 820 * Only valid when {@link #isShowing()} == {@code true}. 821 */ 822 public void clearListSelection() { 823 final DropDownListView list = mDropDownList; 824 if (list != null) { 825 // WARNING: Please read the comment where mListSelectionHidden is declared 826 list.setListSelectionHidden(true); 827 list.hideSelector(); 828 list.requestLayout(); 829 } 830 } 831 832 /** 833 * @return {@code true} if the popup is currently showing, {@code false} otherwise. 834 */ 835 @Override 836 public boolean isShowing() { 837 return mPopup.isShowing(); 838 } 839 840 /** 841 * @return {@code true} if this popup is configured to assume the user does not need 842 * to interact with the IME while it is showing, {@code false} otherwise. 843 */ 844 public boolean isInputMethodNotNeeded() { 845 return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED; 846 } 847 848 /** 849 * Perform an item click operation on the specified list adapter position. 850 * 851 * @param position Adapter position for performing the click 852 * @return true if the click action could be performed, false if not. 853 * (e.g. if the popup was not showing, this method would return false.) 854 */ 855 public boolean performItemClick(int position) { 856 if (isShowing()) { 857 if (mItemClickListener != null) { 858 final DropDownListView list = mDropDownList; 859 final View child = list.getChildAt(position - list.getFirstVisiblePosition()); 860 final ListAdapter adapter = list.getAdapter(); 861 mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position)); 862 } 863 return true; 864 } 865 return false; 866 } 867 868 /** 869 * @return The currently selected item or null if the popup is not showing. 870 */ 871 public @Nullable Object getSelectedItem() { 872 if (!isShowing()) { 873 return null; 874 } 875 return mDropDownList.getSelectedItem(); 876 } 877 878 /** 879 * @return The position of the currently selected item or {@link ListView#INVALID_POSITION} 880 * if {@link #isShowing()} == {@code false}. 881 * 882 * @see ListView#getSelectedItemPosition() 883 */ 884 public int getSelectedItemPosition() { 885 if (!isShowing()) { 886 return ListView.INVALID_POSITION; 887 } 888 return mDropDownList.getSelectedItemPosition(); 889 } 890 891 /** 892 * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID} 893 * if {@link #isShowing()} == {@code false}. 894 * 895 * @see ListView#getSelectedItemId() 896 */ 897 public long getSelectedItemId() { 898 if (!isShowing()) { 899 return ListView.INVALID_ROW_ID; 900 } 901 return mDropDownList.getSelectedItemId(); 902 } 903 904 /** 905 * @return The View for the currently selected item or null if 906 * {@link #isShowing()} == {@code false}. 907 * 908 * @see ListView#getSelectedView() 909 */ 910 public @Nullable View getSelectedView() { 911 if (!isShowing()) { 912 return null; 913 } 914 return mDropDownList.getSelectedView(); 915 } 916 917 /** 918 * @return The {@link ListView} displayed within the popup window. 919 * Only valid when {@link #isShowing()} == {@code true}. 920 */ 921 @Override 922 public @Nullable ListView getListView() { 923 return mDropDownList; 924 } 925 926 @NonNull DropDownListView createDropDownListView(Context context, boolean hijackFocus) { 927 return new DropDownListView(context, hijackFocus); 928 } 929 930 /** 931 * The maximum number of list items that can be visible and still have 932 * the list expand when touched. 933 * 934 * @param max Max number of items that can be visible and still allow the list to expand. 935 */ 936 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 937 void setListItemExpandMax(int max) { 938 mListItemExpandMaximum = max; 939 } 940 941 /** 942 * Filter key down events. By forwarding key down events to this function, 943 * views using non-modal ListPopupWindow can have it handle key selection of items. 944 * 945 * @param keyCode keyCode param passed to the host view's onKeyDown 946 * @param event event param passed to the host view's onKeyDown 947 * @return true if the event was handled, false if it was ignored. 948 * 949 * @see #setModal(boolean) 950 * @see #onKeyUp(int, KeyEvent) 951 */ 952 public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { 953 // when the drop down is shown, we drive it directly 954 if (isShowing()) { 955 // the key events are forwarded to the list in the drop down view 956 // note that ListView handles space but we don't want that to happen 957 // also if selection is not currently in the drop down, then don't 958 // let center or enter presses go there since that would cause it 959 // to select one of its items 960 if (keyCode != KeyEvent.KEYCODE_SPACE 961 && (mDropDownList.getSelectedItemPosition() >= 0 962 || !KeyEvent.isConfirmKey(keyCode))) { 963 int curIndex = mDropDownList.getSelectedItemPosition(); 964 boolean consumed; 965 966 final boolean below = !mPopup.isAboveAnchor(); 967 968 final ListAdapter adapter = mAdapter; 969 970 boolean allEnabled; 971 int firstItem = Integer.MAX_VALUE; 972 int lastItem = Integer.MIN_VALUE; 973 974 if (adapter != null) { 975 allEnabled = adapter.areAllItemsEnabled(); 976 firstItem = allEnabled ? 0 : 977 mDropDownList.lookForSelectablePosition(0, true); 978 lastItem = allEnabled ? adapter.getCount() - 1 : 979 mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false); 980 } 981 982 if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) || 983 (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) { 984 // When the selection is at the top, we block the key 985 // event to prevent focus from moving. 986 clearListSelection(); 987 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); 988 show(); 989 return true; 990 } else { 991 // WARNING: Please read the comment where mListSelectionHidden 992 // is declared 993 mDropDownList.setListSelectionHidden(false); 994 } 995 996 consumed = mDropDownList.onKeyDown(keyCode, event); 997 if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed); 998 999 if (consumed) { 1000 // If it handled the key event, then the user is 1001 // navigating in the list, so we should put it in front. 1002 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); 1003 // Here's a little trick we need to do to make sure that 1004 // the list view is actually showing its focus indicator, 1005 // by ensuring it has focus and getting its window out 1006 // of touch mode. 1007 mDropDownList.requestFocusFromTouch(); 1008 show(); 1009 1010 switch (keyCode) { 1011 // avoid passing the focus from the text view to the 1012 // next component 1013 case KeyEvent.KEYCODE_ENTER: 1014 case KeyEvent.KEYCODE_DPAD_CENTER: 1015 case KeyEvent.KEYCODE_DPAD_DOWN: 1016 case KeyEvent.KEYCODE_DPAD_UP: 1017 case KeyEvent.KEYCODE_NUMPAD_ENTER: 1018 return true; 1019 } 1020 } else { 1021 if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { 1022 // when the selection is at the bottom, we block the 1023 // event to avoid going to the next focusable widget 1024 if (curIndex == lastItem) { 1025 return true; 1026 } 1027 } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && 1028 curIndex == firstItem) { 1029 return true; 1030 } 1031 } 1032 } 1033 } 1034 1035 return false; 1036 } 1037 1038 /** 1039 * Filter key up events. By forwarding key up events to this function, 1040 * views using non-modal ListPopupWindow can have it handle key selection of items. 1041 * 1042 * @param keyCode keyCode param passed to the host view's onKeyUp 1043 * @param event event param passed to the host view's onKeyUp 1044 * @return true if the event was handled, false if it was ignored. 1045 * 1046 * @see #setModal(boolean) 1047 * @see #onKeyDown(int, KeyEvent) 1048 */ 1049 public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { 1050 if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) { 1051 boolean consumed = mDropDownList.onKeyUp(keyCode, event); 1052 if (consumed && KeyEvent.isConfirmKey(keyCode)) { 1053 // if the list accepts the key events and the key event was a click, the text view 1054 // gets the selected item from the drop down as its content 1055 dismiss(); 1056 } 1057 return consumed; 1058 } 1059 return false; 1060 } 1061 1062 /** 1063 * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)} 1064 * events to this function, views using ListPopupWindow can have it dismiss the popup 1065 * when the back key is pressed. 1066 * 1067 * @param keyCode keyCode param passed to the host view's onKeyPreIme 1068 * @param event event param passed to the host view's onKeyPreIme 1069 * @return true if the event was handled, false if it was ignored. 1070 * 1071 * @see #setModal(boolean) 1072 */ onKeyPreIme(int keyCode, @NonNull KeyEvent event)1073 public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) { 1074 if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) { 1075 // special case for the back key, we do not even try to send it 1076 // to the drop down list but instead, consume it immediately 1077 final View anchorView = mDropDownAnchorView; 1078 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 1079 KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState(); 1080 if (state != null) { 1081 state.startTracking(event, this); 1082 } 1083 return true; 1084 } else if (event.getAction() == KeyEvent.ACTION_UP) { 1085 KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState(); 1086 if (state != null) { 1087 state.handleUpEvent(event); 1088 } 1089 if (event.isTracking() && !event.isCanceled()) { 1090 dismiss(); 1091 return true; 1092 } 1093 } 1094 } 1095 return false; 1096 } 1097 1098 /** 1099 * Returns an {@link OnTouchListener} that can be added to the source view 1100 * to implement drag-to-open behavior. Generally, the source view should be 1101 * the same view that was passed to {@link #setAnchorView}. 1102 * <p> 1103 * When the listener is set on a view, touching that view and dragging 1104 * outside of its bounds will open the popup window. Lifting will select the 1105 * currently touched list item. 1106 * <p> 1107 * Example usage: 1108 * <pre> 1109 * ListPopupWindow myPopup = new ListPopupWindow(context); 1110 * myPopup.setAnchor(myAnchor); 1111 * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor); 1112 * myAnchor.setOnTouchListener(dragListener); 1113 * </pre> 1114 * 1115 * @param src the view on which the resulting listener will be set 1116 * @return a touch listener that controls drag-to-open behavior 1117 */ createDragToOpenListener(View src)1118 public OnTouchListener createDragToOpenListener(View src) { 1119 return new ForwardingListener(src) { 1120 @Override 1121 public ShowableListMenu getPopup() { 1122 return ListPopupWindow.this; 1123 } 1124 }; 1125 } 1126 1127 /** 1128 * <p>Builds the popup window's content and returns the height the popup 1129 * should have. Returns -1 when the content already exists.</p> 1130 * 1131 * @return the content's height or -1 if content already exists 1132 */ 1133 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) buildDropDown()1134 private int buildDropDown() { 1135 ViewGroup dropDownView; 1136 int otherHeights = 0; 1137 1138 if (mDropDownList == null) { 1139 Context context = mContext; 1140 1141 /** 1142 * This Runnable exists for the sole purpose of checking if the view layout has got 1143 * completed and if so call showDropDown to display the drop down. This is used to show 1144 * the drop down as soon as possible after user opens up the search dialog, without 1145 * waiting for the normal UI pipeline to do it's job which is slower than this method. 1146 */ 1147 mShowDropDownRunnable = new Runnable() { 1148 public void run() { 1149 // View layout should be all done before displaying the drop down. 1150 View view = getAnchorView(); 1151 if (view != null && view.getWindowToken() != null) { 1152 show(); 1153 } 1154 } 1155 }; 1156 1157 mDropDownList = createDropDownListView(context, !mModal); 1158 if (mDropDownListHighlight != null) { 1159 mDropDownList.setSelector(mDropDownListHighlight); 1160 } 1161 mDropDownList.setAdapter(mAdapter); 1162 mDropDownList.setOnItemClickListener(mItemClickListener); 1163 mDropDownList.setFocusable(true); 1164 mDropDownList.setFocusableInTouchMode(true); 1165 mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 1166 public void onItemSelected(AdapterView<?> parent, View view, 1167 int position, long id) { 1168 1169 if (position != -1) { 1170 DropDownListView dropDownList = mDropDownList; 1171 1172 if (dropDownList != null) { 1173 dropDownList.setListSelectionHidden(false); 1174 } 1175 } 1176 } 1177 1178 public void onNothingSelected(AdapterView<?> parent) { 1179 } 1180 }); 1181 mDropDownList.setOnScrollListener(mScrollListener); 1182 1183 if (mItemSelectedListener != null) { 1184 mDropDownList.setOnItemSelectedListener(mItemSelectedListener); 1185 } 1186 1187 dropDownView = mDropDownList; 1188 1189 View hintView = mPromptView; 1190 if (hintView != null) { 1191 // if a hint has been specified, we accomodate more space for it and 1192 // add a text view in the drop down menu, at the bottom of the list 1193 LinearLayout hintContainer = new LinearLayout(context); 1194 hintContainer.setOrientation(LinearLayout.VERTICAL); 1195 1196 LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams( 1197 ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f 1198 ); 1199 1200 switch (mPromptPosition) { 1201 case POSITION_PROMPT_BELOW: 1202 hintContainer.addView(dropDownView, hintParams); 1203 hintContainer.addView(hintView); 1204 break; 1205 1206 case POSITION_PROMPT_ABOVE: 1207 hintContainer.addView(hintView); 1208 hintContainer.addView(dropDownView, hintParams); 1209 break; 1210 1211 default: 1212 Log.e(TAG, "Invalid hint position " + mPromptPosition); 1213 break; 1214 } 1215 1216 // Measure the hint's height to find how much more vertical 1217 // space we need to add to the drop down's height. 1218 final int widthSize; 1219 final int widthMode; 1220 if (mDropDownWidth >= 0) { 1221 widthMode = MeasureSpec.AT_MOST; 1222 widthSize = mDropDownWidth; 1223 } else { 1224 widthMode = MeasureSpec.UNSPECIFIED; 1225 widthSize = 0; 1226 } 1227 final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode); 1228 final int heightSpec = MeasureSpec.UNSPECIFIED; 1229 hintView.measure(widthSpec, heightSpec); 1230 1231 hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams(); 1232 otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin 1233 + hintParams.bottomMargin; 1234 1235 dropDownView = hintContainer; 1236 } 1237 1238 mPopup.setContentView(dropDownView); 1239 } else { 1240 final View view = mPromptView; 1241 if (view != null) { 1242 LinearLayout.LayoutParams hintParams = 1243 (LinearLayout.LayoutParams) view.getLayoutParams(); 1244 otherHeights = view.getMeasuredHeight() + hintParams.topMargin 1245 + hintParams.bottomMargin; 1246 } 1247 } 1248 1249 // getMaxAvailableHeight() subtracts the padding, so we put it back 1250 // to get the available height for the whole window. 1251 final int padding; 1252 final Drawable background = mPopup.getBackground(); 1253 if (background != null) { 1254 background.getPadding(mTempRect); 1255 padding = mTempRect.top + mTempRect.bottom; 1256 1257 // If we don't have an explicit vertical offset, determine one from 1258 // the window background so that content will line up. 1259 if (!mDropDownVerticalOffsetSet) { 1260 mDropDownVerticalOffset = -mTempRect.top; 1261 } 1262 } else { 1263 mTempRect.setEmpty(); 1264 padding = 0; 1265 } 1266 1267 // Max height available on the screen for a popup. 1268 final boolean ignoreBottomDecorations = 1269 mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; 1270 final int maxHeight = mPopup.getMaxAvailableHeight( 1271 getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations); 1272 if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { 1273 return maxHeight + padding; 1274 } 1275 1276 final int childWidthSpec; 1277 switch (mDropDownWidth) { 1278 case ViewGroup.LayoutParams.WRAP_CONTENT: 1279 childWidthSpec = MeasureSpec.makeMeasureSpec( 1280 mContext.getResources().getDisplayMetrics().widthPixels 1281 - (mTempRect.left + mTempRect.right), 1282 MeasureSpec.AT_MOST); 1283 break; 1284 case ViewGroup.LayoutParams.MATCH_PARENT: 1285 childWidthSpec = MeasureSpec.makeMeasureSpec( 1286 mContext.getResources().getDisplayMetrics().widthPixels 1287 - (mTempRect.left + mTempRect.right), 1288 MeasureSpec.EXACTLY); 1289 break; 1290 default: 1291 childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY); 1292 break; 1293 } 1294 1295 // Add padding only if the list has items in it, that way we don't show 1296 // the popup if it is not needed. 1297 final int listContent = mDropDownList.measureHeightOfChildren(childWidthSpec, 1298 0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1); 1299 if (listContent > 0) { 1300 final int listPadding = mDropDownList.getPaddingTop() 1301 + mDropDownList.getPaddingBottom(); 1302 otherHeights += padding + listPadding; 1303 } 1304 1305 return listContent + otherHeights; 1306 } 1307 1308 /** 1309 * @hide 1310 */ setOverlapAnchor(boolean overlap)1311 public void setOverlapAnchor(boolean overlap) { 1312 mOverlapAnchorSet = true; 1313 mOverlapAnchor = overlap; 1314 } 1315 1316 private class PopupDataSetObserver extends DataSetObserver { 1317 @Override onChanged()1318 public void onChanged() { 1319 if (isShowing()) { 1320 // Resize the popup to fit new content 1321 show(); 1322 } 1323 } 1324 1325 @Override onInvalidated()1326 public void onInvalidated() { 1327 dismiss(); 1328 } 1329 } 1330 1331 private class ListSelectorHider implements Runnable { run()1332 public void run() { 1333 clearListSelection(); 1334 } 1335 } 1336 1337 private class ResizePopupRunnable implements Runnable { run()1338 public void run() { 1339 if (mDropDownList != null && mDropDownList.isAttachedToWindow() 1340 && mDropDownList.getCount() > mDropDownList.getChildCount() 1341 && mDropDownList.getChildCount() <= mListItemExpandMaximum) { 1342 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); 1343 show(); 1344 } 1345 } 1346 } 1347 1348 private class PopupTouchInterceptor implements OnTouchListener { onTouch(View v, MotionEvent event)1349 public boolean onTouch(View v, MotionEvent event) { 1350 final int action = event.getAction(); 1351 final int x = (int) event.getX(); 1352 final int y = (int) event.getY(); 1353 1354 if (action == MotionEvent.ACTION_DOWN && 1355 mPopup != null && mPopup.isShowing() && 1356 (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) { 1357 mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT); 1358 } else if (action == MotionEvent.ACTION_UP) { 1359 mHandler.removeCallbacks(mResizePopupRunnable); 1360 } 1361 return false; 1362 } 1363 } 1364 1365 private class PopupScrollListener implements ListView.OnScrollListener { onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)1366 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 1367 int totalItemCount) { 1368 1369 } 1370 onScrollStateChanged(AbsListView view, int scrollState)1371 public void onScrollStateChanged(AbsListView view, int scrollState) { 1372 if (scrollState == SCROLL_STATE_TOUCH_SCROLL && 1373 !isInputMethodNotNeeded() && mPopup.getContentView() != null) { 1374 mHandler.removeCallbacks(mResizePopupRunnable); 1375 mResizePopupRunnable.run(); 1376 } 1377 } 1378 } 1379 } 1380