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