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