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 return p; 1100 } 1101 1102 private int computeFlags(int curFlags) { 1103 curFlags &= ~( 1104 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | 1105 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 1106 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 1107 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 1108 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 1109 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | 1110 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH); 1111 if(mIgnoreCheekPress) { 1112 curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 1113 } 1114 if (!mFocusable) { 1115 curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 1116 if (mInputMethodMode == INPUT_METHOD_NEEDED) { 1117 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1118 } 1119 } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { 1120 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1121 } 1122 if (!mTouchable) { 1123 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 1124 } 1125 if (mOutsideTouchable) { 1126 curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 1127 } 1128 if (!mClippingEnabled) { 1129 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 1130 } 1131 if (isSplitTouchEnabled()) { 1132 curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 1133 } 1134 if (mLayoutInScreen) { 1135 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 1136 } 1137 if (mLayoutInsetDecor) { 1138 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 1139 } 1140 if (mNotTouchModal) { 1141 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 1142 } 1143 return curFlags; 1144 } 1145 1146 private int computeAnimationResource() { 1147 if (mAnimationStyle == -1) { 1148 if (mIsDropdown) { 1149 return mAboveAnchor 1150 ? com.android.internal.R.style.Animation_DropDownUp 1151 : com.android.internal.R.style.Animation_DropDownDown; 1152 } 1153 return 0; 1154 } 1155 return mAnimationStyle; 1156 } 1157 1158 /** 1159 * Positions the popup window on screen. When the popup window is too tall 1160 * to fit under the anchor, a parent scroll view is seeked and scrolled up 1161 * to reclaim space. If scrolling is not possible or not enough, the popup 1162 * window gets moved on top of the anchor. 1163 * <p> 1164 * The height must have been set on the layout parameters prior to calling 1165 * this method. 1166 * 1167 * @param anchor the view on which the popup window must be anchored 1168 * @param p the layout parameters used to display the drop down 1169 * @param xoff horizontal offset used to adjust for background padding 1170 * @param yoff vertical offset used to adjust for background padding 1171 * @param gravity horizontal gravity specifying popup alignment 1172 * @return true if the popup is translated upwards to fit on screen 1173 */ 1174 private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff, 1175 int yoff, int gravity) { 1176 final int anchorHeight = anchor.getHeight(); 1177 final int anchorWidth = anchor.getWidth(); 1178 if (mOverlapAnchor) { 1179 yoff -= anchorHeight; 1180 } 1181 1182 anchor.getLocationInWindow(mDrawingLocation); 1183 p.x = mDrawingLocation[0] + xoff; 1184 p.y = mDrawingLocation[1] + anchorHeight + yoff; 1185 1186 final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection()) 1187 & Gravity.HORIZONTAL_GRAVITY_MASK; 1188 if (hgrav == Gravity.RIGHT) { 1189 // Flip the location to align the right sides of the popup and 1190 // anchor instead of left. 1191 p.x -= mPopupWidth - anchorWidth; 1192 } 1193 1194 boolean onTop = false; 1195 1196 p.gravity = Gravity.LEFT | Gravity.TOP; 1197 1198 anchor.getLocationOnScreen(mScreenLocation); 1199 final Rect displayFrame = new Rect(); 1200 anchor.getWindowVisibleDisplayFrame(displayFrame); 1201 1202 final int screenY = mScreenLocation[1] + anchorHeight + yoff; 1203 final View root = anchor.getRootView(); 1204 if (screenY + mPopupHeight > displayFrame.bottom 1205 || p.x + mPopupWidth - root.getWidth() > 0) { 1206 // If the drop down disappears at the bottom of the screen, we try 1207 // to scroll a parent scrollview or move the drop down back up on 1208 // top of the edit box. 1209 if (mAllowScrollingAnchorParent) { 1210 final int scrollX = anchor.getScrollX(); 1211 final int scrollY = anchor.getScrollY(); 1212 final Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff, 1213 scrollY + mPopupHeight + anchorHeight + yoff); 1214 anchor.requestRectangleOnScreen(r, true); 1215 } 1216 1217 // Now we re-evaluate the space available, and decide from that 1218 // whether the pop-up will go above or below the anchor. 1219 anchor.getLocationInWindow(mDrawingLocation); 1220 p.x = mDrawingLocation[0] + xoff; 1221 p.y = mDrawingLocation[1] + anchorHeight + yoff; 1222 1223 // Preserve the gravity adjustment. 1224 if (hgrav == Gravity.RIGHT) { 1225 p.x -= mPopupWidth - anchorWidth; 1226 } 1227 1228 // Determine whether there is more space above or below the anchor. 1229 anchor.getLocationOnScreen(mScreenLocation); 1230 onTop = (displayFrame.bottom - mScreenLocation[1] - anchorHeight - yoff) < 1231 (mScreenLocation[1] - yoff - displayFrame.top); 1232 if (onTop) { 1233 p.gravity = Gravity.LEFT | Gravity.BOTTOM; 1234 p.y = root.getHeight() - mDrawingLocation[1] + yoff; 1235 } else { 1236 p.y = mDrawingLocation[1] + anchorHeight + yoff; 1237 } 1238 } 1239 1240 if (mClipToScreen) { 1241 final int displayFrameWidth = displayFrame.right - displayFrame.left; 1242 final int right = p.x + p.width; 1243 if (right > displayFrameWidth) { 1244 p.x -= right - displayFrameWidth; 1245 } 1246 1247 if (p.x < displayFrame.left) { 1248 p.x = displayFrame.left; 1249 p.width = Math.min(p.width, displayFrameWidth); 1250 } 1251 1252 if (onTop) { 1253 final int popupTop = mScreenLocation[1] + yoff - mPopupHeight; 1254 if (popupTop < 0) { 1255 p.y += popupTop; 1256 } 1257 } else { 1258 p.y = Math.max(p.y, displayFrame.top); 1259 } 1260 } 1261 1262 p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; 1263 1264 // Compute the position of the anchor relative to the popup. 1265 mAnchorRelativeX = mDrawingLocation[0] - p.x + anchorHeight / 2; 1266 mAnchorRelativeY = mDrawingLocation[1] - p.y + anchorWidth / 2; 1267 1268 return onTop; 1269 } 1270 1271 /** 1272 * Returns the maximum height that is available for the popup to be 1273 * completely shown. It is recommended that this height be the maximum for 1274 * the popup's height, otherwise it is possible that the popup will be 1275 * clipped. 1276 * 1277 * @param anchor The view on which the popup window must be anchored. 1278 * @return The maximum available height for the popup to be completely 1279 * shown. 1280 */ 1281 public int getMaxAvailableHeight(View anchor) { 1282 return getMaxAvailableHeight(anchor, 0); 1283 } 1284 1285 /** 1286 * Returns the maximum height that is available for the popup to be 1287 * completely shown. It is recommended that this height be the maximum for 1288 * the popup's height, otherwise it is possible that the popup will be 1289 * clipped. 1290 * 1291 * @param anchor The view on which the popup window must be anchored. 1292 * @param yOffset y offset from the view's bottom edge 1293 * @return The maximum available height for the popup to be completely 1294 * shown. 1295 */ 1296 public int getMaxAvailableHeight(View anchor, int yOffset) { 1297 return getMaxAvailableHeight(anchor, yOffset, false); 1298 } 1299 1300 /** 1301 * Returns the maximum height that is available for the popup to be 1302 * completely shown, optionally ignoring any bottom decorations such as 1303 * the input method. It is recommended that this height be the maximum for 1304 * the popup's height, otherwise it is possible that the popup will be 1305 * clipped. 1306 * 1307 * @param anchor The view on which the popup window must be anchored. 1308 * @param yOffset y offset from the view's bottom edge 1309 * @param ignoreBottomDecorations if true, the height returned will be 1310 * all the way to the bottom of the display, ignoring any 1311 * bottom decorations 1312 * @return The maximum available height for the popup to be completely 1313 * shown. 1314 * 1315 * @hide Pending API council approval. 1316 */ 1317 public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) { 1318 final Rect displayFrame = new Rect(); 1319 anchor.getWindowVisibleDisplayFrame(displayFrame); 1320 1321 final int[] anchorPos = mDrawingLocation; 1322 anchor.getLocationOnScreen(anchorPos); 1323 1324 int bottomEdge = displayFrame.bottom; 1325 if (ignoreBottomDecorations) { 1326 Resources res = anchor.getContext().getResources(); 1327 bottomEdge = res.getDisplayMetrics().heightPixels; 1328 } 1329 final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; 1330 final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; 1331 1332 // anchorPos[1] is distance from anchor to top of screen 1333 int returnedHeight = Math.max(distanceToBottom, distanceToTop); 1334 if (mBackground != null) { 1335 mBackground.getPadding(mTempRect); 1336 returnedHeight -= mTempRect.top + mTempRect.bottom; 1337 } 1338 1339 return returnedHeight; 1340 } 1341 1342 /** 1343 * <p>Dispose of the popup window. This method can be invoked only after 1344 * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling 1345 * this method will have no effect.</p> 1346 * 1347 * @see #showAsDropDown(android.view.View) 1348 */ 1349 public void dismiss() { 1350 if (isShowing() && mPopupView != null) { 1351 mIsShowing = false; 1352 1353 unregisterForScrollChanged(); 1354 1355 try { 1356 mWindowManager.removeViewImmediate(mPopupView); 1357 } finally { 1358 if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { 1359 ((ViewGroup) mPopupView).removeView(mContentView); 1360 } 1361 mPopupView = null; 1362 1363 if (mOnDismissListener != null) { 1364 mOnDismissListener.onDismiss(); 1365 } 1366 } 1367 } 1368 } 1369 1370 /** 1371 * Sets the listener to be called when the window is dismissed. 1372 * 1373 * @param onDismissListener The listener. 1374 */ 1375 public void setOnDismissListener(OnDismissListener onDismissListener) { 1376 mOnDismissListener = onDismissListener; 1377 } 1378 1379 /** 1380 * Updates the state of the popup window, if it is currently being displayed, 1381 * from the currently set state. This includes: 1382 * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)}, 1383 * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)}, 1384 * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}. 1385 */ 1386 public void update() { 1387 if (!isShowing() || mContentView == null) { 1388 return; 1389 } 1390 1391 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1392 mPopupView.getLayoutParams(); 1393 1394 boolean update = false; 1395 1396 final int newAnim = computeAnimationResource(); 1397 if (newAnim != p.windowAnimations) { 1398 p.windowAnimations = newAnim; 1399 update = true; 1400 } 1401 1402 final int newFlags = computeFlags(p.flags); 1403 if (newFlags != p.flags) { 1404 p.flags = newFlags; 1405 update = true; 1406 } 1407 1408 if (update) { 1409 setLayoutDirectionFromAnchor(); 1410 mWindowManager.updateViewLayout(mPopupView, p); 1411 } 1412 } 1413 1414 /** 1415 * <p>Updates the dimension of the popup window. Calling this function 1416 * also updates the window with the current popup state as described 1417 * for {@link #update()}.</p> 1418 * 1419 * @param width the new width 1420 * @param height the new height 1421 */ 1422 public void update(int width, int height) { 1423 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1424 mPopupView.getLayoutParams(); 1425 update(p.x, p.y, width, height, false); 1426 } 1427 1428 /** 1429 * <p>Updates the position and the dimension of the popup window. Width and 1430 * height can be set to -1 to update location only. Calling this function 1431 * also updates the window with the current popup state as 1432 * described for {@link #update()}.</p> 1433 * 1434 * @param x the new x location 1435 * @param y the new y location 1436 * @param width the new width, can be -1 to ignore 1437 * @param height the new height, can be -1 to ignore 1438 */ 1439 public void update(int x, int y, int width, int height) { 1440 update(x, y, width, height, false); 1441 } 1442 1443 /** 1444 * <p>Updates the position and the dimension of the popup window. Width and 1445 * height can be set to -1 to update location only. Calling this function 1446 * also updates the window with the current popup state as 1447 * described for {@link #update()}.</p> 1448 * 1449 * @param x the new x location 1450 * @param y the new y location 1451 * @param width the new width, can be -1 to ignore 1452 * @param height the new height, can be -1 to ignore 1453 * @param force reposition the window even if the specified position 1454 * already seems to correspond to the LayoutParams 1455 */ 1456 public void update(int x, int y, int width, int height, boolean force) { 1457 if (width != -1) { 1458 mLastWidth = width; 1459 setWidth(width); 1460 } 1461 1462 if (height != -1) { 1463 mLastHeight = height; 1464 setHeight(height); 1465 } 1466 1467 if (!isShowing() || mContentView == null) { 1468 return; 1469 } 1470 1471 WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); 1472 1473 boolean update = force; 1474 1475 final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; 1476 if (width != -1 && p.width != finalWidth) { 1477 p.width = mLastWidth = finalWidth; 1478 update = true; 1479 } 1480 1481 final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; 1482 if (height != -1 && p.height != finalHeight) { 1483 p.height = mLastHeight = finalHeight; 1484 update = true; 1485 } 1486 1487 if (p.x != x) { 1488 p.x = x; 1489 update = true; 1490 } 1491 1492 if (p.y != y) { 1493 p.y = y; 1494 update = true; 1495 } 1496 1497 final int newAnim = computeAnimationResource(); 1498 if (newAnim != p.windowAnimations) { 1499 p.windowAnimations = newAnim; 1500 update = true; 1501 } 1502 1503 final int newFlags = computeFlags(p.flags); 1504 if (newFlags != p.flags) { 1505 p.flags = newFlags; 1506 update = true; 1507 } 1508 1509 if (update) { 1510 setLayoutDirectionFromAnchor(); 1511 mWindowManager.updateViewLayout(mPopupView, p); 1512 } 1513 } 1514 1515 /** 1516 * <p>Updates the position and the dimension of the popup window. Calling this 1517 * function also updates the window with the current popup state as described 1518 * for {@link #update()}.</p> 1519 * 1520 * @param anchor the popup's anchor view 1521 * @param width the new width, can be -1 to ignore 1522 * @param height the new height, can be -1 to ignore 1523 */ 1524 public void update(View anchor, int width, int height) { 1525 update(anchor, false, 0, 0, true, width, height, mAnchoredGravity); 1526 } 1527 1528 /** 1529 * <p>Updates the position and the dimension of the popup window. Width and 1530 * height can be set to -1 to update location only. Calling this function 1531 * also updates the window with the current popup state as 1532 * described for {@link #update()}.</p> 1533 * 1534 * <p>If the view later scrolls to move <code>anchor</code> to a different 1535 * location, the popup will be moved correspondingly.</p> 1536 * 1537 * @param anchor the popup's anchor view 1538 * @param xoff x offset from the view's left edge 1539 * @param yoff y offset from the view's bottom edge 1540 * @param width the new width, can be -1 to ignore 1541 * @param height the new height, can be -1 to ignore 1542 */ 1543 public void update(View anchor, int xoff, int yoff, int width, int height) { 1544 update(anchor, true, xoff, yoff, true, width, height, mAnchoredGravity); 1545 } 1546 1547 private void update(View anchor, boolean updateLocation, int xoff, int yoff, 1548 boolean updateDimension, int width, int height, int gravity) { 1549 1550 if (!isShowing() || mContentView == null) { 1551 return; 1552 } 1553 1554 WeakReference<View> oldAnchor = mAnchor; 1555 final boolean needsUpdate = updateLocation 1556 && (mAnchorXoff != xoff || mAnchorYoff != yoff); 1557 if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) { 1558 registerForScrollChanged(anchor, xoff, yoff, gravity); 1559 } else if (needsUpdate) { 1560 // No need to register again if this is a DropDown, showAsDropDown already did. 1561 mAnchorXoff = xoff; 1562 mAnchorYoff = yoff; 1563 mAnchoredGravity = gravity; 1564 } 1565 1566 WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); 1567 1568 if (updateDimension) { 1569 if (width == -1) { 1570 width = mPopupWidth; 1571 } else { 1572 mPopupWidth = width; 1573 } 1574 if (height == -1) { 1575 height = mPopupHeight; 1576 } else { 1577 mPopupHeight = height; 1578 } 1579 } 1580 1581 int x = p.x; 1582 int y = p.y; 1583 1584 if (updateLocation) { 1585 updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity)); 1586 } else { 1587 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, 1588 mAnchoredGravity)); 1589 } 1590 1591 update(p.x, p.y, width, height, x != p.x || y != p.y); 1592 } 1593 1594 /** 1595 * Listener that is called when this popup window is dismissed. 1596 */ 1597 public interface OnDismissListener { 1598 /** 1599 * Called when this popup window is dismissed. 1600 */ 1601 public void onDismiss(); 1602 } 1603 1604 private void unregisterForScrollChanged() { 1605 WeakReference<View> anchorRef = mAnchor; 1606 View anchor = null; 1607 if (anchorRef != null) { 1608 anchor = anchorRef.get(); 1609 } 1610 if (anchor != null) { 1611 ViewTreeObserver vto = anchor.getViewTreeObserver(); 1612 vto.removeOnScrollChangedListener(mOnScrollChangedListener); 1613 } 1614 mAnchor = null; 1615 } 1616 1617 private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) { 1618 unregisterForScrollChanged(); 1619 1620 mAnchor = new WeakReference<View>(anchor); 1621 ViewTreeObserver vto = anchor.getViewTreeObserver(); 1622 if (vto != null) { 1623 vto.addOnScrollChangedListener(mOnScrollChangedListener); 1624 } 1625 1626 mAnchorXoff = xoff; 1627 mAnchorYoff = yoff; 1628 mAnchoredGravity = gravity; 1629 } 1630 1631 private class PopupViewContainer extends FrameLayout { 1632 private static final String TAG = "PopupWindow.PopupViewContainer"; 1633 1634 public PopupViewContainer(Context context) { 1635 super(context); 1636 } 1637 1638 @Override 1639 protected int[] onCreateDrawableState(int extraSpace) { 1640 if (mAboveAnchor) { 1641 // 1 more needed for the above anchor state 1642 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 1643 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); 1644 return drawableState; 1645 } else { 1646 return super.onCreateDrawableState(extraSpace); 1647 } 1648 } 1649 1650 @Override 1651 public boolean dispatchKeyEvent(KeyEvent event) { 1652 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1653 if (getKeyDispatcherState() == null) { 1654 return super.dispatchKeyEvent(event); 1655 } 1656 1657 if (event.getAction() == KeyEvent.ACTION_DOWN 1658 && event.getRepeatCount() == 0) { 1659 KeyEvent.DispatcherState state = getKeyDispatcherState(); 1660 if (state != null) { 1661 state.startTracking(event, this); 1662 } 1663 return true; 1664 } else if (event.getAction() == KeyEvent.ACTION_UP) { 1665 KeyEvent.DispatcherState state = getKeyDispatcherState(); 1666 if (state != null && state.isTracking(event) && !event.isCanceled()) { 1667 dismiss(); 1668 return true; 1669 } 1670 } 1671 return super.dispatchKeyEvent(event); 1672 } else { 1673 return super.dispatchKeyEvent(event); 1674 } 1675 } 1676 1677 @Override 1678 public boolean dispatchTouchEvent(MotionEvent ev) { 1679 if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { 1680 return true; 1681 } 1682 return super.dispatchTouchEvent(ev); 1683 } 1684 1685 @Override 1686 public boolean onTouchEvent(MotionEvent event) { 1687 final int x = (int) event.getX(); 1688 final int y = (int) event.getY(); 1689 1690 if ((event.getAction() == MotionEvent.ACTION_DOWN) 1691 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 1692 dismiss(); 1693 return true; 1694 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 1695 dismiss(); 1696 return true; 1697 } else { 1698 return super.onTouchEvent(event); 1699 } 1700 } 1701 1702 @Override 1703 public void sendAccessibilityEvent(int eventType) { 1704 // clinets are interested in the content not the container, make it event source 1705 if (mContentView != null) { 1706 mContentView.sendAccessibilityEvent(eventType); 1707 } else { 1708 super.sendAccessibilityEvent(eventType); 1709 } 1710 } 1711 } 1712 1713} 1714