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