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