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