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