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