PopupWindow.java revision 8216eb2221ad5ad6615d3966f43844268756f3c8
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 static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; 20import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; 21 22import com.android.internal.R; 23 24import android.annotation.NonNull; 25import android.content.Context; 26import android.content.res.Resources; 27import android.content.res.TypedArray; 28import android.graphics.PixelFormat; 29import android.graphics.Rect; 30import android.graphics.drawable.Drawable; 31import android.graphics.drawable.StateListDrawable; 32import android.os.Build; 33import android.os.IBinder; 34import android.transition.Transition; 35import android.transition.Transition.EpicenterCallback; 36import android.transition.Transition.TransitionListener; 37import android.transition.Transition.TransitionListenerAdapter; 38import android.transition.TransitionInflater; 39import android.transition.TransitionManager; 40import android.transition.TransitionSet; 41import android.util.AttributeSet; 42import android.view.Gravity; 43import android.view.KeyEvent; 44import android.view.MotionEvent; 45import android.view.View; 46import android.view.View.OnTouchListener; 47import android.view.ViewGroup; 48import android.view.ViewParent; 49import android.view.ViewTreeObserver; 50import android.view.ViewTreeObserver.OnGlobalLayoutListener; 51import android.view.ViewTreeObserver.OnScrollChangedListener; 52import android.view.WindowManager; 53import android.view.WindowManager.LayoutParams; 54 55import java.lang.ref.WeakReference; 56 57/** 58 * <p>A popup window that can be used to display an arbitrary view. The popup 59 * window is a floating container that appears on top of the current 60 * activity.</p> 61 * 62 * @see android.widget.AutoCompleteTextView 63 * @see android.widget.Spinner 64 */ 65public class PopupWindow { 66 /** 67 * Mode for {@link #setInputMethodMode(int)}: the requirements for the 68 * input method should be based on the focusability of the popup. That is 69 * if it is focusable than it needs to work with the input method, else 70 * it doesn't. 71 */ 72 public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; 73 74 /** 75 * Mode for {@link #setInputMethodMode(int)}: this popup always needs to 76 * work with an input method, regardless of whether it is focusable. This 77 * means that it will always be displayed so that the user can also operate 78 * the input method while it is shown. 79 */ 80 public static final int INPUT_METHOD_NEEDED = 1; 81 82 /** 83 * Mode for {@link #setInputMethodMode(int)}: this popup never needs to 84 * work with an input method, regardless of whether it is focusable. This 85 * means that it will always be displayed to use as much space on the 86 * screen as needed, regardless of whether this covers the input method. 87 */ 88 public static final int INPUT_METHOD_NOT_NEEDED = 2; 89 90 private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START; 91 92 /** 93 * Default animation style indicating that separate animations should be 94 * used for top/bottom anchoring states. 95 */ 96 private static final int ANIMATION_STYLE_DEFAULT = -1; 97 98 private final int[] mDrawingLocation = new int[2]; 99 private final int[] mScreenLocation = new int[2]; 100 private final Rect mTempRect = new Rect(); 101 102 private Context mContext; 103 private WindowManager mWindowManager; 104 105 private boolean mIsShowing; 106 private boolean mIsTransitioningToDismiss; 107 private boolean mIsDropdown; 108 109 /** View that handles event dispatch and content transitions. */ 110 private PopupDecorView mDecorView; 111 112 /** View that holds the background and may animate during a transition. */ 113 private View mBackgroundView; 114 115 /** The contents of the popup. May be identical to the background view. */ 116 private View mContentView; 117 118 private boolean mFocusable; 119 private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; 120 private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; 121 private boolean mTouchable = true; 122 private boolean mOutsideTouchable = false; 123 private boolean mClippingEnabled = true; 124 private int mSplitTouchEnabled = -1; 125 private boolean mLayoutInScreen; 126 private boolean mClipToScreen; 127 private boolean mAllowScrollingAnchorParent = true; 128 private boolean mLayoutInsetDecor = false; 129 private boolean mNotTouchModal; 130 private boolean mAttachedInDecor = true; 131 private boolean mAttachedInDecorSet = false; 132 133 private OnTouchListener mTouchInterceptor; 134 135 private int mWidthMode; 136 private int mWidth = LayoutParams.WRAP_CONTENT; 137 private int mLastWidth; 138 private int mHeightMode; 139 private int mHeight = LayoutParams.WRAP_CONTENT; 140 private int mLastHeight; 141 142 private int mPopupWidth; 143 private int mPopupHeight; 144 145 private float mElevation; 146 147 private Drawable mBackground; 148 private Drawable mAboveAnchorBackgroundDrawable; 149 private Drawable mBelowAnchorBackgroundDrawable; 150 151 private Transition mEnterTransition; 152 private Transition mExitTransition; 153 154 private boolean mAboveAnchor; 155 private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 156 157 private OnDismissListener mOnDismissListener; 158 private boolean mIgnoreCheekPress = false; 159 160 private int mAnimationStyle = ANIMATION_STYLE_DEFAULT; 161 162 private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] { 163 com.android.internal.R.attr.state_above_anchor 164 }; 165 166 private WeakReference<View> mAnchor; 167 168 private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() { 169 @Override 170 public void onScrollChanged() { 171 final View anchor = mAnchor != null ? mAnchor.get() : null; 172 if (anchor != null && mDecorView != null) { 173 final WindowManager.LayoutParams p = (WindowManager.LayoutParams) 174 mDecorView.getLayoutParams(); 175 176 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, 177 mAnchoredGravity)); 178 update(p.x, p.y, -1, -1, true); 179 } 180 } 181 }; 182 183 private int mAnchorXoff; 184 private int mAnchorYoff; 185 private int mAnchoredGravity; 186 private boolean mOverlapAnchor; 187 188 private boolean mPopupViewInitialLayoutDirectionInherited; 189 190 /** 191 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 192 * 193 * <p>The popup does provide a background.</p> 194 */ 195 public PopupWindow(Context context) { 196 this(context, null); 197 } 198 199 /** 200 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 201 * 202 * <p>The popup does provide a background.</p> 203 */ 204 public PopupWindow(Context context, AttributeSet attrs) { 205 this(context, attrs, com.android.internal.R.attr.popupWindowStyle); 206 } 207 208 /** 209 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 210 * 211 * <p>The popup does provide a background.</p> 212 */ 213 public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) { 214 this(context, attrs, defStyleAttr, 0); 215 } 216 217 /** 218 * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p> 219 * 220 * <p>The popup does not provide a background.</p> 221 */ 222 public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 223 mContext = context; 224 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 225 226 final TypedArray a = context.obtainStyledAttributes( 227 attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes); 228 final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground); 229 mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0); 230 mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); 231 232 // Preserve default behavior from Gingerbread. If the animation is 233 // undefined or explicitly specifies the Gingerbread animation style, 234 // use a sentinel value. 235 if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) { 236 final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0); 237 if (animStyle == R.style.Animation_PopupWindow) { 238 mAnimationStyle = ANIMATION_STYLE_DEFAULT; 239 } else { 240 mAnimationStyle = animStyle; 241 } 242 } else { 243 mAnimationStyle = ANIMATION_STYLE_DEFAULT; 244 } 245 246 final Transition enterTransition = getTransition(a.getResourceId( 247 R.styleable.PopupWindow_popupEnterTransition, 0)); 248 final Transition exitTransition; 249 if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) { 250 exitTransition = getTransition(a.getResourceId( 251 R.styleable.PopupWindow_popupExitTransition, 0)); 252 } else { 253 exitTransition = enterTransition == null ? null : enterTransition.clone(); 254 } 255 256 a.recycle(); 257 258 setEnterTransition(enterTransition); 259 setExitTransition(exitTransition); 260 setBackgroundDrawable(bg); 261 } 262 263 /** 264 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 265 * 266 * <p>The popup does not provide any background. This should be handled 267 * by the content view.</p> 268 */ 269 public PopupWindow() { 270 this(null, 0, 0); 271 } 272 273 /** 274 * <p>Create a new non focusable popup window which can display the 275 * <tt>contentView</tt>. The dimension of the window are (0,0).</p> 276 * 277 * <p>The popup does not provide any background. This should be handled 278 * by the content view.</p> 279 * 280 * @param contentView the popup's content 281 */ 282 public PopupWindow(View contentView) { 283 this(contentView, 0, 0); 284 } 285 286 /** 287 * <p>Create a new empty, non focusable popup window. The dimension of the 288 * window must be passed to this constructor.</p> 289 * 290 * <p>The popup does not provide any background. This should be handled 291 * by the content view.</p> 292 * 293 * @param width the popup's width 294 * @param height the popup's height 295 */ 296 public PopupWindow(int width, int height) { 297 this(null, width, height); 298 } 299 300 /** 301 * <p>Create a new non focusable popup window which can display the 302 * <tt>contentView</tt>. The dimension of the window must be passed to 303 * this constructor.</p> 304 * 305 * <p>The popup does not provide any background. This should be handled 306 * by the content view.</p> 307 * 308 * @param contentView the popup's content 309 * @param width the popup's width 310 * @param height the popup's height 311 */ 312 public PopupWindow(View contentView, int width, int height) { 313 this(contentView, width, height, false); 314 } 315 316 /** 317 * <p>Create a new popup window which can display the <tt>contentView</tt>. 318 * The dimension of the window must be passed to this constructor.</p> 319 * 320 * <p>The popup does not provide any background. This should be handled 321 * by the content view.</p> 322 * 323 * @param contentView the popup's content 324 * @param width the popup's width 325 * @param height the popup's height 326 * @param focusable true if the popup can be focused, false otherwise 327 */ 328 public PopupWindow(View contentView, int width, int height, boolean focusable) { 329 if (contentView != null) { 330 mContext = contentView.getContext(); 331 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 332 } 333 334 setContentView(contentView); 335 setWidth(width); 336 setHeight(height); 337 setFocusable(focusable); 338 } 339 340 public void setEnterTransition(Transition enterTransition) { 341 mEnterTransition = enterTransition; 342 } 343 344 public void setExitTransition(Transition exitTransition) { 345 mExitTransition = exitTransition; 346 } 347 348 private Transition getTransition(int resId) { 349 if (resId != 0 && resId != R.transition.no_transition) { 350 final TransitionInflater inflater = TransitionInflater.from(mContext); 351 final Transition transition = inflater.inflateTransition(resId); 352 if (transition != null) { 353 final boolean isEmpty = transition instanceof TransitionSet 354 && ((TransitionSet) transition).getTransitionCount() == 0; 355 if (!isEmpty) { 356 return transition; 357 } 358 } 359 } 360 return null; 361 } 362 363 /** 364 * Return the drawable used as the popup window's background. 365 * 366 * @return the background drawable or {@code null} if not set 367 * @see #setBackgroundDrawable(Drawable) 368 * @attr ref android.R.styleable#PopupWindow_popupBackground 369 */ 370 public Drawable getBackground() { 371 return mBackground; 372 } 373 374 /** 375 * Specifies the background drawable for this popup window. The background 376 * can be set to {@code null}. 377 * 378 * @param background the popup's background 379 * @see #getBackground() 380 * @attr ref android.R.styleable#PopupWindow_popupBackground 381 */ 382 public void setBackgroundDrawable(Drawable background) { 383 mBackground = background; 384 385 // If this is a StateListDrawable, try to find and store the drawable to be 386 // used when the drop-down is placed above its anchor view, and the one to be 387 // used when the drop-down is placed below its anchor view. We extract 388 // the drawables ourselves to work around a problem with using refreshDrawableState 389 // that it will take into account the padding of all drawables specified in a 390 // StateListDrawable, thus adding superfluous padding to drop-down views. 391 // 392 // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and 393 // at least one other drawable, intended for the 'below-anchor state'. 394 if (mBackground instanceof StateListDrawable) { 395 StateListDrawable stateList = (StateListDrawable) mBackground; 396 397 // Find the above-anchor view - this one's easy, it should be labeled as such. 398 int aboveAnchorStateIndex = stateList.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET); 399 400 // Now, for the below-anchor view, look for any other drawable specified in the 401 // StateListDrawable which is not for the above-anchor state and use that. 402 int count = stateList.getStateCount(); 403 int belowAnchorStateIndex = -1; 404 for (int i = 0; i < count; i++) { 405 if (i != aboveAnchorStateIndex) { 406 belowAnchorStateIndex = i; 407 break; 408 } 409 } 410 411 // Store the drawables we found, if we found them. Otherwise, set them both 412 // to null so that we'll just use refreshDrawableState. 413 if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) { 414 mAboveAnchorBackgroundDrawable = stateList.getStateDrawable(aboveAnchorStateIndex); 415 mBelowAnchorBackgroundDrawable = stateList.getStateDrawable(belowAnchorStateIndex); 416 } else { 417 mBelowAnchorBackgroundDrawable = null; 418 mAboveAnchorBackgroundDrawable = null; 419 } 420 } 421 } 422 423 /** 424 * @return the elevation for this popup window in pixels 425 * @see #setElevation(float) 426 * @attr ref android.R.styleable#PopupWindow_popupElevation 427 */ 428 public float getElevation() { 429 return mElevation; 430 } 431 432 /** 433 * Specifies the elevation for this popup window. 434 * 435 * @param elevation the popup's elevation in pixels 436 * @see #getElevation() 437 * @attr ref android.R.styleable#PopupWindow_popupElevation 438 */ 439 public void setElevation(float elevation) { 440 mElevation = elevation; 441 } 442 443 /** 444 * <p>Return the animation style to use the popup appears and disappears</p> 445 * 446 * @return the animation style to use the popup appears and disappears 447 */ 448 public int getAnimationStyle() { 449 return mAnimationStyle; 450 } 451 452 /** 453 * Set the flag on popup to ignore cheek press events; by default this flag 454 * is set to false 455 * which means the popup will not ignore cheek press dispatch events. 456 * 457 * <p>If the popup is showing, calling this method will take effect only 458 * the next time the popup is shown or through a manual call to one of 459 * the {@link #update()} methods.</p> 460 * 461 * @see #update() 462 */ 463 public void setIgnoreCheekPress() { 464 mIgnoreCheekPress = true; 465 } 466 467 468 /** 469 * <p>Change the animation style resource for this popup.</p> 470 * 471 * <p>If the popup is showing, calling this method will take effect only 472 * the next time the popup is shown or through a manual call to one of 473 * the {@link #update()} methods.</p> 474 * 475 * @param animationStyle animation style to use when the popup appears 476 * and disappears. Set to -1 for the default animation, 0 for no 477 * animation, or a resource identifier for an explicit animation. 478 * 479 * @see #update() 480 */ 481 public void setAnimationStyle(int animationStyle) { 482 mAnimationStyle = animationStyle; 483 } 484 485 /** 486 * <p>Return the view used as the content of the popup window.</p> 487 * 488 * @return a {@link android.view.View} representing the popup's content 489 * 490 * @see #setContentView(android.view.View) 491 */ 492 public View getContentView() { 493 return mContentView; 494 } 495 496 /** 497 * <p>Change the popup's content. The content is represented by an instance 498 * of {@link android.view.View}.</p> 499 * 500 * <p>This method has no effect if called when the popup is showing.</p> 501 * 502 * @param contentView the new content for the popup 503 * 504 * @see #getContentView() 505 * @see #isShowing() 506 */ 507 public void setContentView(View contentView) { 508 if (isShowing()) { 509 return; 510 } 511 512 mContentView = contentView; 513 514 if (mContext == null && mContentView != null) { 515 mContext = mContentView.getContext(); 516 } 517 518 if (mWindowManager == null && mContentView != null) { 519 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 520 } 521 522 // Setting the default for attachedInDecor based on SDK version here 523 // instead of in the constructor since we might not have the context 524 // object in the constructor. We only want to set default here if the 525 // app hasn't already set the attachedInDecor. 526 if (mContext != null && !mAttachedInDecorSet) { 527 // Attach popup window in decor frame of parent window by default for 528 // {@link Build.VERSION_CODES.LOLLIPOP_MR1} or greater. Keep current 529 // behavior of not attaching to decor frame for older SDKs. 530 setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion 531 >= Build.VERSION_CODES.LOLLIPOP_MR1); 532 } 533 534 } 535 536 /** 537 * Set a callback for all touch events being dispatched to the popup 538 * window. 539 */ 540 public void setTouchInterceptor(OnTouchListener l) { 541 mTouchInterceptor = l; 542 } 543 544 /** 545 * <p>Indicate whether the popup window can grab the focus.</p> 546 * 547 * @return true if the popup is focusable, false otherwise 548 * 549 * @see #setFocusable(boolean) 550 */ 551 public boolean isFocusable() { 552 return mFocusable; 553 } 554 555 /** 556 * <p>Changes the focusability of the popup window. When focusable, the 557 * window will grab the focus from the current focused widget if the popup 558 * contains a focusable {@link android.view.View}. By default a popup 559 * window is not focusable.</p> 560 * 561 * <p>If the popup is showing, calling this method will take effect only 562 * the next time the popup is shown or through a manual call to one of 563 * the {@link #update()} methods.</p> 564 * 565 * @param focusable true if the popup should grab focus, false otherwise. 566 * 567 * @see #isFocusable() 568 * @see #isShowing() 569 * @see #update() 570 */ 571 public void setFocusable(boolean focusable) { 572 mFocusable = focusable; 573 } 574 575 /** 576 * Return the current value in {@link #setInputMethodMode(int)}. 577 * 578 * @see #setInputMethodMode(int) 579 */ 580 public int getInputMethodMode() { 581 return mInputMethodMode; 582 583 } 584 585 /** 586 * Control how the popup operates with an input method: one of 587 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, 588 * or {@link #INPUT_METHOD_NOT_NEEDED}. 589 * 590 * <p>If the popup is showing, calling this method will take effect only 591 * the next time the popup is shown or through a manual call to one of 592 * the {@link #update()} methods.</p> 593 * 594 * @see #getInputMethodMode() 595 * @see #update() 596 */ 597 public void setInputMethodMode(int mode) { 598 mInputMethodMode = mode; 599 } 600 601 /** 602 * Sets the operating mode for the soft input area. 603 * 604 * @param mode The desired mode, see 605 * {@link android.view.WindowManager.LayoutParams#softInputMode} 606 * for the full list 607 * 608 * @see android.view.WindowManager.LayoutParams#softInputMode 609 * @see #getSoftInputMode() 610 */ 611 public void setSoftInputMode(int mode) { 612 mSoftInputMode = mode; 613 } 614 615 /** 616 * Returns the current value in {@link #setSoftInputMode(int)}. 617 * 618 * @see #setSoftInputMode(int) 619 * @see android.view.WindowManager.LayoutParams#softInputMode 620 */ 621 public int getSoftInputMode() { 622 return mSoftInputMode; 623 } 624 625 /** 626 * <p>Indicates whether the popup window receives touch events.</p> 627 * 628 * @return true if the popup is touchable, false otherwise 629 * 630 * @see #setTouchable(boolean) 631 */ 632 public boolean isTouchable() { 633 return mTouchable; 634 } 635 636 /** 637 * <p>Changes the touchability of the popup window. When touchable, the 638 * window will receive touch events, otherwise touch events will go to the 639 * window below it. By default the window is touchable.</p> 640 * 641 * <p>If the popup is showing, calling this method will take effect only 642 * the next time the popup is shown or through a manual call to one of 643 * the {@link #update()} methods.</p> 644 * 645 * @param touchable true if the popup should receive touch events, false otherwise 646 * 647 * @see #isTouchable() 648 * @see #isShowing() 649 * @see #update() 650 */ 651 public void setTouchable(boolean touchable) { 652 mTouchable = touchable; 653 } 654 655 /** 656 * <p>Indicates whether the popup window will be informed of touch events 657 * outside of its window.</p> 658 * 659 * @return true if the popup is outside touchable, false otherwise 660 * 661 * @see #setOutsideTouchable(boolean) 662 */ 663 public boolean isOutsideTouchable() { 664 return mOutsideTouchable; 665 } 666 667 /** 668 * <p>Controls whether the pop-up will be informed of touch events outside 669 * of its window. This only makes sense for pop-ups that are touchable 670 * but not focusable, which means touches outside of the window will 671 * be delivered to the window behind. The default is false.</p> 672 * 673 * <p>If the popup is showing, calling this method will take effect only 674 * the next time the popup is shown or through a manual call to one of 675 * the {@link #update()} methods.</p> 676 * 677 * @param touchable true if the popup should receive outside 678 * touch events, false otherwise 679 * 680 * @see #isOutsideTouchable() 681 * @see #isShowing() 682 * @see #update() 683 */ 684 public void setOutsideTouchable(boolean touchable) { 685 mOutsideTouchable = touchable; 686 } 687 688 /** 689 * <p>Indicates whether clipping of the popup window is enabled.</p> 690 * 691 * @return true if the clipping is enabled, false otherwise 692 * 693 * @see #setClippingEnabled(boolean) 694 */ 695 public boolean isClippingEnabled() { 696 return mClippingEnabled; 697 } 698 699 /** 700 * <p>Allows the popup window to extend beyond the bounds of the screen. By default the 701 * window is clipped to the screen boundaries. Setting this to false will allow windows to be 702 * accurately positioned.</p> 703 * 704 * <p>If the popup is showing, calling this method will take effect only 705 * the next time the popup is shown or through a manual call to one of 706 * the {@link #update()} methods.</p> 707 * 708 * @param enabled false if the window should be allowed to extend outside of the screen 709 * @see #isShowing() 710 * @see #isClippingEnabled() 711 * @see #update() 712 */ 713 public void setClippingEnabled(boolean enabled) { 714 mClippingEnabled = enabled; 715 } 716 717 /** 718 * Clip this popup window to the screen, but not to the containing window. 719 * 720 * @param enabled True to clip to the screen. 721 * @hide 722 */ 723 public void setClipToScreenEnabled(boolean enabled) { 724 mClipToScreen = enabled; 725 setClippingEnabled(!enabled); 726 } 727 728 /** 729 * Allow PopupWindow to scroll the anchor's parent to provide more room 730 * for the popup. Enabled by default. 731 * 732 * @param enabled True to scroll the anchor's parent when more room is desired by the popup. 733 */ 734 void setAllowScrollingAnchorParent(boolean enabled) { 735 mAllowScrollingAnchorParent = enabled; 736 } 737 738 /** 739 * <p>Indicates whether the popup window supports splitting touches.</p> 740 * 741 * @return true if the touch splitting is enabled, false otherwise 742 * 743 * @see #setSplitTouchEnabled(boolean) 744 */ 745 public boolean isSplitTouchEnabled() { 746 if (mSplitTouchEnabled < 0 && mContext != null) { 747 return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB; 748 } 749 return mSplitTouchEnabled == 1; 750 } 751 752 /** 753 * <p>Allows the popup window to split touches across other windows that also 754 * support split touch. When this flag is false, the first pointer 755 * that goes down determines the window to which all subsequent touches 756 * go until all pointers go up. When this flag is true, each pointer 757 * (not necessarily the first) that goes down determines the window 758 * to which all subsequent touches of that pointer will go until that 759 * pointer goes up thereby enabling touches with multiple pointers 760 * to be split across multiple windows.</p> 761 * 762 * @param enabled true if the split touches should be enabled, false otherwise 763 * @see #isSplitTouchEnabled() 764 */ 765 public void setSplitTouchEnabled(boolean enabled) { 766 mSplitTouchEnabled = enabled ? 1 : 0; 767 } 768 769 /** 770 * <p>Indicates whether the popup window will be forced into using absolute screen coordinates 771 * for positioning.</p> 772 * 773 * @return true if the window will always be positioned in screen coordinates. 774 * @hide 775 */ 776 public boolean isLayoutInScreenEnabled() { 777 return mLayoutInScreen; 778 } 779 780 /** 781 * <p>Allows the popup window to force the flag 782 * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior. 783 * This will cause the popup to be positioned in absolute screen coordinates.</p> 784 * 785 * @param enabled true if the popup should always be positioned in screen coordinates 786 * @hide 787 */ 788 public void setLayoutInScreenEnabled(boolean enabled) { 789 mLayoutInScreen = enabled; 790 } 791 792 /** 793 * <p>Indicates whether the popup window will be attached in the decor frame of its parent 794 * window. 795 * 796 * @return true if the window will be attached to the decor frame of its parent window. 797 * 798 * @see #setAttachedInDecor(boolean) 799 * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR 800 */ 801 public boolean isAttachedInDecor() { 802 return mAttachedInDecor; 803 } 804 805 /** 806 * <p>This will attach the popup window to the decor frame of the parent window to avoid 807 * overlaping with screen decorations like the navigation bar. Overrides the default behavior of 808 * the flag {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR}. 809 * 810 * <p>By default the flag is set on SDK version {@link Build.VERSION_CODES#LOLLIPOP_MR1} or 811 * greater and cleared on lesser SDK versions. 812 * 813 * @param enabled true if the popup should be attached to the decor frame of its parent window. 814 * 815 * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR 816 */ 817 public void setAttachedInDecor(boolean enabled) { 818 mAttachedInDecor = enabled; 819 mAttachedInDecorSet = true; 820 } 821 822 /** 823 * Allows the popup window to force the flag 824 * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior. 825 * This will cause the popup to inset its content to account for system windows overlaying 826 * the screen, such as the status bar. 827 * 828 * <p>This will often be combined with {@link #setLayoutInScreenEnabled(boolean)}. 829 * 830 * @param enabled true if the popup's views should inset content to account for system windows, 831 * the way that decor views behave for full-screen windows. 832 * @hide 833 */ 834 public void setLayoutInsetDecor(boolean enabled) { 835 mLayoutInsetDecor = enabled; 836 } 837 838 /** 839 * Set the layout type for this window. 840 * <p> 841 * See {@link WindowManager.LayoutParams#type} for possible values. 842 * 843 * @param layoutType Layout type for this window. 844 * 845 * @see WindowManager.LayoutParams#type 846 */ 847 public void setWindowLayoutType(int layoutType) { 848 mWindowLayoutType = layoutType; 849 } 850 851 /** 852 * Returns the layout type for this window. 853 * 854 * @see #setWindowLayoutType(int) 855 */ 856 public int getWindowLayoutType() { 857 return mWindowLayoutType; 858 } 859 860 /** 861 * Set whether this window is touch modal or if outside touches will be sent to 862 * other windows behind it. 863 * @hide 864 */ 865 public void setTouchModal(boolean touchModal) { 866 mNotTouchModal = !touchModal; 867 } 868 869 /** 870 * <p>Change the width and height measure specs that are given to the 871 * window manager by the popup. By default these are 0, meaning that 872 * the current width or height is requested as an explicit size from 873 * the window manager. You can supply 874 * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or 875 * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure 876 * spec supplied instead, replacing the absolute width and height that 877 * has been set in the popup.</p> 878 * 879 * <p>If the popup is showing, calling this method will take effect only 880 * the next time the popup is shown.</p> 881 * 882 * @param widthSpec an explicit width measure spec mode, either 883 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 884 * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute 885 * width. 886 * @param heightSpec an explicit height measure spec mode, either 887 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 888 * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute 889 * height. 890 * 891 * @deprecated Use {@link #setWidth(int)} and {@link #setHeight(int)}. 892 */ 893 @Deprecated 894 public void setWindowLayoutMode(int widthSpec, int heightSpec) { 895 mWidthMode = widthSpec; 896 mHeightMode = heightSpec; 897 } 898 899 /** 900 * Returns the popup's height MeasureSpec. 901 * 902 * @return the height MeasureSpec of the popup 903 * @see #setHeight(int) 904 */ 905 public int getHeight() { 906 return mHeight; 907 } 908 909 /** 910 * Sets the popup's height MeasureSpec. 911 * <p> 912 * If the popup is showing, calling this method will take effect the next 913 * time the popup is shown. 914 * 915 * @param height the height MeasureSpec of the popup 916 * @see #getHeight() 917 * @see #isShowing() 918 */ 919 public void setHeight(int height) { 920 mHeight = height; 921 } 922 923 /** 924 * Returns the popup's width MeasureSpec. 925 * 926 * @return the width MeasureSpec of the popup 927 * @see #setWidth(int) 928 */ 929 public int getWidth() { 930 return mWidth; 931 } 932 933 /** 934 * Sets the popup's width MeasureSpec. 935 * <p> 936 * If the popup is showing, calling this method will take effect the next 937 * time the popup is shown. 938 * 939 * @param width the width MeasureSpec of the popup 940 * @see #getWidth() 941 * @see #isShowing() 942 */ 943 public void setWidth(int width) { 944 mWidth = width; 945 } 946 947 /** 948 * Sets whether the popup window should overlap its anchor view when 949 * displayed as a drop-down. 950 * <p> 951 * If the popup is showing, calling this method will take effect only 952 * the next time the popup is shown. 953 * 954 * @param overlapAnchor Whether the popup should overlap its anchor. 955 * 956 * @see #getOverlapAnchor() 957 * @see #isShowing() 958 */ 959 public void setOverlapAnchor(boolean overlapAnchor) { 960 mOverlapAnchor = overlapAnchor; 961 } 962 963 /** 964 * Returns whether the popup window should overlap its anchor view when 965 * displayed as a drop-down. 966 * 967 * @return Whether the popup should overlap its anchor. 968 * 969 * @see #setOverlapAnchor(boolean) 970 */ 971 public boolean getOverlapAnchor() { 972 return mOverlapAnchor; 973 } 974 975 /** 976 * <p>Indicate whether this popup window is showing on screen.</p> 977 * 978 * @return true if the popup is showing, false otherwise 979 */ 980 public boolean isShowing() { 981 return mIsShowing; 982 } 983 984 /** 985 * <p> 986 * Display the content view in a popup window at the specified location. If the popup window 987 * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams} 988 * for more information on how gravity and the x and y parameters are related. Specifying 989 * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying 990 * <code>Gravity.LEFT | Gravity.TOP</code>. 991 * </p> 992 * 993 * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from 994 * @param gravity the gravity which controls the placement of the popup window 995 * @param x the popup's x location offset 996 * @param y the popup's y location offset 997 */ 998 public void showAtLocation(View parent, int gravity, int x, int y) { 999 showAtLocation(parent.getWindowToken(), gravity, x, y); 1000 } 1001 1002 /** 1003 * Display the content view in a popup window at the specified location. 1004 * 1005 * @param token Window token to use for creating the new window 1006 * @param gravity the gravity which controls the placement of the popup window 1007 * @param x the popup's x location offset 1008 * @param y the popup's y location offset 1009 * 1010 * @hide Internal use only. Applications should use 1011 * {@link #showAtLocation(View, int, int, int)} instead. 1012 */ 1013 public void showAtLocation(IBinder token, int gravity, int x, int y) { 1014 if (isShowing() || mContentView == null) { 1015 return; 1016 } 1017 1018 TransitionManager.endTransitions(mDecorView); 1019 1020 unregisterForScrollChanged(); 1021 1022 mIsShowing = true; 1023 mIsDropdown = false; 1024 1025 final WindowManager.LayoutParams p = createPopupLayoutParams(token); 1026 preparePopup(p); 1027 1028 // Only override the default if some gravity was specified. 1029 if (gravity != Gravity.NO_GRAVITY) { 1030 p.gravity = gravity; 1031 } 1032 1033 p.x = x; 1034 p.y = y; 1035 1036 invokePopup(p); 1037 } 1038 1039 /** 1040 * Display the content view in a popup window anchored to the bottom-left 1041 * corner of the anchor view. If there is not enough room on screen to show 1042 * the popup in its entirety, this method tries to find a parent scroll 1043 * view to scroll. If no parent scroll view can be scrolled, the 1044 * bottom-left corner of the popup is pinned at the top left corner of the 1045 * anchor view. 1046 * 1047 * @param anchor the view on which to pin the popup window 1048 * 1049 * @see #dismiss() 1050 */ 1051 public void showAsDropDown(View anchor) { 1052 showAsDropDown(anchor, 0, 0); 1053 } 1054 1055 /** 1056 * Display the content view in a popup window anchored to the bottom-left 1057 * corner of the anchor view offset by the specified x and y coordinates. 1058 * If there is not enough room on screen to show the popup in its entirety, 1059 * this method tries to find a parent scroll view to scroll. If no parent 1060 * scroll view can be scrolled, the bottom-left corner of the popup is 1061 * pinned at the top left corner of the anchor view. 1062 * <p> 1063 * If the view later scrolls to move <code>anchor</code> to a different 1064 * location, the popup will be moved correspondingly. 1065 * 1066 * @param anchor the view on which to pin the popup window 1067 * @param xoff A horizontal offset from the anchor in pixels 1068 * @param yoff A vertical offset from the anchor in pixels 1069 * 1070 * @see #dismiss() 1071 */ 1072 public void showAsDropDown(View anchor, int xoff, int yoff) { 1073 showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY); 1074 } 1075 1076 /** 1077 * Displays the content view in a popup window anchored to the corner of 1078 * another view. The window is positioned according to the specified 1079 * gravity and offset by the specified x and y coordinates. 1080 * <p> 1081 * If there is not enough room on screen to show the popup in its entirety, 1082 * this method tries to find a parent scroll view to scroll. If no parent 1083 * view can be scrolled, the specified vertical gravity will be ignored and 1084 * the popup will anchor itself such that it is visible. 1085 * <p> 1086 * If the view later scrolls to move <code>anchor</code> to a different 1087 * location, the popup will be moved correspondingly. 1088 * 1089 * @param anchor the view on which to pin the popup window 1090 * @param xoff A horizontal offset from the anchor in pixels 1091 * @param yoff A vertical offset from the anchor in pixels 1092 * @param gravity Alignment of the popup relative to the anchor 1093 * 1094 * @see #dismiss() 1095 */ 1096 public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { 1097 if (isShowing() || mContentView == null) { 1098 return; 1099 } 1100 1101 TransitionManager.endTransitions(mDecorView); 1102 1103 registerForScrollChanged(anchor, xoff, yoff, gravity); 1104 1105 mIsShowing = true; 1106 mIsDropdown = true; 1107 1108 final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken()); 1109 preparePopup(p); 1110 1111 final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity); 1112 updateAboveAnchor(aboveAnchor); 1113 1114 invokePopup(p); 1115 } 1116 1117 private void updateAboveAnchor(boolean aboveAnchor) { 1118 if (aboveAnchor != mAboveAnchor) { 1119 mAboveAnchor = aboveAnchor; 1120 1121 if (mBackground != null && mBackgroundView != null) { 1122 // If the background drawable provided was a StateListDrawable 1123 // with above-anchor and below-anchor states, use those. 1124 // Otherwise, rely on refreshDrawableState to do the job. 1125 if (mAboveAnchorBackgroundDrawable != null) { 1126 if (mAboveAnchor) { 1127 mBackgroundView.setBackground(mAboveAnchorBackgroundDrawable); 1128 } else { 1129 mBackgroundView.setBackground(mBelowAnchorBackgroundDrawable); 1130 } 1131 } else { 1132 mBackgroundView.refreshDrawableState(); 1133 } 1134 } 1135 } 1136 } 1137 1138 /** 1139 * Indicates whether the popup is showing above (the y coordinate of the popup's bottom 1140 * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate 1141 * of the popup is greater than y coordinate of the anchor's bottom). 1142 * 1143 * The value returned 1144 * by this method is meaningful only after {@link #showAsDropDown(android.view.View)} 1145 * or {@link #showAsDropDown(android.view.View, int, int)} was invoked. 1146 * 1147 * @return True if this popup is showing above the anchor view, false otherwise. 1148 */ 1149 public boolean isAboveAnchor() { 1150 return mAboveAnchor; 1151 } 1152 1153 /** 1154 * Prepare the popup by embedding it into a new ViewGroup if the background 1155 * drawable is not null. If embedding is required, the layout parameters' 1156 * height is modified to take into account the background's padding. 1157 * 1158 * @param p the layout parameters of the popup's content view 1159 */ 1160 private void preparePopup(WindowManager.LayoutParams p) { 1161 if (mContentView == null || mContext == null || mWindowManager == null) { 1162 throw new IllegalStateException("You must specify a valid content view by " 1163 + "calling setContentView() before attempting to show the popup."); 1164 } 1165 1166 // The old decor view may be transitioning out. Make sure it finishes 1167 // and cleans up before we try to create another one. 1168 if (mDecorView != null) { 1169 mDecorView.cancelTransitions(); 1170 } 1171 1172 // When a background is available, we embed the content view within 1173 // another view that owns the background drawable. 1174 if (mBackground != null) { 1175 mBackgroundView = createBackgroundView(mContentView); 1176 mBackgroundView.setBackground(mBackground); 1177 } else { 1178 mBackgroundView = mContentView; 1179 } 1180 1181 mDecorView = createDecorView(mBackgroundView); 1182 1183 // The background owner should be elevated so that it casts a shadow. 1184 mBackgroundView.setElevation(mElevation); 1185 1186 // We may wrap that in another view, so we'll need to manually specify 1187 // the surface insets. 1188 final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2); 1189 p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset); 1190 p.hasManualSurfaceInsets = true; 1191 1192 mPopupViewInitialLayoutDirectionInherited = 1193 (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); 1194 mPopupWidth = p.width; 1195 mPopupHeight = p.height; 1196 } 1197 1198 /** 1199 * Wraps a content view in a PopupViewContainer. 1200 * 1201 * @param contentView the content view to wrap 1202 * @return a PopupViewContainer that wraps the content view 1203 */ 1204 private PopupBackgroundView createBackgroundView(View contentView) { 1205 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 1206 final int height; 1207 if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { 1208 height = ViewGroup.LayoutParams.WRAP_CONTENT; 1209 } else { 1210 height = ViewGroup.LayoutParams.MATCH_PARENT; 1211 } 1212 1213 final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext); 1214 final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams( 1215 ViewGroup.LayoutParams.MATCH_PARENT, height); 1216 backgroundView.addView(contentView, listParams); 1217 1218 return backgroundView; 1219 } 1220 1221 /** 1222 * Wraps a content view in a FrameLayout. 1223 * 1224 * @param contentView the content view to wrap 1225 * @return a FrameLayout that wraps the content view 1226 */ 1227 private PopupDecorView createDecorView(View contentView) { 1228 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 1229 final int height; 1230 if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { 1231 height = ViewGroup.LayoutParams.WRAP_CONTENT; 1232 } else { 1233 height = ViewGroup.LayoutParams.MATCH_PARENT; 1234 } 1235 1236 final PopupDecorView decorView = new PopupDecorView(mContext); 1237 decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height); 1238 decorView.setClipChildren(false); 1239 decorView.setClipToPadding(false); 1240 1241 return decorView; 1242 } 1243 1244 /** 1245 * <p>Invoke the popup window by adding the content view to the window 1246 * manager.</p> 1247 * 1248 * <p>The content view must be non-null when this method is invoked.</p> 1249 * 1250 * @param p the layout parameters of the popup's content view 1251 */ 1252 private void invokePopup(WindowManager.LayoutParams p) { 1253 if (mContext != null) { 1254 p.packageName = mContext.getPackageName(); 1255 } 1256 1257 final PopupDecorView decorView = mDecorView; 1258 decorView.setFitsSystemWindows(mLayoutInsetDecor); 1259 1260 setLayoutDirectionFromAnchor(); 1261 1262 mWindowManager.addView(decorView, p); 1263 1264 if (mEnterTransition != null) { 1265 decorView.requestEnterTransition(mEnterTransition); 1266 } 1267 } 1268 1269 private void setLayoutDirectionFromAnchor() { 1270 if (mAnchor != null) { 1271 View anchor = mAnchor.get(); 1272 if (anchor != null && mPopupViewInitialLayoutDirectionInherited) { 1273 mDecorView.setLayoutDirection(anchor.getLayoutDirection()); 1274 } 1275 } 1276 } 1277 1278 /** 1279 * <p>Generate the layout parameters for the popup window.</p> 1280 * 1281 * @param token the window token used to bind the popup's window 1282 * 1283 * @return the layout parameters to pass to the window manager 1284 */ 1285 private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { 1286 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 1287 1288 // These gravity settings put the view at the top left corner of the 1289 // screen. The view is then positioned to the appropriate location by 1290 // setting the x and y offsets to match the anchor's bottom-left 1291 // corner. 1292 p.gravity = Gravity.START | Gravity.TOP; 1293 p.flags = computeFlags(p.flags); 1294 p.type = mWindowLayoutType; 1295 p.token = token; 1296 p.softInputMode = mSoftInputMode; 1297 p.windowAnimations = computeAnimationResource(); 1298 1299 if (mBackground != null) { 1300 p.format = mBackground.getOpacity(); 1301 } else { 1302 p.format = PixelFormat.TRANSLUCENT; 1303 } 1304 1305 if (mHeightMode < 0) { 1306 p.height = mLastHeight = mHeightMode; 1307 } else { 1308 p.height = mLastHeight = mHeight; 1309 } 1310 1311 if (mWidthMode < 0) { 1312 p.width = mLastWidth = mWidthMode; 1313 } else { 1314 p.width = mLastWidth = mWidth; 1315 } 1316 1317 p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH 1318 | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; 1319 1320 // Used for debugging. 1321 p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); 1322 1323 return p; 1324 } 1325 1326 private int computeFlags(int curFlags) { 1327 curFlags &= ~( 1328 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | 1329 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 1330 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 1331 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 1332 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 1333 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | 1334 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH); 1335 if(mIgnoreCheekPress) { 1336 curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 1337 } 1338 if (!mFocusable) { 1339 curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 1340 if (mInputMethodMode == INPUT_METHOD_NEEDED) { 1341 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1342 } 1343 } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { 1344 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1345 } 1346 if (!mTouchable) { 1347 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 1348 } 1349 if (mOutsideTouchable) { 1350 curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 1351 } 1352 if (!mClippingEnabled) { 1353 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 1354 } 1355 if (isSplitTouchEnabled()) { 1356 curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 1357 } 1358 if (mLayoutInScreen) { 1359 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 1360 } 1361 if (mLayoutInsetDecor) { 1362 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 1363 } 1364 if (mNotTouchModal) { 1365 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 1366 } 1367 if (mAttachedInDecor) { 1368 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR; 1369 } 1370 return curFlags; 1371 } 1372 1373 private int computeAnimationResource() { 1374 if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) { 1375 if (mIsDropdown) { 1376 return mAboveAnchor 1377 ? com.android.internal.R.style.Animation_DropDownUp 1378 : com.android.internal.R.style.Animation_DropDownDown; 1379 } 1380 return 0; 1381 } 1382 return mAnimationStyle; 1383 } 1384 1385 /** 1386 * Positions the popup window on screen. When the popup window is too tall 1387 * to fit under the anchor, a parent scroll view is seeked and scrolled up 1388 * to reclaim space. If scrolling is not possible or not enough, the popup 1389 * window gets moved on top of the anchor. 1390 * <p> 1391 * The height must have been set on the layout parameters prior to calling 1392 * this method. 1393 * 1394 * @param anchor the view on which the popup window must be anchored 1395 * @param p the layout parameters used to display the drop down 1396 * @param xoff horizontal offset used to adjust for background padding 1397 * @param yoff vertical offset used to adjust for background padding 1398 * @param gravity horizontal gravity specifying popup alignment 1399 * @return true if the popup is translated upwards to fit on screen 1400 */ 1401 private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff, 1402 int yoff, int gravity) { 1403 final int anchorHeight = anchor.getHeight(); 1404 final int anchorWidth = anchor.getWidth(); 1405 if (mOverlapAnchor) { 1406 yoff -= anchorHeight; 1407 } 1408 1409 anchor.getLocationInWindow(mDrawingLocation); 1410 p.x = mDrawingLocation[0] + xoff; 1411 p.y = mDrawingLocation[1] + anchorHeight + yoff; 1412 1413 final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection()) 1414 & Gravity.HORIZONTAL_GRAVITY_MASK; 1415 if (hgrav == Gravity.RIGHT) { 1416 // Flip the location to align the right sides of the popup and 1417 // anchor instead of left. 1418 p.x -= mPopupWidth - anchorWidth; 1419 } 1420 1421 boolean onTop = false; 1422 1423 p.gravity = Gravity.LEFT | Gravity.TOP; 1424 1425 anchor.getLocationOnScreen(mScreenLocation); 1426 final Rect displayFrame = new Rect(); 1427 anchor.getWindowVisibleDisplayFrame(displayFrame); 1428 1429 final int screenY = mScreenLocation[1] + anchorHeight + yoff; 1430 final View root = anchor.getRootView(); 1431 if (screenY + mPopupHeight > displayFrame.bottom 1432 || p.x + mPopupWidth - root.getWidth() > 0) { 1433 // If the drop down disappears at the bottom of the screen, we try 1434 // to scroll a parent scrollview or move the drop down back up on 1435 // top of the edit box. 1436 if (mAllowScrollingAnchorParent) { 1437 final int scrollX = anchor.getScrollX(); 1438 final int scrollY = anchor.getScrollY(); 1439 final Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff, 1440 scrollY + mPopupHeight + anchorHeight + yoff); 1441 anchor.requestRectangleOnScreen(r, true); 1442 } 1443 1444 // Now we re-evaluate the space available, and decide from that 1445 // whether the pop-up will go above or below the anchor. 1446 anchor.getLocationInWindow(mDrawingLocation); 1447 p.x = mDrawingLocation[0] + xoff; 1448 p.y = mDrawingLocation[1] + anchorHeight + yoff; 1449 1450 // Preserve the gravity adjustment. 1451 if (hgrav == Gravity.RIGHT) { 1452 p.x -= mPopupWidth - anchorWidth; 1453 } 1454 1455 // Determine whether there is more space above or below the anchor. 1456 anchor.getLocationOnScreen(mScreenLocation); 1457 onTop = (displayFrame.bottom - mScreenLocation[1] - anchorHeight - yoff) < 1458 (mScreenLocation[1] - yoff - displayFrame.top); 1459 if (!mOverlapAnchor) { 1460 if (onTop) { 1461 p.gravity = Gravity.LEFT | Gravity.BOTTOM; 1462 p.y = root.getHeight() - mDrawingLocation[1] + yoff; 1463 } else { 1464 p.y = mDrawingLocation[1] + anchorHeight + yoff; 1465 } 1466 } 1467 } 1468 1469 if (mClipToScreen) { 1470 final int displayFrameWidth = displayFrame.right - displayFrame.left; 1471 final int right = p.x + p.width; 1472 if (right > displayFrameWidth) { 1473 p.x -= right - displayFrameWidth; 1474 } 1475 1476 if (p.x < displayFrame.left) { 1477 p.x = displayFrame.left; 1478 p.width = Math.min(p.width, displayFrameWidth); 1479 } 1480 1481 if (mOverlapAnchor) { 1482 final int displayFrameHeight = displayFrame.bottom - displayFrame.top; 1483 final int bottom = p.y + p.height; 1484 if (bottom > displayFrame.bottom) { 1485 p.y -= bottom - displayFrameHeight; 1486 } 1487 } else { 1488 if (onTop) { 1489 final int popupTop = mScreenLocation[1] + yoff - mPopupHeight; 1490 if (popupTop < 0) { 1491 p.y += popupTop; 1492 } 1493 } else { 1494 p.y = Math.max(p.y, displayFrame.top); 1495 } 1496 } 1497 } 1498 1499 p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; 1500 1501 return onTop; 1502 } 1503 1504 /** 1505 * Returns the maximum height that is available for the popup to be 1506 * completely shown. It is recommended that this height be the maximum for 1507 * the popup's height, otherwise it is possible that the popup will be 1508 * clipped. 1509 * 1510 * @param anchor The view on which the popup window must be anchored. 1511 * @return The maximum available height for the popup to be completely 1512 * shown. 1513 */ 1514 public int getMaxAvailableHeight(@NonNull View anchor) { 1515 return getMaxAvailableHeight(anchor, 0); 1516 } 1517 1518 /** 1519 * Returns the maximum height that is available for the popup to be 1520 * completely shown. It is recommended that this height be the maximum for 1521 * the popup's height, otherwise it is possible that the popup will be 1522 * clipped. 1523 * 1524 * @param anchor The view on which the popup window must be anchored. 1525 * @param yOffset y offset from the view's bottom edge 1526 * @return The maximum available height for the popup to be completely 1527 * shown. 1528 */ 1529 public int getMaxAvailableHeight(@NonNull View anchor, int yOffset) { 1530 return getMaxAvailableHeight(anchor, yOffset, false); 1531 } 1532 1533 /** 1534 * Returns the maximum height that is available for the popup to be 1535 * completely shown, optionally ignoring any bottom decorations such as 1536 * the input method. It is recommended that this height be the maximum for 1537 * the popup's height, otherwise it is possible that the popup will be 1538 * clipped. 1539 * 1540 * @param anchor The view on which the popup window must be anchored. 1541 * @param yOffset y offset from the view's bottom edge 1542 * @param ignoreBottomDecorations if true, the height returned will be 1543 * all the way to the bottom of the display, ignoring any 1544 * bottom decorations 1545 * @return The maximum available height for the popup to be completely 1546 * shown. 1547 */ 1548 public int getMaxAvailableHeight( 1549 @NonNull View anchor, int yOffset, boolean ignoreBottomDecorations) { 1550 final Rect displayFrame = new Rect(); 1551 anchor.getWindowVisibleDisplayFrame(displayFrame); 1552 1553 final int[] anchorPos = mDrawingLocation; 1554 anchor.getLocationOnScreen(anchorPos); 1555 1556 final int bottomEdge; 1557 if (ignoreBottomDecorations) { 1558 final Resources res = anchor.getContext().getResources(); 1559 bottomEdge = res.getDisplayMetrics().heightPixels; 1560 } else { 1561 bottomEdge = displayFrame.bottom; 1562 } 1563 1564 final int distanceToBottom; 1565 if (mOverlapAnchor) { 1566 distanceToBottom = bottomEdge - anchorPos[1] - yOffset; 1567 } else { 1568 distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; 1569 } 1570 final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; 1571 1572 // anchorPos[1] is distance from anchor to top of screen 1573 int returnedHeight = Math.max(distanceToBottom, distanceToTop); 1574 if (mBackground != null) { 1575 mBackground.getPadding(mTempRect); 1576 returnedHeight -= mTempRect.top + mTempRect.bottom; 1577 } 1578 1579 return returnedHeight; 1580 } 1581 1582 /** 1583 * Disposes of the popup window. This method can be invoked only after 1584 * {@link #showAsDropDown(android.view.View)} has been executed. Failing 1585 * that, calling this method will have no effect. 1586 * 1587 * @see #showAsDropDown(android.view.View) 1588 */ 1589 public void dismiss() { 1590 if (!isShowing() || mIsTransitioningToDismiss) { 1591 return; 1592 } 1593 1594 final PopupDecorView decorView = mDecorView; 1595 final View contentView = mContentView; 1596 1597 final ViewGroup contentHolder; 1598 final ViewParent contentParent = contentView.getParent(); 1599 if (contentParent instanceof ViewGroup) { 1600 contentHolder = ((ViewGroup) contentParent); 1601 } else { 1602 contentHolder = null; 1603 } 1604 1605 // Ensure any ongoing or pending transitions are canceled. 1606 decorView.cancelTransitions(); 1607 1608 mIsShowing = false; 1609 mIsTransitioningToDismiss = true; 1610 1611 final Transition exitTransition = mExitTransition; 1612 if (exitTransition != null && decorView.isLaidOut()) { 1613 // The decor view is non-interactive during exit transitions. 1614 final LayoutParams p = (LayoutParams) decorView.getLayoutParams(); 1615 p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE; 1616 p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE; 1617 mWindowManager.updateViewLayout(decorView, p); 1618 1619 final Rect epicenter = getRelativeAnchorBounds(); 1620 exitTransition.setEpicenterCallback(new EpicenterCallback() { 1621 @Override 1622 public Rect onGetEpicenter(Transition transition) { 1623 return epicenter; 1624 } 1625 }); 1626 decorView.startExitTransition(exitTransition, new TransitionListenerAdapter() { 1627 @Override 1628 public void onTransitionEnd(Transition transition) { 1629 dismissImmediate(decorView, contentHolder, contentView); 1630 } 1631 }); 1632 } else { 1633 dismissImmediate(decorView, contentHolder, contentView); 1634 } 1635 1636 // Clears the anchor view. 1637 unregisterForScrollChanged(); 1638 1639 if (mOnDismissListener != null) { 1640 mOnDismissListener.onDismiss(); 1641 } 1642 } 1643 1644 private Rect getRelativeAnchorBounds() { 1645 final View anchor = mAnchor != null ? mAnchor.get() : null; 1646 final View decor = mDecorView; 1647 if (anchor == null || decor == null) { 1648 return null; 1649 } 1650 1651 final int[] anchorLocation = anchor.getLocationOnScreen(); 1652 final int[] popupLocation = mDecorView.getLocationOnScreen(); 1653 1654 // Compute the position of the anchor relative to the popup. 1655 final Rect bounds = new Rect(0, 0, anchor.getWidth(), anchor.getHeight()); 1656 bounds.offset(anchorLocation[0] - popupLocation[0], anchorLocation[1] - popupLocation[1]); 1657 return bounds; 1658 } 1659 1660 /** 1661 * Removes the popup from the window manager and tears down the supporting 1662 * view hierarchy, if necessary. 1663 */ 1664 private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) { 1665 // If this method gets called and the decor view doesn't have a parent, 1666 // then it was either never added or was already removed. That should 1667 // never happen, but it's worth checking to avoid potential crashes. 1668 if (decorView.getParent() != null) { 1669 mWindowManager.removeViewImmediate(decorView); 1670 } 1671 1672 if (contentHolder != null) { 1673 contentHolder.removeView(contentView); 1674 } 1675 1676 // This needs to stay until after all transitions have ended since we 1677 // need the reference to cancel transitions in preparePopup(). 1678 mDecorView = null; 1679 mBackgroundView = null; 1680 mIsTransitioningToDismiss = false; 1681 } 1682 1683 /** 1684 * Sets the listener to be called when the window is dismissed. 1685 * 1686 * @param onDismissListener The listener. 1687 */ 1688 public void setOnDismissListener(OnDismissListener onDismissListener) { 1689 mOnDismissListener = onDismissListener; 1690 } 1691 1692 /** 1693 * Updates the state of the popup window, if it is currently being displayed, 1694 * from the currently set state. 1695 * <p> 1696 * This includes: 1697 * <ul> 1698 * <li>{@link #setClippingEnabled(boolean)}</li> 1699 * <li>{@link #setFocusable(boolean)}</li> 1700 * <li>{@link #setIgnoreCheekPress()}</li> 1701 * <li>{@link #setInputMethodMode(int)}</li> 1702 * <li>{@link #setTouchable(boolean)}</li> 1703 * <li>{@link #setAnimationStyle(int)}</li> 1704 * </ul> 1705 */ 1706 public void update() { 1707 if (!isShowing() || mContentView == null) { 1708 return; 1709 } 1710 1711 final WindowManager.LayoutParams p = 1712 (WindowManager.LayoutParams) mDecorView.getLayoutParams(); 1713 1714 boolean update = false; 1715 1716 final int newAnim = computeAnimationResource(); 1717 if (newAnim != p.windowAnimations) { 1718 p.windowAnimations = newAnim; 1719 update = true; 1720 } 1721 1722 final int newFlags = computeFlags(p.flags); 1723 if (newFlags != p.flags) { 1724 p.flags = newFlags; 1725 update = true; 1726 } 1727 1728 if (update) { 1729 setLayoutDirectionFromAnchor(); 1730 mWindowManager.updateViewLayout(mDecorView, p); 1731 } 1732 } 1733 1734 /** 1735 * Updates the dimension of the popup window. 1736 * <p> 1737 * Calling this function also updates the window with the current popup 1738 * state as described for {@link #update()}. 1739 * 1740 * @param width the new width, must be >= 0 or -1 to ignore 1741 * @param height the new height, must be >= 0 or -1 to ignore 1742 */ 1743 public void update(int width, int height) { 1744 final WindowManager.LayoutParams p = 1745 (WindowManager.LayoutParams) mDecorView.getLayoutParams(); 1746 update(p.x, p.y, width, height, false); 1747 } 1748 1749 /** 1750 * Updates the position and the dimension of the popup window. 1751 * <p> 1752 * Width and height can be set to -1 to update location only. Calling this 1753 * function also updates the window with the current popup state as 1754 * described for {@link #update()}. 1755 * 1756 * @param x the new x location 1757 * @param y the new y location 1758 * @param width the new width, must be >= 0 or -1 to ignore 1759 * @param height the new height, must be >= 0 or -1 to ignore 1760 */ 1761 public void update(int x, int y, int width, int height) { 1762 update(x, y, width, height, false); 1763 } 1764 1765 /** 1766 * Updates the position and the dimension of the popup window. 1767 * <p> 1768 * Width and height can be set to -1 to update location only. Calling this 1769 * function also updates the window with the current popup state as 1770 * described for {@link #update()}. 1771 * 1772 * @param x the new x location 1773 * @param y the new y location 1774 * @param width the new width, must be >= 0 or -1 to ignore 1775 * @param height the new height, must be >= 0 or -1 to ignore 1776 * @param force {@code true} to reposition the window even if the specified 1777 * position already seems to correspond to the LayoutParams, 1778 * {@code false} to only reposition if needed 1779 */ 1780 public void update(int x, int y, int width, int height, boolean force) { 1781 if (width >= 0) { 1782 mLastWidth = width; 1783 setWidth(width); 1784 } 1785 1786 if (height >= 0) { 1787 mLastHeight = height; 1788 setHeight(height); 1789 } 1790 1791 if (!isShowing() || mContentView == null) { 1792 return; 1793 } 1794 1795 final WindowManager.LayoutParams p = 1796 (WindowManager.LayoutParams) mDecorView.getLayoutParams(); 1797 1798 boolean update = force; 1799 1800 final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; 1801 if (width != -1 && p.width != finalWidth) { 1802 p.width = mLastWidth = finalWidth; 1803 update = true; 1804 } 1805 1806 final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; 1807 if (height != -1 && p.height != finalHeight) { 1808 p.height = mLastHeight = finalHeight; 1809 update = true; 1810 } 1811 1812 if (p.x != x) { 1813 p.x = x; 1814 update = true; 1815 } 1816 1817 if (p.y != y) { 1818 p.y = y; 1819 update = true; 1820 } 1821 1822 final int newAnim = computeAnimationResource(); 1823 if (newAnim != p.windowAnimations) { 1824 p.windowAnimations = newAnim; 1825 update = true; 1826 } 1827 1828 final int newFlags = computeFlags(p.flags); 1829 if (newFlags != p.flags) { 1830 p.flags = newFlags; 1831 update = true; 1832 } 1833 1834 if (update) { 1835 setLayoutDirectionFromAnchor(); 1836 mWindowManager.updateViewLayout(mDecorView, p); 1837 } 1838 } 1839 1840 /** 1841 * Updates the position and the dimension of the popup window. 1842 * <p> 1843 * Calling this function also updates the window with the current popup 1844 * state as described for {@link #update()}. 1845 * 1846 * @param anchor the popup's anchor view 1847 * @param width the new width, must be >= 0 or -1 to ignore 1848 * @param height the new height, must be >= 0 or -1 to ignore 1849 */ 1850 public void update(View anchor, int width, int height) { 1851 update(anchor, false, 0, 0, true, width, height); 1852 } 1853 1854 /** 1855 * Updates the position and the dimension of the popup window. 1856 * <p> 1857 * Width and height can be set to -1 to update location only. Calling this 1858 * function also updates the window with the current popup state as 1859 * described for {@link #update()}. 1860 * <p> 1861 * If the view later scrolls to move {@code anchor} to a different 1862 * location, the popup will be moved correspondingly. 1863 * 1864 * @param anchor the popup's anchor view 1865 * @param xoff x offset from the view's left edge 1866 * @param yoff y offset from the view's bottom edge 1867 * @param width the new width, must be >= 0 or -1 to ignore 1868 * @param height the new height, must be >= 0 or -1 to ignore 1869 */ 1870 public void update(View anchor, int xoff, int yoff, int width, int height) { 1871 update(anchor, true, xoff, yoff, true, width, height); 1872 } 1873 1874 private void update(View anchor, boolean updateLocation, int xoff, int yoff, 1875 boolean updateDimension, int width, int height) { 1876 1877 if (!isShowing() || mContentView == null) { 1878 return; 1879 } 1880 1881 final WeakReference<View> oldAnchor = mAnchor; 1882 final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff); 1883 if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) { 1884 registerForScrollChanged(anchor, xoff, yoff, mAnchoredGravity); 1885 } else if (needsUpdate) { 1886 // No need to register again if this is a DropDown, showAsDropDown already did. 1887 mAnchorXoff = xoff; 1888 mAnchorYoff = yoff; 1889 } 1890 1891 if (updateDimension) { 1892 if (width == -1) { 1893 width = mPopupWidth; 1894 } else { 1895 mPopupWidth = width; 1896 } 1897 if (height == -1) { 1898 height = mPopupHeight; 1899 } else { 1900 mPopupHeight = height; 1901 } 1902 } 1903 1904 final WindowManager.LayoutParams p = 1905 (WindowManager.LayoutParams) mDecorView.getLayoutParams(); 1906 final int x = p.x; 1907 final int y = p.y; 1908 if (updateLocation) { 1909 updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, mAnchoredGravity)); 1910 } else { 1911 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, 1912 mAnchoredGravity)); 1913 } 1914 1915 update(p.x, p.y, width, height, x != p.x || y != p.y); 1916 } 1917 1918 /** 1919 * Listener that is called when this popup window is dismissed. 1920 */ 1921 public interface OnDismissListener { 1922 /** 1923 * Called when this popup window is dismissed. 1924 */ 1925 public void onDismiss(); 1926 } 1927 1928 private void unregisterForScrollChanged() { 1929 final WeakReference<View> anchorRef = mAnchor; 1930 final View anchor = anchorRef == null ? null : anchorRef.get(); 1931 if (anchor != null) { 1932 final ViewTreeObserver vto = anchor.getViewTreeObserver(); 1933 vto.removeOnScrollChangedListener(mOnScrollChangedListener); 1934 } 1935 1936 mAnchor = null; 1937 } 1938 1939 private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) { 1940 unregisterForScrollChanged(); 1941 1942 mAnchor = new WeakReference<>(anchor); 1943 1944 final ViewTreeObserver vto = anchor.getViewTreeObserver(); 1945 if (vto != null) { 1946 vto.addOnScrollChangedListener(mOnScrollChangedListener); 1947 } 1948 1949 mAnchorXoff = xoff; 1950 mAnchorYoff = yoff; 1951 mAnchoredGravity = gravity; 1952 } 1953 1954 private class PopupDecorView extends FrameLayout { 1955 private TransitionListenerAdapter mPendingExitListener; 1956 1957 public PopupDecorView(Context context) { 1958 super(context); 1959 } 1960 1961 @Override 1962 public boolean dispatchKeyEvent(KeyEvent event) { 1963 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1964 if (getKeyDispatcherState() == null) { 1965 return super.dispatchKeyEvent(event); 1966 } 1967 1968 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 1969 final KeyEvent.DispatcherState state = getKeyDispatcherState(); 1970 if (state != null) { 1971 state.startTracking(event, this); 1972 } 1973 return true; 1974 } else if (event.getAction() == KeyEvent.ACTION_UP) { 1975 final KeyEvent.DispatcherState state = getKeyDispatcherState(); 1976 if (state != null && state.isTracking(event) && !event.isCanceled()) { 1977 dismiss(); 1978 return true; 1979 } 1980 } 1981 return super.dispatchKeyEvent(event); 1982 } else { 1983 return super.dispatchKeyEvent(event); 1984 } 1985 } 1986 1987 @Override 1988 public boolean dispatchTouchEvent(MotionEvent ev) { 1989 if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { 1990 return true; 1991 } 1992 return super.dispatchTouchEvent(ev); 1993 } 1994 1995 @Override 1996 public boolean onTouchEvent(MotionEvent event) { 1997 final int x = (int) event.getX(); 1998 final int y = (int) event.getY(); 1999 2000 if ((event.getAction() == MotionEvent.ACTION_DOWN) 2001 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 2002 dismiss(); 2003 return true; 2004 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 2005 dismiss(); 2006 return true; 2007 } else { 2008 return super.onTouchEvent(event); 2009 } 2010 } 2011 2012 /** 2013 * Requests that an enter transition run after the next layout pass. 2014 */ 2015 public void requestEnterTransition(Transition transition) { 2016 final ViewTreeObserver observer = getViewTreeObserver(); 2017 if (observer != null && transition != null) { 2018 final Transition enterTransition = transition.clone(); 2019 2020 // Postpone the enter transition after the first layout pass. 2021 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 2022 @Override 2023 public void onGlobalLayout() { 2024 final ViewTreeObserver observer = getViewTreeObserver(); 2025 if (observer != null) { 2026 observer.removeOnGlobalLayoutListener(this); 2027 } 2028 2029 final Rect epicenter = getRelativeAnchorBounds(); 2030 enterTransition.setEpicenterCallback(new EpicenterCallback() { 2031 @Override 2032 public Rect onGetEpicenter(Transition transition) { 2033 return epicenter; 2034 } 2035 }); 2036 startEnterTransition(enterTransition); 2037 } 2038 }); 2039 } 2040 } 2041 2042 /** 2043 * Starts the pending enter transition, if one is set. 2044 */ 2045 private void startEnterTransition(Transition enterTransition) { 2046 final int count = getChildCount(); 2047 for (int i = 0; i < count; i++) { 2048 final View child = getChildAt(i); 2049 enterTransition.addTarget(child); 2050 child.setVisibility(View.INVISIBLE); 2051 } 2052 2053 TransitionManager.beginDelayedTransition(this, enterTransition); 2054 2055 for (int i = 0; i < count; i++) { 2056 final View child = getChildAt(i); 2057 child.setVisibility(View.VISIBLE); 2058 } 2059 } 2060 2061 /** 2062 * Starts an exit transition immediately. 2063 * <p> 2064 * <strong>Note:</strong> The transition listener is guaranteed to have 2065 * its {@code onTransitionEnd} method called even if the transition 2066 * never starts; however, it may be called with a {@code null} argument. 2067 */ 2068 public void startExitTransition(Transition transition, final TransitionListener listener) { 2069 if (transition == null) { 2070 return; 2071 } 2072 2073 // The exit listener MUST be called for cleanup, even if the 2074 // transition never starts or ends. Stash it for later. 2075 mPendingExitListener = new TransitionListenerAdapter() { 2076 @Override 2077 public void onTransitionEnd(Transition transition) { 2078 listener.onTransitionEnd(transition); 2079 2080 // The listener was called. Our job here is done. 2081 mPendingExitListener = null; 2082 } 2083 }; 2084 2085 final Transition exitTransition = transition.clone(); 2086 exitTransition.addListener(mPendingExitListener); 2087 2088 final int count = getChildCount(); 2089 for (int i = 0; i < count; i++) { 2090 final View child = getChildAt(i); 2091 exitTransition.addTarget(child); 2092 } 2093 2094 TransitionManager.beginDelayedTransition(this, exitTransition); 2095 2096 for (int i = 0; i < count; i++) { 2097 final View child = getChildAt(i); 2098 child.setVisibility(View.INVISIBLE); 2099 } 2100 } 2101 2102 /** 2103 * Cancels all pending or current transitions. 2104 */ 2105 public void cancelTransitions() { 2106 TransitionManager.endTransitions(this); 2107 2108 if (mPendingExitListener != null) { 2109 mPendingExitListener.onTransitionEnd(null); 2110 } 2111 } 2112 } 2113 2114 private class PopupBackgroundView extends FrameLayout { 2115 public PopupBackgroundView(Context context) { 2116 super(context); 2117 } 2118 2119 @Override 2120 protected int[] onCreateDrawableState(int extraSpace) { 2121 if (mAboveAnchor) { 2122 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 2123 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); 2124 return drawableState; 2125 } else { 2126 return super.onCreateDrawableState(extraSpace); 2127 } 2128 } 2129 } 2130} 2131