PopupWindow.java revision c3808b5dc7d5873d04e8a0a247b179b2757764ba
1/* 2 * Copyright (C) 2007 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 com.android.internal.R; 20 21import android.content.Context; 22import android.content.res.TypedArray; 23import android.view.KeyEvent; 24import android.view.MotionEvent; 25import android.view.View; 26import android.view.WindowManager; 27import android.view.Gravity; 28import android.view.ViewGroup; 29import android.view.ViewTreeObserver; 30import android.view.ViewTreeObserver.OnScrollChangedListener; 31import android.view.View.OnTouchListener; 32import android.graphics.PixelFormat; 33import android.graphics.Rect; 34import android.graphics.drawable.Drawable; 35import android.graphics.drawable.StateListDrawable; 36import android.os.IBinder; 37import android.util.AttributeSet; 38 39import java.lang.ref.WeakReference; 40 41/** 42 * <p>A popup window that can be used to display an arbitrary view. The popup 43 * windows is a floating container that appears on top of the current 44 * activity.</p> 45 * 46 * @see android.widget.AutoCompleteTextView 47 * @see android.widget.Spinner 48 */ 49public class PopupWindow { 50 /** 51 * Mode for {@link #setInputMethodMode(int)}: the requirements for the 52 * input method should be based on the focusability of the popup. That is 53 * if it is focusable than it needs to work with the input method, else 54 * it doesn't. 55 */ 56 public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; 57 58 /** 59 * Mode for {@link #setInputMethodMode(int)}: this popup always needs to 60 * work with an input method, regardless of whether it is focusable. This 61 * means that it will always be displayed so that the user can also operate 62 * the input method while it is shown. 63 */ 64 public static final int INPUT_METHOD_NEEDED = 1; 65 66 /** 67 * Mode for {@link #setInputMethodMode(int)}: this popup never needs to 68 * work with an input method, regardless of whether it is focusable. This 69 * means that it will always be displayed to use as much space on the 70 * screen as needed, regardless of whether this covers the input method. 71 */ 72 public static final int INPUT_METHOD_NOT_NEEDED = 2; 73 74 private Context mContext; 75 private WindowManager mWindowManager; 76 77 private boolean mIsShowing; 78 private boolean mIsDropdown; 79 80 private View mContentView; 81 private View mPopupView; 82 private boolean mFocusable; 83 private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; 84 private int mSoftInputMode; 85 private boolean mTouchable = true; 86 private boolean mOutsideTouchable = false; 87 private boolean mClippingEnabled = true; 88 private boolean mSplitTouchEnabled; 89 private boolean mLayoutInScreen; 90 91 private OnTouchListener mTouchInterceptor; 92 93 private int mWidthMode; 94 private int mWidth; 95 private int mLastWidth; 96 private int mHeightMode; 97 private int mHeight; 98 private int mLastHeight; 99 100 private int mPopupWidth; 101 private int mPopupHeight; 102 103 private int[] mDrawingLocation = new int[2]; 104 private int[] mScreenLocation = new int[2]; 105 private Rect mTempRect = new Rect(); 106 107 private Drawable mBackground; 108 private Drawable mAboveAnchorBackgroundDrawable; 109 private Drawable mBelowAnchorBackgroundDrawable; 110 111 private boolean mAboveAnchor; 112 113 private OnDismissListener mOnDismissListener; 114 private boolean mIgnoreCheekPress = false; 115 116 private int mAnimationStyle = -1; 117 118 private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] { 119 com.android.internal.R.attr.state_above_anchor 120 }; 121 122 private WeakReference<View> mAnchor; 123 private OnScrollChangedListener mOnScrollChangedListener = 124 new OnScrollChangedListener() { 125 public void onScrollChanged() { 126 View anchor = mAnchor.get(); 127 if (anchor != null && mPopupView != null) { 128 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 129 mPopupView.getLayoutParams(); 130 131 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff)); 132 update(p.x, p.y, -1, -1, true); 133 } 134 } 135 }; 136 private int mAnchorXoff, mAnchorYoff; 137 138 /** 139 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 140 * 141 * <p>The popup does provide a background.</p> 142 */ 143 public PopupWindow(Context context) { 144 this(context, null); 145 } 146 147 /** 148 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 149 * 150 * <p>The popup does provide a background.</p> 151 */ 152 public PopupWindow(Context context, AttributeSet attrs) { 153 this(context, attrs, com.android.internal.R.attr.popupWindowStyle); 154 } 155 156 /** 157 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 158 * 159 * <p>The popup does provide a background.</p> 160 */ 161 public PopupWindow(Context context, AttributeSet attrs, int defStyle) { 162 mContext = context; 163 mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); 164 165 TypedArray a = 166 context.obtainStyledAttributes( 167 attrs, com.android.internal.R.styleable.PopupWindow, defStyle, 0); 168 169 mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground); 170 171 final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1); 172 mAnimationStyle = animStyle == com.android.internal.R.style.Animation_PopupWindow ? -1 : 173 animStyle; 174 175 // If this is a StateListDrawable, try to find and store the drawable to be 176 // used when the drop-down is placed above its anchor view, and the one to be 177 // used when the drop-down is placed below its anchor view. We extract 178 // the drawables ourselves to work around a problem with using refreshDrawableState 179 // that it will take into account the padding of all drawables specified in a 180 // StateListDrawable, thus adding superfluous padding to drop-down views. 181 // 182 // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and 183 // at least one other drawable, intended for the 'below-anchor state'. 184 if (mBackground instanceof StateListDrawable) { 185 StateListDrawable background = (StateListDrawable) mBackground; 186 187 // Find the above-anchor view - this one's easy, it should be labeled as such. 188 int aboveAnchorStateIndex = background.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET); 189 190 // Now, for the below-anchor view, look for any other drawable specified in the 191 // StateListDrawable which is not for the above-anchor state and use that. 192 int count = background.getStateCount(); 193 int belowAnchorStateIndex = -1; 194 for (int i = 0; i < count; i++) { 195 if (i != aboveAnchorStateIndex) { 196 belowAnchorStateIndex = i; 197 break; 198 } 199 } 200 201 // Store the drawables we found, if we found them. Otherwise, set them both 202 // to null so that we'll just use refreshDrawableState. 203 if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) { 204 mAboveAnchorBackgroundDrawable = background.getStateDrawable(aboveAnchorStateIndex); 205 mBelowAnchorBackgroundDrawable = background.getStateDrawable(belowAnchorStateIndex); 206 } else { 207 mBelowAnchorBackgroundDrawable = null; 208 mAboveAnchorBackgroundDrawable = null; 209 } 210 } 211 212 a.recycle(); 213 } 214 215 /** 216 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 217 * 218 * <p>The popup does not provide any background. This should be handled 219 * by the content view.</p> 220 */ 221 public PopupWindow() { 222 this(null, 0, 0); 223 } 224 225 /** 226 * <p>Create a new non focusable popup window which can display the 227 * <tt>contentView</tt>. The dimension of the window are (0,0).</p> 228 * 229 * <p>The popup does not provide any background. This should be handled 230 * by the content view.</p> 231 * 232 * @param contentView the popup's content 233 */ 234 public PopupWindow(View contentView) { 235 this(contentView, 0, 0); 236 } 237 238 /** 239 * <p>Create a new empty, non focusable popup window. The dimension of the 240 * window must be passed to this constructor.</p> 241 * 242 * <p>The popup does not provide any background. This should be handled 243 * by the content view.</p> 244 * 245 * @param width the popup's width 246 * @param height the popup's height 247 */ 248 public PopupWindow(int width, int height) { 249 this(null, width, height); 250 } 251 252 /** 253 * <p>Create a new non focusable popup window which can display the 254 * <tt>contentView</tt>. The dimension of the window must be passed to 255 * this constructor.</p> 256 * 257 * <p>The popup does not provide any background. This should be handled 258 * by the content view.</p> 259 * 260 * @param contentView the popup's content 261 * @param width the popup's width 262 * @param height the popup's height 263 */ 264 public PopupWindow(View contentView, int width, int height) { 265 this(contentView, width, height, false); 266 } 267 268 /** 269 * <p>Create a new popup window which can display the <tt>contentView</tt>. 270 * The dimension of the window must be passed to this constructor.</p> 271 * 272 * <p>The popup does not provide any background. This should be handled 273 * by the content view.</p> 274 * 275 * @param contentView the popup's content 276 * @param width the popup's width 277 * @param height the popup's height 278 * @param focusable true if the popup can be focused, false otherwise 279 */ 280 public PopupWindow(View contentView, int width, int height, boolean focusable) { 281 if (contentView != null) { 282 mContext = contentView.getContext(); 283 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 284 } 285 setContentView(contentView); 286 setWidth(width); 287 setHeight(height); 288 setFocusable(focusable); 289 } 290 291 /** 292 * <p>Return the drawable used as the popup window's background.</p> 293 * 294 * @return the background drawable or null 295 */ 296 public Drawable getBackground() { 297 return mBackground; 298 } 299 300 /** 301 * <p>Change the background drawable for this popup window. The background 302 * can be set to null.</p> 303 * 304 * @param background the popup's background 305 */ 306 public void setBackgroundDrawable(Drawable background) { 307 mBackground = background; 308 } 309 310 /** 311 * <p>Return the animation style to use the popup appears and disappears</p> 312 * 313 * @return the animation style to use the popup appears and disappears 314 */ 315 public int getAnimationStyle() { 316 return mAnimationStyle; 317 } 318 319 /** 320 * Set the flag on popup to ignore cheek press eventt; by default this flag 321 * is set to false 322 * which means the pop wont ignore cheek press dispatch events. 323 * 324 * <p>If the popup is showing, calling this method will take effect only 325 * the next time the popup is shown or through a manual call to one of 326 * the {@link #update()} methods.</p> 327 * 328 * @see #update() 329 */ 330 public void setIgnoreCheekPress() { 331 mIgnoreCheekPress = true; 332 } 333 334 335 /** 336 * <p>Change the animation style resource for this popup.</p> 337 * 338 * <p>If the popup is showing, calling this method will take effect only 339 * the next time the popup is shown or through a manual call to one of 340 * the {@link #update()} methods.</p> 341 * 342 * @param animationStyle animation style to use when the popup appears 343 * and disappears. Set to -1 for the default animation, 0 for no 344 * animation, or a resource identifier for an explicit animation. 345 * 346 * @see #update() 347 */ 348 public void setAnimationStyle(int animationStyle) { 349 mAnimationStyle = animationStyle; 350 } 351 352 /** 353 * <p>Return the view used as the content of the popup window.</p> 354 * 355 * @return a {@link android.view.View} representing the popup's content 356 * 357 * @see #setContentView(android.view.View) 358 */ 359 public View getContentView() { 360 return mContentView; 361 } 362 363 /** 364 * <p>Change the popup's content. The content is represented by an instance 365 * of {@link android.view.View}.</p> 366 * 367 * <p>This method has no effect if called when the popup is showing. To 368 * apply it while a popup is showing, call </p> 369 * 370 * @param contentView the new content for the popup 371 * 372 * @see #getContentView() 373 * @see #isShowing() 374 */ 375 public void setContentView(View contentView) { 376 if (isShowing()) { 377 return; 378 } 379 380 mContentView = contentView; 381 382 if (mContext == null) { 383 mContext = mContentView.getContext(); 384 } 385 386 if (mWindowManager == null) { 387 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 388 } 389 } 390 391 /** 392 * Set a callback for all touch events being dispatched to the popup 393 * window. 394 */ 395 public void setTouchInterceptor(OnTouchListener l) { 396 mTouchInterceptor = l; 397 } 398 399 /** 400 * <p>Indicate whether the popup window can grab the focus.</p> 401 * 402 * @return true if the popup is focusable, false otherwise 403 * 404 * @see #setFocusable(boolean) 405 */ 406 public boolean isFocusable() { 407 return mFocusable; 408 } 409 410 /** 411 * <p>Changes the focusability of the popup window. When focusable, the 412 * window will grab the focus from the current focused widget if the popup 413 * contains a focusable {@link android.view.View}. By default a popup 414 * window is not focusable.</p> 415 * 416 * <p>If the popup is showing, calling this method will take effect only 417 * the next time the popup is shown or through a manual call to one of 418 * the {@link #update()} methods.</p> 419 * 420 * @param focusable true if the popup should grab focus, false otherwise. 421 * 422 * @see #isFocusable() 423 * @see #isShowing() 424 * @see #update() 425 */ 426 public void setFocusable(boolean focusable) { 427 mFocusable = focusable; 428 } 429 430 /** 431 * Return the current value in {@link #setInputMethodMode(int)}. 432 * 433 * @see #setInputMethodMode(int) 434 */ 435 public int getInputMethodMode() { 436 return mInputMethodMode; 437 438 } 439 440 /** 441 * Control how the popup operates with an input method: one of 442 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, 443 * or {@link #INPUT_METHOD_NOT_NEEDED}. 444 * 445 * <p>If the popup is showing, calling this method will take effect only 446 * the next time the popup is shown or through a manual call to one of 447 * the {@link #update()} methods.</p> 448 * 449 * @see #getInputMethodMode() 450 * @see #update() 451 */ 452 public void setInputMethodMode(int mode) { 453 mInputMethodMode = mode; 454 } 455 456 /** 457 * Sets the operating mode for the soft input area. 458 * 459 * @param mode The desired mode, see 460 * {@link android.view.WindowManager.LayoutParams#softInputMode} 461 * for the full list 462 * 463 * @see android.view.WindowManager.LayoutParams#softInputMode 464 * @see #getSoftInputMode() 465 */ 466 public void setSoftInputMode(int mode) { 467 mSoftInputMode = mode; 468 } 469 470 /** 471 * Returns the current value in {@link #setSoftInputMode(int)}. 472 * 473 * @see #setSoftInputMode(int) 474 * @see android.view.WindowManager.LayoutParams#softInputMode 475 */ 476 public int getSoftInputMode() { 477 return mSoftInputMode; 478 } 479 480 /** 481 * <p>Indicates whether the popup window receives touch events.</p> 482 * 483 * @return true if the popup is touchable, false otherwise 484 * 485 * @see #setTouchable(boolean) 486 */ 487 public boolean isTouchable() { 488 return mTouchable; 489 } 490 491 /** 492 * <p>Changes the touchability of the popup window. When touchable, the 493 * window will receive touch events, otherwise touch events will go to the 494 * window below it. By default the window is touchable.</p> 495 * 496 * <p>If the popup is showing, calling this method will take effect only 497 * the next time the popup is shown or through a manual call to one of 498 * the {@link #update()} methods.</p> 499 * 500 * @param touchable true if the popup should receive touch events, false otherwise 501 * 502 * @see #isTouchable() 503 * @see #isShowing() 504 * @see #update() 505 */ 506 public void setTouchable(boolean touchable) { 507 mTouchable = touchable; 508 } 509 510 /** 511 * <p>Indicates whether the popup window will be informed of touch events 512 * outside of its window.</p> 513 * 514 * @return true if the popup is outside touchable, false otherwise 515 * 516 * @see #setOutsideTouchable(boolean) 517 */ 518 public boolean isOutsideTouchable() { 519 return mOutsideTouchable; 520 } 521 522 /** 523 * <p>Controls whether the pop-up will be informed of touch events outside 524 * of its window. This only makes sense for pop-ups that are touchable 525 * but not focusable, which means touches outside of the window will 526 * be delivered to the window behind. The default is false.</p> 527 * 528 * <p>If the popup is showing, calling this method will take effect only 529 * the next time the popup is shown or through a manual call to one of 530 * the {@link #update()} methods.</p> 531 * 532 * @param touchable true if the popup should receive outside 533 * touch events, false otherwise 534 * 535 * @see #isOutsideTouchable() 536 * @see #isShowing() 537 * @see #update() 538 */ 539 public void setOutsideTouchable(boolean touchable) { 540 mOutsideTouchable = touchable; 541 } 542 543 /** 544 * <p>Indicates whether clipping of the popup window is enabled.</p> 545 * 546 * @return true if the clipping is enabled, false otherwise 547 * 548 * @see #setClippingEnabled(boolean) 549 */ 550 public boolean isClippingEnabled() { 551 return mClippingEnabled; 552 } 553 554 /** 555 * <p>Allows the popup window to extend beyond the bounds of the screen. By default the 556 * window is clipped to the screen boundaries. Setting this to false will allow windows to be 557 * accurately positioned.</p> 558 * 559 * <p>If the popup is showing, calling this method will take effect only 560 * the next time the popup is shown or through a manual call to one of 561 * the {@link #update()} methods.</p> 562 * 563 * @param enabled false if the window should be allowed to extend outside of the screen 564 * @see #isShowing() 565 * @see #isClippingEnabled() 566 * @see #update() 567 */ 568 public void setClippingEnabled(boolean enabled) { 569 mClippingEnabled = enabled; 570 } 571 572 /** 573 * <p>Indicates whether the popup window supports splitting touches.</p> 574 * 575 * @return true if the touch splitting is enabled, false otherwise 576 * 577 * @see #setSplitTouchEnabled(boolean) 578 * @hide 579 */ 580 public boolean isSplitTouchEnabled() { 581 return mSplitTouchEnabled; 582 } 583 584 /** 585 * <p>Allows the popup window to split touches across other windows that also 586 * support split touch. When this flag is not set, the first pointer 587 * that goes down determines the window to which all subsequent touches 588 * go until all pointers go up. When this flag is set, each pointer 589 * (not necessarily the first) that goes down determines the window 590 * to which all subsequent touches of that pointer will go until that 591 * pointer goes up thereby enabling touches with multiple pointers 592 * to be split across multiple windows.</p> 593 * 594 * @param enabled true if the split touches should be enabled, false otherwise 595 * @see #isSplitTouchEnabled() 596 * @hide 597 */ 598 public void setSplitTouchEnabled(boolean enabled) { 599 mSplitTouchEnabled = enabled; 600 } 601 602 /** 603 * <p>Indicates whether the popup window will be forced into using absolute screen coordinates 604 * for positioning.</p> 605 * 606 * @return true if the window will always be positioned in screen coordinates. 607 * @hide 608 */ 609 public boolean isLayoutInScreenEnabled() { 610 return mLayoutInScreen; 611 } 612 613 /** 614 * <p>Allows the popup window to force the flag 615 * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior. 616 * This will cause the popup to be positioned in absolute screen coordinates.</p> 617 * 618 * @param enabled true if the popup should always be positioned in screen coordinates 619 * @hide 620 */ 621 public void setLayoutInScreenEnabled(boolean enabled) { 622 mLayoutInScreen = enabled; 623 } 624 625 /** 626 * <p>Change the width and height measure specs that are given to the 627 * window manager by the popup. By default these are 0, meaning that 628 * the current width or height is requested as an explicit size from 629 * the window manager. You can supply 630 * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or 631 * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure 632 * spec supplied instead, replacing the absolute width and height that 633 * has been set in the popup.</p> 634 * 635 * <p>If the popup is showing, calling this method will take effect only 636 * the next time the popup is shown.</p> 637 * 638 * @param widthSpec an explicit width measure spec mode, either 639 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 640 * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute 641 * width. 642 * @param heightSpec an explicit height measure spec mode, either 643 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 644 * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute 645 * height. 646 */ 647 public void setWindowLayoutMode(int widthSpec, int heightSpec) { 648 mWidthMode = widthSpec; 649 mHeightMode = heightSpec; 650 } 651 652 /** 653 * <p>Return this popup's height MeasureSpec</p> 654 * 655 * @return the height MeasureSpec of the popup 656 * 657 * @see #setHeight(int) 658 */ 659 public int getHeight() { 660 return mHeight; 661 } 662 663 /** 664 * <p>Change the popup's height MeasureSpec</p> 665 * 666 * <p>If the popup is showing, calling this method will take effect only 667 * the next time the popup is shown.</p> 668 * 669 * @param height the height MeasureSpec of the popup 670 * 671 * @see #getHeight() 672 * @see #isShowing() 673 */ 674 public void setHeight(int height) { 675 mHeight = height; 676 } 677 678 /** 679 * <p>Return this popup's width MeasureSpec</p> 680 * 681 * @return the width MeasureSpec of the popup 682 * 683 * @see #setWidth(int) 684 */ 685 public int getWidth() { 686 return mWidth; 687 } 688 689 /** 690 * <p>Change the popup's width MeasureSpec</p> 691 * 692 * <p>If the popup is showing, calling this method will take effect only 693 * the next time the popup is shown.</p> 694 * 695 * @param width the width MeasureSpec of the popup 696 * 697 * @see #getWidth() 698 * @see #isShowing() 699 */ 700 public void setWidth(int width) { 701 mWidth = width; 702 } 703 704 /** 705 * <p>Indicate whether this popup window is showing on screen.</p> 706 * 707 * @return true if the popup is showing, false otherwise 708 */ 709 public boolean isShowing() { 710 return mIsShowing; 711 } 712 713 /** 714 * <p> 715 * Display the content view in a popup window at the specified location. If the popup window 716 * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams} 717 * for more information on how gravity and the x and y parameters are related. Specifying 718 * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying 719 * <code>Gravity.LEFT | Gravity.TOP</code>. 720 * </p> 721 * 722 * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from 723 * @param gravity the gravity which controls the placement of the popup window 724 * @param x the popup's x location offset 725 * @param y the popup's y location offset 726 */ 727 public void showAtLocation(View parent, int gravity, int x, int y) { 728 if (isShowing() || mContentView == null) { 729 return; 730 } 731 732 unregisterForScrollChanged(); 733 734 mIsShowing = true; 735 mIsDropdown = false; 736 737 WindowManager.LayoutParams p = createPopupLayout(parent.getWindowToken()); 738 p.windowAnimations = computeAnimationResource(); 739 740 preparePopup(p); 741 if (gravity == Gravity.NO_GRAVITY) { 742 gravity = Gravity.TOP | Gravity.LEFT; 743 } 744 p.gravity = gravity; 745 p.x = x; 746 p.y = y; 747 invokePopup(p); 748 } 749 750 /** 751 * <p>Display the content view in a popup window anchored to the bottom-left 752 * corner of the anchor view. If there is not enough room on screen to show 753 * the popup in its entirety, this method tries to find a parent scroll 754 * view to scroll. If no parent scroll view can be scrolled, the bottom-left 755 * corner of the popup is pinned at the top left corner of the anchor view.</p> 756 * 757 * @param anchor the view on which to pin the popup window 758 * 759 * @see #dismiss() 760 */ 761 public void showAsDropDown(View anchor) { 762 showAsDropDown(anchor, 0, 0); 763 } 764 765 /** 766 * <p>Display the content view in a popup window anchored to the bottom-left 767 * corner of the anchor view offset by the specified x and y coordinates. 768 * If there is not enough room on screen to show 769 * the popup in its entirety, this method tries to find a parent scroll 770 * view to scroll. If no parent scroll view can be scrolled, the bottom-left 771 * corner of the popup is pinned at the top left corner of the anchor view.</p> 772 * <p>If the view later scrolls to move <code>anchor</code> to a different 773 * location, the popup will be moved correspondingly.</p> 774 * 775 * @param anchor the view on which to pin the popup window 776 * 777 * @see #dismiss() 778 */ 779 public void showAsDropDown(View anchor, int xoff, int yoff) { 780 if (isShowing() || mContentView == null) { 781 return; 782 } 783 784 registerForScrollChanged(anchor, xoff, yoff); 785 786 mIsShowing = true; 787 mIsDropdown = true; 788 789 WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken()); 790 preparePopup(p); 791 792 updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff)); 793 794 if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; 795 if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; 796 797 p.windowAnimations = computeAnimationResource(); 798 799 invokePopup(p); 800 } 801 802 private void updateAboveAnchor(boolean aboveAnchor) { 803 if (aboveAnchor != mAboveAnchor) { 804 mAboveAnchor = aboveAnchor; 805 806 if (mBackground != null) { 807 // If the background drawable provided was a StateListDrawable with above-anchor 808 // and below-anchor states, use those. Otherwise rely on refreshDrawableState to 809 // do the job. 810 if (mAboveAnchorBackgroundDrawable != null) { 811 if (mAboveAnchor) { 812 mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable); 813 } else { 814 mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable); 815 } 816 } else { 817 mPopupView.refreshDrawableState(); 818 } 819 } 820 } 821 } 822 823 /** 824 * Indicates whether the popup is showing above (the y coordinate of the popup's bottom 825 * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate 826 * of the popup is greater than y coordinate of the anchor's bottom). 827 * 828 * The value returned 829 * by this method is meaningful only after {@link #showAsDropDown(android.view.View)} 830 * or {@link #showAsDropDown(android.view.View, int, int)} was invoked. 831 * 832 * @return True if this popup is showing above the anchor view, false otherwise. 833 */ 834 public boolean isAboveAnchor() { 835 return mAboveAnchor; 836 } 837 838 /** 839 * <p>Prepare the popup by embedding in into a new ViewGroup if the 840 * background drawable is not null. If embedding is required, the layout 841 * parameters' height is mnodified to take into account the background's 842 * padding.</p> 843 * 844 * @param p the layout parameters of the popup's content view 845 */ 846 private void preparePopup(WindowManager.LayoutParams p) { 847 if (mContentView == null || mContext == null || mWindowManager == null) { 848 throw new IllegalStateException("You must specify a valid content view by " 849 + "calling setContentView() before attempting to show the popup."); 850 } 851 852 if (mBackground != null) { 853 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 854 int height = ViewGroup.LayoutParams.MATCH_PARENT; 855 if (layoutParams != null && 856 layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { 857 height = ViewGroup.LayoutParams.WRAP_CONTENT; 858 } 859 860 // when a background is available, we embed the content view 861 // within another view that owns the background drawable 862 PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); 863 PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( 864 ViewGroup.LayoutParams.MATCH_PARENT, height 865 ); 866 popupViewContainer.setBackgroundDrawable(mBackground); 867 popupViewContainer.addView(mContentView, listParams); 868 869 mPopupView = popupViewContainer; 870 } else { 871 mPopupView = mContentView; 872 } 873 mPopupWidth = p.width; 874 mPopupHeight = p.height; 875 } 876 877 /** 878 * <p>Invoke the popup window by adding the content view to the window 879 * manager.</p> 880 * 881 * <p>The content view must be non-null when this method is invoked.</p> 882 * 883 * @param p the layout parameters of the popup's content view 884 */ 885 private void invokePopup(WindowManager.LayoutParams p) { 886 p.packageName = mContext.getPackageName(); 887 mWindowManager.addView(mPopupView, p); 888 } 889 890 /** 891 * <p>Generate the layout parameters for the popup window.</p> 892 * 893 * @param token the window token used to bind the popup's window 894 * 895 * @return the layout parameters to pass to the window manager 896 */ 897 private WindowManager.LayoutParams createPopupLayout(IBinder token) { 898 // generates the layout parameters for the drop down 899 // we want a fixed size view located at the bottom left of the anchor 900 WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 901 // these gravity settings put the view at the top left corner of the 902 // screen. The view is then positioned to the appropriate location 903 // by setting the x and y offsets to match the anchor's bottom 904 // left corner 905 p.gravity = Gravity.LEFT | Gravity.TOP; 906 p.width = mLastWidth = mWidth; 907 p.height = mLastHeight = mHeight; 908 if (mBackground != null) { 909 p.format = mBackground.getOpacity(); 910 } else { 911 p.format = PixelFormat.TRANSLUCENT; 912 } 913 p.flags = computeFlags(p.flags); 914 p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 915 p.token = token; 916 p.softInputMode = mSoftInputMode; 917 p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); 918 919 return p; 920 } 921 922 private int computeFlags(int curFlags) { 923 curFlags &= ~( 924 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | 925 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 926 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 927 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 928 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 929 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | 930 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH); 931 if(mIgnoreCheekPress) { 932 curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 933 } 934 if (!mFocusable) { 935 curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 936 if (mInputMethodMode == INPUT_METHOD_NEEDED) { 937 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 938 } 939 } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { 940 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 941 } 942 if (!mTouchable) { 943 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 944 } 945 if (mOutsideTouchable) { 946 curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 947 } 948 if (!mClippingEnabled) { 949 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 950 } 951 if (mSplitTouchEnabled) { 952 curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 953 } 954 if (mLayoutInScreen) { 955 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 956 } 957 return curFlags; 958 } 959 960 private int computeAnimationResource() { 961 if (mAnimationStyle == -1) { 962 if (mIsDropdown) { 963 return mAboveAnchor 964 ? com.android.internal.R.style.Animation_DropDownUp 965 : com.android.internal.R.style.Animation_DropDownDown; 966 } 967 return 0; 968 } 969 return mAnimationStyle; 970 } 971 972 /** 973 * <p>Positions the popup window on screen. When the popup window is too 974 * tall to fit under the anchor, a parent scroll view is seeked and scrolled 975 * up to reclaim space. If scrolling is not possible or not enough, the 976 * popup window gets moved on top of the anchor.</p> 977 * 978 * <p>The height must have been set on the layout parameters prior to 979 * calling this method.</p> 980 * 981 * @param anchor the view on which the popup window must be anchored 982 * @param p the layout parameters used to display the drop down 983 * 984 * @return true if the popup is translated upwards to fit on screen 985 */ 986 private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, 987 int xoff, int yoff) { 988 989 anchor.getLocationInWindow(mDrawingLocation); 990 p.x = mDrawingLocation[0] + xoff; 991 p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; 992 993 boolean onTop = false; 994 995 p.gravity = Gravity.LEFT | Gravity.TOP; 996 997 anchor.getLocationOnScreen(mScreenLocation); 998 final Rect displayFrame = new Rect(); 999 anchor.getWindowVisibleDisplayFrame(displayFrame); 1000 1001 final View root = anchor.getRootView(); 1002 if (p.y + mPopupHeight > displayFrame.bottom || p.x + mPopupWidth - root.getWidth() > 0) { 1003 // if the drop down disappears at the bottom of the screen. we try to 1004 // scroll a parent scrollview or move the drop down back up on top of 1005 // the edit box 1006 int scrollX = anchor.getScrollX(); 1007 int scrollY = anchor.getScrollY(); 1008 Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff, 1009 scrollY + mPopupHeight + anchor.getHeight() + yoff); 1010 anchor.requestRectangleOnScreen(r, true); 1011 1012 // now we re-evaluate the space available, and decide from that 1013 // whether the pop-up will go above or below the anchor. 1014 anchor.getLocationInWindow(mDrawingLocation); 1015 p.x = mDrawingLocation[0] + xoff; 1016 p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; 1017 1018 // determine whether there is more space above or below the anchor 1019 anchor.getLocationOnScreen(mScreenLocation); 1020 1021 onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getHeight() - yoff) < 1022 (mScreenLocation[1] - yoff - displayFrame.top); 1023 if (onTop) { 1024 p.gravity = Gravity.LEFT | Gravity.BOTTOM; 1025 p.y = root.getHeight() - mDrawingLocation[1] + yoff; 1026 } else { 1027 p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; 1028 } 1029 } 1030 1031 p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; 1032 1033 return onTop; 1034 } 1035 1036 /** 1037 * Returns the maximum height that is available for the popup to be 1038 * completely shown. It is recommended that this height be the maximum for 1039 * the popup's height, otherwise it is possible that the popup will be 1040 * clipped. 1041 * 1042 * @param anchor The view on which the popup window must be anchored. 1043 * @return The maximum available height for the popup to be completely 1044 * shown. 1045 */ 1046 public int getMaxAvailableHeight(View anchor) { 1047 return getMaxAvailableHeight(anchor, 0); 1048 } 1049 1050 /** 1051 * Returns the maximum height that is available for the popup to be 1052 * completely shown. It is recommended that this height be the maximum for 1053 * the popup's height, otherwise it is possible that the popup will be 1054 * clipped. 1055 * 1056 * @param anchor The view on which the popup window must be anchored. 1057 * @param yOffset y offset from the view's bottom edge 1058 * @return The maximum available height for the popup to be completely 1059 * shown. 1060 */ 1061 public int getMaxAvailableHeight(View anchor, int yOffset) { 1062 return getMaxAvailableHeight(anchor, yOffset, false); 1063 } 1064 1065 /** 1066 * Returns the maximum height that is available for the popup to be 1067 * completely shown, optionally ignoring any bottom decorations such as 1068 * the input method. It is recommended that this height be the maximum for 1069 * the popup's height, otherwise it is possible that the popup will be 1070 * clipped. 1071 * 1072 * @param anchor The view on which the popup window must be anchored. 1073 * @param yOffset y offset from the view's bottom edge 1074 * @param ignoreBottomDecorations if true, the height returned will be 1075 * all the way to the bottom of the display, ignoring any 1076 * bottom decorations 1077 * @return The maximum available height for the popup to be completely 1078 * shown. 1079 * 1080 * @hide Pending API council approval. 1081 */ 1082 public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) { 1083 final Rect displayFrame = new Rect(); 1084 anchor.getWindowVisibleDisplayFrame(displayFrame); 1085 1086 final int[] anchorPos = mDrawingLocation; 1087 anchor.getLocationOnScreen(anchorPos); 1088 1089 int bottomEdge = displayFrame.bottom; 1090 if (ignoreBottomDecorations) { 1091 bottomEdge = anchor.getContext().getResources().getDisplayMetrics().heightPixels; 1092 } 1093 final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; 1094 final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; 1095 1096 // anchorPos[1] is distance from anchor to top of screen 1097 int returnedHeight = Math.max(distanceToBottom, distanceToTop); 1098 if (mBackground != null) { 1099 mBackground.getPadding(mTempRect); 1100 returnedHeight -= mTempRect.top + mTempRect.bottom; 1101 } 1102 1103 return returnedHeight; 1104 } 1105 1106 /** 1107 * <p>Dispose of the popup window. This method can be invoked only after 1108 * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling 1109 * this method will have no effect.</p> 1110 * 1111 * @see #showAsDropDown(android.view.View) 1112 */ 1113 public void dismiss() { 1114 if (isShowing() && mPopupView != null) { 1115 unregisterForScrollChanged(); 1116 1117 try { 1118 mWindowManager.removeView(mPopupView); 1119 } finally { 1120 if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { 1121 ((ViewGroup) mPopupView).removeView(mContentView); 1122 } 1123 mPopupView = null; 1124 mIsShowing = false; 1125 1126 if (mOnDismissListener != null) { 1127 mOnDismissListener.onDismiss(); 1128 } 1129 } 1130 } 1131 } 1132 1133 /** 1134 * Sets the listener to be called when the window is dismissed. 1135 * 1136 * @param onDismissListener The listener. 1137 */ 1138 public void setOnDismissListener(OnDismissListener onDismissListener) { 1139 mOnDismissListener = onDismissListener; 1140 } 1141 1142 /** 1143 * Updates the state of the popup window, if it is currently being displayed, 1144 * from the currently set state. This include: 1145 * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)}, 1146 * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)}, 1147 * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}. 1148 */ 1149 public void update() { 1150 if (!isShowing() || mContentView == null) { 1151 return; 1152 } 1153 1154 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1155 mPopupView.getLayoutParams(); 1156 1157 boolean update = false; 1158 1159 final int newAnim = computeAnimationResource(); 1160 if (newAnim != p.windowAnimations) { 1161 p.windowAnimations = newAnim; 1162 update = true; 1163 } 1164 1165 final int newFlags = computeFlags(p.flags); 1166 if (newFlags != p.flags) { 1167 p.flags = newFlags; 1168 update = true; 1169 } 1170 1171 if (update) { 1172 mWindowManager.updateViewLayout(mPopupView, p); 1173 } 1174 } 1175 1176 /** 1177 * <p>Updates the dimension of the popup window. Calling this function 1178 * also updates the window with the current popup state as described 1179 * for {@link #update()}.</p> 1180 * 1181 * @param width the new width 1182 * @param height the new height 1183 */ 1184 public void update(int width, int height) { 1185 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1186 mPopupView.getLayoutParams(); 1187 update(p.x, p.y, width, height, false); 1188 } 1189 1190 /** 1191 * <p>Updates the position and the dimension of the popup window. Width and 1192 * height can be set to -1 to update location only. Calling this function 1193 * also updates the window with the current popup state as 1194 * described for {@link #update()}.</p> 1195 * 1196 * @param x the new x location 1197 * @param y the new y location 1198 * @param width the new width, can be -1 to ignore 1199 * @param height the new height, can be -1 to ignore 1200 */ 1201 public void update(int x, int y, int width, int height) { 1202 update(x, y, width, height, false); 1203 } 1204 1205 /** 1206 * <p>Updates the position and the dimension of the popup window. Width and 1207 * height can be set to -1 to update location only. Calling this function 1208 * also updates the window with the current popup state as 1209 * described for {@link #update()}.</p> 1210 * 1211 * @param x the new x location 1212 * @param y the new y location 1213 * @param width the new width, can be -1 to ignore 1214 * @param height the new height, can be -1 to ignore 1215 * @param force reposition the window even if the specified position 1216 * already seems to correspond to the LayoutParams 1217 */ 1218 public void update(int x, int y, int width, int height, boolean force) { 1219 if (width != -1) { 1220 mLastWidth = width; 1221 setWidth(width); 1222 } 1223 1224 if (height != -1) { 1225 mLastHeight = height; 1226 setHeight(height); 1227 } 1228 1229 if (!isShowing() || mContentView == null) { 1230 return; 1231 } 1232 1233 WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); 1234 1235 boolean update = force; 1236 1237 final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; 1238 if (width != -1 && p.width != finalWidth) { 1239 p.width = mLastWidth = finalWidth; 1240 update = true; 1241 } 1242 1243 final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; 1244 if (height != -1 && p.height != finalHeight) { 1245 p.height = mLastHeight = finalHeight; 1246 update = true; 1247 } 1248 1249 if (p.x != x) { 1250 p.x = x; 1251 update = true; 1252 } 1253 1254 if (p.y != y) { 1255 p.y = y; 1256 update = true; 1257 } 1258 1259 final int newAnim = computeAnimationResource(); 1260 if (newAnim != p.windowAnimations) { 1261 p.windowAnimations = newAnim; 1262 update = true; 1263 } 1264 1265 final int newFlags = computeFlags(p.flags); 1266 if (newFlags != p.flags) { 1267 p.flags = newFlags; 1268 update = true; 1269 } 1270 1271 if (update) { 1272 mWindowManager.updateViewLayout(mPopupView, p); 1273 } 1274 } 1275 1276 /** 1277 * <p>Updates the position and the dimension of the popup window. Calling this 1278 * function also updates the window with the current popup state as described 1279 * for {@link #update()}.</p> 1280 * 1281 * @param anchor the popup's anchor view 1282 * @param width the new width, can be -1 to ignore 1283 * @param height the new height, can be -1 to ignore 1284 */ 1285 public void update(View anchor, int width, int height) { 1286 update(anchor, false, 0, 0, true, width, height); 1287 } 1288 1289 /** 1290 * <p>Updates the position and the dimension of the popup window. Width and 1291 * height can be set to -1 to update location only. Calling this function 1292 * also updates the window with the current popup state as 1293 * described for {@link #update()}.</p> 1294 * <p>If the view later scrolls to move <code>anchor</code> to a different 1295 * location, the popup will be moved correspondingly.</p> 1296 * 1297 * @param anchor the popup's anchor view 1298 * @param xoff x offset from the view's left edge 1299 * @param yoff y offset from the view's bottom edge 1300 * @param width the new width, can be -1 to ignore 1301 * @param height the new height, can be -1 to ignore 1302 */ 1303 public void update(View anchor, int xoff, int yoff, int width, int height) { 1304 update(anchor, true, xoff, yoff, true, width, height); 1305 } 1306 1307 private void update(View anchor, boolean updateLocation, int xoff, int yoff, 1308 boolean updateDimension, int width, int height) { 1309 1310 if (!isShowing() || mContentView == null) { 1311 return; 1312 } 1313 1314 WeakReference<View> oldAnchor = mAnchor; 1315 if (oldAnchor == null || oldAnchor.get() != anchor || 1316 (updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff))) { 1317 registerForScrollChanged(anchor, xoff, yoff); 1318 } 1319 1320 WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); 1321 1322 if (updateDimension) { 1323 if (width == -1) { 1324 width = mPopupWidth; 1325 } else { 1326 mPopupWidth = width; 1327 } 1328 if (height == -1) { 1329 height = mPopupHeight; 1330 } else { 1331 mPopupHeight = height; 1332 } 1333 } 1334 1335 int x = p.x; 1336 int y = p.y; 1337 1338 if (updateLocation) { 1339 updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff)); 1340 } else { 1341 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff)); 1342 } 1343 1344 update(p.x, p.y, width, height, x != p.x || y != p.y); 1345 } 1346 1347 /** 1348 * Listener that is called when this popup window is dismissed. 1349 */ 1350 public interface OnDismissListener { 1351 /** 1352 * Called when this popup window is dismissed. 1353 */ 1354 public void onDismiss(); 1355 } 1356 1357 private void unregisterForScrollChanged() { 1358 WeakReference<View> anchorRef = mAnchor; 1359 View anchor = null; 1360 if (anchorRef != null) { 1361 anchor = anchorRef.get(); 1362 } 1363 if (anchor != null) { 1364 ViewTreeObserver vto = anchor.getViewTreeObserver(); 1365 vto.removeOnScrollChangedListener(mOnScrollChangedListener); 1366 } 1367 mAnchor = null; 1368 } 1369 1370 private void registerForScrollChanged(View anchor, int xoff, int yoff) { 1371 unregisterForScrollChanged(); 1372 1373 mAnchor = new WeakReference<View>(anchor); 1374 ViewTreeObserver vto = anchor.getViewTreeObserver(); 1375 if (vto != null) { 1376 vto.addOnScrollChangedListener(mOnScrollChangedListener); 1377 } 1378 1379 mAnchorXoff = xoff; 1380 mAnchorYoff = yoff; 1381 } 1382 1383 private class PopupViewContainer extends FrameLayout { 1384 1385 public PopupViewContainer(Context context) { 1386 super(context); 1387 } 1388 1389 @Override 1390 protected int[] onCreateDrawableState(int extraSpace) { 1391 if (mAboveAnchor) { 1392 // 1 more needed for the above anchor state 1393 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 1394 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); 1395 return drawableState; 1396 } else { 1397 return super.onCreateDrawableState(extraSpace); 1398 } 1399 } 1400 1401 @Override 1402 public boolean dispatchKeyEvent(KeyEvent event) { 1403 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1404 if (event.getAction() == KeyEvent.ACTION_DOWN 1405 && event.getRepeatCount() == 0) { 1406 getKeyDispatcherState().startTracking(event, this); 1407 return true; 1408 } else if (event.getAction() == KeyEvent.ACTION_UP 1409 && getKeyDispatcherState().isTracking(event) && !event.isCanceled()) { 1410 dismiss(); 1411 return true; 1412 } 1413 return super.dispatchKeyEvent(event); 1414 } else { 1415 return super.dispatchKeyEvent(event); 1416 } 1417 } 1418 1419 @Override 1420 public boolean dispatchTouchEvent(MotionEvent ev) { 1421 if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { 1422 return true; 1423 } 1424 return super.dispatchTouchEvent(ev); 1425 } 1426 1427 @Override 1428 public boolean onTouchEvent(MotionEvent event) { 1429 final int x = (int) event.getX(); 1430 final int y = (int) event.getY(); 1431 1432 if ((event.getAction() == MotionEvent.ACTION_DOWN) 1433 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 1434 dismiss(); 1435 return true; 1436 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 1437 dismiss(); 1438 return true; 1439 } else { 1440 return super.onTouchEvent(event); 1441 } 1442 } 1443 1444 @Override 1445 public void sendAccessibilityEvent(int eventType) { 1446 // clinets are interested in the content not the container, make it event source 1447 if (mContentView != null) { 1448 mContentView.sendAccessibilityEvent(eventType); 1449 } else { 1450 super.sendAccessibilityEvent(eventType); 1451 } 1452 } 1453 } 1454 1455} 1456