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