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 17 package android.widget; 18 19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 20 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 21 import static android.view.WindowManager.LayoutParams 22 .PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; 23 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.UnsupportedAppUsage; 28 import android.content.Context; 29 import android.content.res.TypedArray; 30 import android.graphics.PixelFormat; 31 import android.graphics.Rect; 32 import android.graphics.drawable.Drawable; 33 import android.graphics.drawable.StateListDrawable; 34 import android.os.Build; 35 import android.os.IBinder; 36 import android.transition.Transition; 37 import android.transition.Transition.EpicenterCallback; 38 import android.transition.Transition.TransitionListener; 39 import android.transition.TransitionInflater; 40 import android.transition.TransitionListenerAdapter; 41 import android.transition.TransitionManager; 42 import android.transition.TransitionSet; 43 import android.util.AttributeSet; 44 import android.view.Gravity; 45 import android.view.KeyEvent; 46 import android.view.KeyboardShortcutGroup; 47 import android.view.MotionEvent; 48 import android.view.View; 49 import android.view.View.OnAttachStateChangeListener; 50 import android.view.View.OnTouchListener; 51 import android.view.ViewGroup; 52 import android.view.ViewParent; 53 import android.view.ViewTreeObserver; 54 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 55 import android.view.ViewTreeObserver.OnScrollChangedListener; 56 import android.view.WindowManager; 57 import android.view.WindowManager.LayoutParams; 58 import android.view.WindowManager.LayoutParams.SoftInputModeFlags; 59 import android.view.WindowManagerGlobal; 60 61 import com.android.internal.R; 62 63 import java.lang.ref.WeakReference; 64 import java.util.List; 65 66 /** 67 * <p> 68 * This class represents a popup window that can be used to display an 69 * arbitrary view. The popup window is a floating container that appears on top 70 * of the current activity. 71 * </p> 72 * <a name="Animation"></a> 73 * <h3>Animation</h3> 74 * <p> 75 * On all versions of Android, popup window enter and exit animations may be 76 * specified by calling {@link #setAnimationStyle(int)} and passing the 77 * resource ID for an animation style that defines {@code windowEnterAnimation} 78 * and {@code windowExitAnimation}. For example, passing 79 * {@link android.R.style#Animation_Dialog} will give a scale and alpha 80 * animation. 81 * </br> 82 * A window animation style may also be specified in the popup window's style 83 * XML via the {@link android.R.styleable#PopupWindow_popupAnimationStyle popupAnimationStyle} 84 * attribute. 85 * </p> 86 * <p> 87 * Starting with API 23, more complex popup window enter and exit transitions 88 * may be specified by calling either {@link #setEnterTransition(Transition)} 89 * or {@link #setExitTransition(Transition)} and passing a {@link Transition}. 90 * </br> 91 * Popup enter and exit transitions may also be specified in the popup window's 92 * style XML via the {@link android.R.styleable#PopupWindow_popupEnterTransition popupEnterTransition} 93 * and {@link android.R.styleable#PopupWindow_popupExitTransition popupExitTransition} 94 * attributes, respectively. 95 * </p> 96 * 97 * @attr ref android.R.styleable#PopupWindow_overlapAnchor 98 * @attr ref android.R.styleable#PopupWindow_popupAnimationStyle 99 * @attr ref android.R.styleable#PopupWindow_popupBackground 100 * @attr ref android.R.styleable#PopupWindow_popupElevation 101 * @attr ref android.R.styleable#PopupWindow_popupEnterTransition 102 * @attr ref android.R.styleable#PopupWindow_popupExitTransition 103 * 104 * @see android.widget.AutoCompleteTextView 105 * @see android.widget.Spinner 106 */ 107 public class PopupWindow { 108 /** 109 * Mode for {@link #setInputMethodMode(int)}: the requirements for the 110 * input method should be based on the focusability of the popup. That is 111 * if it is focusable than it needs to work with the input method, else 112 * it doesn't. 113 */ 114 public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; 115 116 /** 117 * Mode for {@link #setInputMethodMode(int)}: this popup always needs to 118 * work with an input method, regardless of whether it is focusable. This 119 * means that it will always be displayed so that the user can also operate 120 * the input method while it is shown. 121 */ 122 public static final int INPUT_METHOD_NEEDED = 1; 123 124 /** 125 * Mode for {@link #setInputMethodMode(int)}: this popup never needs to 126 * work with an input method, regardless of whether it is focusable. This 127 * means that it will always be displayed to use as much space on the 128 * screen as needed, regardless of whether this covers the input method. 129 */ 130 public static final int INPUT_METHOD_NOT_NEEDED = 2; 131 132 private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START; 133 134 /** 135 * Default animation style indicating that separate animations should be 136 * used for top/bottom anchoring states. 137 */ 138 private static final int ANIMATION_STYLE_DEFAULT = -1; 139 140 private final int[] mTmpDrawingLocation = new int[2]; 141 private final int[] mTmpScreenLocation = new int[2]; 142 private final int[] mTmpAppLocation = new int[2]; 143 private final Rect mTempRect = new Rect(); 144 145 @UnsupportedAppUsage 146 private Context mContext; 147 @UnsupportedAppUsage 148 private WindowManager mWindowManager; 149 150 /** 151 * Keeps track of popup's parent's decor view. This is needed to dispatch 152 * requestKeyboardShortcuts to the owning Activity. 153 */ 154 private WeakReference<View> mParentRootView; 155 156 @UnsupportedAppUsage 157 private boolean mIsShowing; 158 private boolean mIsTransitioningToDismiss; 159 @UnsupportedAppUsage 160 private boolean mIsDropdown; 161 162 /** View that handles event dispatch and content transitions. */ 163 @UnsupportedAppUsage 164 private PopupDecorView mDecorView; 165 166 /** View that holds the background and may animate during a transition. */ 167 @UnsupportedAppUsage 168 private View mBackgroundView; 169 170 /** The contents of the popup. May be identical to the background view. */ 171 @UnsupportedAppUsage 172 private View mContentView; 173 174 private boolean mFocusable; 175 private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; 176 @SoftInputModeFlags 177 private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; 178 private boolean mTouchable = true; 179 private boolean mOutsideTouchable = false; 180 private boolean mClippingEnabled = true; 181 private int mSplitTouchEnabled = -1; 182 @UnsupportedAppUsage 183 private boolean mLayoutInScreen; 184 private boolean mClipToScreen; 185 private boolean mAllowScrollingAnchorParent = true; 186 private boolean mLayoutInsetDecor = false; 187 @UnsupportedAppUsage 188 private boolean mNotTouchModal; 189 private boolean mAttachedInDecor = true; 190 private boolean mAttachedInDecorSet = false; 191 192 @UnsupportedAppUsage 193 private OnTouchListener mTouchInterceptor; 194 195 @UnsupportedAppUsage 196 private int mWidthMode; 197 private int mWidth = LayoutParams.WRAP_CONTENT; 198 @UnsupportedAppUsage 199 private int mLastWidth; 200 @UnsupportedAppUsage 201 private int mHeightMode; 202 private int mHeight = LayoutParams.WRAP_CONTENT; 203 @UnsupportedAppUsage 204 private int mLastHeight; 205 206 private float mElevation; 207 208 private Drawable mBackground; 209 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 210 private Drawable mAboveAnchorBackgroundDrawable; 211 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 212 private Drawable mBelowAnchorBackgroundDrawable; 213 214 private Transition mEnterTransition; 215 private Transition mExitTransition; 216 private Rect mEpicenterBounds; 217 218 @UnsupportedAppUsage 219 private boolean mAboveAnchor; 220 @UnsupportedAppUsage 221 private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 222 223 @UnsupportedAppUsage 224 private OnDismissListener mOnDismissListener; 225 private boolean mIgnoreCheekPress = false; 226 227 @UnsupportedAppUsage 228 private int mAnimationStyle = ANIMATION_STYLE_DEFAULT; 229 230 private int mGravity = Gravity.NO_GRAVITY; 231 232 private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] { 233 com.android.internal.R.attr.state_above_anchor 234 }; 235 236 private final OnAttachStateChangeListener mOnAnchorDetachedListener = 237 new OnAttachStateChangeListener() { 238 @Override 239 public void onViewAttachedToWindow(View v) { 240 // Anchor might have been reattached in a different position. 241 alignToAnchor(); 242 } 243 244 @Override 245 public void onViewDetachedFromWindow(View v) { 246 // Leave the popup in its current position. 247 // The anchor might become attached again. 248 } 249 }; 250 251 private final OnAttachStateChangeListener mOnAnchorRootDetachedListener = 252 new OnAttachStateChangeListener() { 253 @Override 254 public void onViewAttachedToWindow(View v) {} 255 256 @Override 257 public void onViewDetachedFromWindow(View v) { 258 mIsAnchorRootAttached = false; 259 } 260 }; 261 262 @UnsupportedAppUsage 263 private WeakReference<View> mAnchor; 264 private WeakReference<View> mAnchorRoot; 265 private boolean mIsAnchorRootAttached; 266 267 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 268 private final OnScrollChangedListener mOnScrollChangedListener = this::alignToAnchor; 269 270 private final View.OnLayoutChangeListener mOnLayoutChangeListener = 271 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> alignToAnchor(); 272 273 private int mAnchorXoff; 274 private int mAnchorYoff; 275 private int mAnchoredGravity; 276 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 277 private boolean mOverlapAnchor; 278 279 private boolean mPopupViewInitialLayoutDirectionInherited; 280 281 /** 282 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 283 * 284 * <p>The popup does provide a background.</p> 285 */ 286 public PopupWindow(Context context) { 287 this(context, null); 288 } 289 290 /** 291 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 292 * 293 * <p>The popup does provide a background.</p> 294 */ 295 public PopupWindow(Context context, AttributeSet attrs) { 296 this(context, attrs, com.android.internal.R.attr.popupWindowStyle); 297 } 298 299 /** 300 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 301 * 302 * <p>The popup does provide a background.</p> 303 */ 304 public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) { 305 this(context, attrs, defStyleAttr, 0); 306 } 307 308 /** 309 * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p> 310 * 311 * <p>The popup does not provide a background.</p> 312 */ 313 public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 314 mContext = context; 315 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 316 317 final TypedArray a = context.obtainStyledAttributes( 318 attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes); 319 final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground); 320 mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0); 321 mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); 322 323 // Preserve default behavior from Gingerbread. If the animation is 324 // undefined or explicitly specifies the Gingerbread animation style, 325 // use a sentinel value. 326 if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) { 327 final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0); 328 if (animStyle == R.style.Animation_PopupWindow) { 329 mAnimationStyle = ANIMATION_STYLE_DEFAULT; 330 } else { 331 mAnimationStyle = animStyle; 332 } 333 } else { 334 mAnimationStyle = ANIMATION_STYLE_DEFAULT; 335 } 336 337 final Transition enterTransition = getTransition(a.getResourceId( 338 R.styleable.PopupWindow_popupEnterTransition, 0)); 339 final Transition exitTransition; 340 if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) { 341 exitTransition = getTransition(a.getResourceId( 342 R.styleable.PopupWindow_popupExitTransition, 0)); 343 } else { 344 exitTransition = enterTransition == null ? null : enterTransition.clone(); 345 } 346 347 a.recycle(); 348 349 setEnterTransition(enterTransition); 350 setExitTransition(exitTransition); 351 setBackgroundDrawable(bg); 352 } 353 354 /** 355 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 356 * 357 * <p>The popup does not provide any background. This should be handled 358 * by the content view.</p> 359 */ 360 public PopupWindow() { 361 this(null, 0, 0); 362 } 363 364 /** 365 * <p>Create a new non focusable popup window which can display the 366 * <tt>contentView</tt>. The dimension of the window are (0,0).</p> 367 * 368 * <p>The popup does not provide any background. This should be handled 369 * by the content view.</p> 370 * 371 * @param contentView the popup's content 372 */ 373 public PopupWindow(View contentView) { 374 this(contentView, 0, 0); 375 } 376 377 /** 378 * <p>Create a new empty, non focusable popup window. The dimension of the 379 * window must be passed to this constructor.</p> 380 * 381 * <p>The popup does not provide any background. This should be handled 382 * by the content view.</p> 383 * 384 * @param width the popup's width 385 * @param height the popup's height 386 */ 387 public PopupWindow(int width, int height) { 388 this(null, width, height); 389 } 390 391 /** 392 * <p>Create a new non focusable popup window which can display the 393 * <tt>contentView</tt>. The dimension of the window must be passed to 394 * this constructor.</p> 395 * 396 * <p>The popup does not provide any background. This should be handled 397 * by the content view.</p> 398 * 399 * @param contentView the popup's content 400 * @param width the popup's width 401 * @param height the popup's height 402 */ 403 public PopupWindow(View contentView, int width, int height) { 404 this(contentView, width, height, false); 405 } 406 407 /** 408 * <p>Create a new popup window which can display the <tt>contentView</tt>. 409 * The dimension of the window must be passed to this constructor.</p> 410 * 411 * <p>The popup does not provide any background. This should be handled 412 * by the content view.</p> 413 * 414 * @param contentView the popup's content 415 * @param width the popup's width 416 * @param height the popup's height 417 * @param focusable true if the popup can be focused, false otherwise 418 */ 419 public PopupWindow(View contentView, int width, int height, boolean focusable) { 420 if (contentView != null) { 421 mContext = contentView.getContext(); 422 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 423 } 424 425 setContentView(contentView); 426 setWidth(width); 427 setHeight(height); 428 setFocusable(focusable); 429 } 430 431 /** 432 * Sets the enter transition to be used when the popup window is shown. 433 * 434 * @param enterTransition the enter transition, or {@code null} to clear 435 * @see #getEnterTransition() 436 * @attr ref android.R.styleable#PopupWindow_popupEnterTransition 437 */ 438 public void setEnterTransition(@Nullable Transition enterTransition) { 439 mEnterTransition = enterTransition; 440 } 441 442 /** 443 * Returns the enter transition to be used when the popup window is shown. 444 * 445 * @return the enter transition, or {@code null} if not set 446 * @see #setEnterTransition(Transition) 447 * @attr ref android.R.styleable#PopupWindow_popupEnterTransition 448 */ 449 @Nullable 450 public Transition getEnterTransition() { 451 return mEnterTransition; 452 } 453 454 /** 455 * Sets the exit transition to be used when the popup window is dismissed. 456 * 457 * @param exitTransition the exit transition, or {@code null} to clear 458 * @see #getExitTransition() 459 * @attr ref android.R.styleable#PopupWindow_popupExitTransition 460 */ 461 public void setExitTransition(@Nullable Transition exitTransition) { 462 mExitTransition = exitTransition; 463 } 464 465 /** 466 * Returns the exit transition to be used when the popup window is 467 * dismissed. 468 * 469 * @return the exit transition, or {@code null} if not set 470 * @see #setExitTransition(Transition) 471 * @attr ref android.R.styleable#PopupWindow_popupExitTransition 472 */ 473 @Nullable 474 public Transition getExitTransition() { 475 return mExitTransition; 476 } 477 478 /** 479 * <p>Returns bounds which are used as a center of the enter and exit transitions.<p/> 480 * 481 * <p>Transitions use Rect, referred to as the epicenter, to orient 482 * the direction of travel. For popup windows, the anchor view bounds are 483 * used as the default epicenter.</p> 484 * 485 * <p>See {@link Transition#setEpicenterCallback(EpicenterCallback)} for more 486 * information about how transition epicenters work.</p> 487 * 488 * @return bounds relative to anchor view, or {@code null} if not set 489 * @see #setEpicenterBounds(Rect) 490 */ 491 @Nullable 492 public Rect getEpicenterBounds() { 493 return mEpicenterBounds != null ? new Rect(mEpicenterBounds) : null; 494 } 495 496 /** 497 * <p>Sets the bounds used as the epicenter of the enter and exit transitions.</p> 498 * 499 * <p>Transitions use Rect, referred to as the epicenter, to orient 500 * the direction of travel. For popup windows, the anchor view bounds are 501 * used as the default epicenter.</p> 502 * 503 * <p>See {@link Transition#setEpicenterCallback(EpicenterCallback)} for more 504 * information about how transition epicenters work.</p> 505 * 506 * @param bounds the epicenter bounds relative to the anchor view, or 507 * {@code null} to use the default epicenter 508 * 509 * @see #getEpicenterBounds() 510 */ 511 public void setEpicenterBounds(@Nullable Rect bounds) { 512 mEpicenterBounds = bounds != null ? new Rect(bounds) : null; 513 } 514 515 private Transition getTransition(int resId) { 516 if (resId != 0 && resId != R.transition.no_transition) { 517 final TransitionInflater inflater = TransitionInflater.from(mContext); 518 final Transition transition = inflater.inflateTransition(resId); 519 if (transition != null) { 520 final boolean isEmpty = transition instanceof TransitionSet 521 && ((TransitionSet) transition).getTransitionCount() == 0; 522 if (!isEmpty) { 523 return transition; 524 } 525 } 526 } 527 return null; 528 } 529 530 /** 531 * Return the drawable used as the popup window's background. 532 * 533 * @return the background drawable or {@code null} if not set 534 * @see #setBackgroundDrawable(Drawable) 535 * @attr ref android.R.styleable#PopupWindow_popupBackground 536 */ 537 public Drawable getBackground() { 538 return mBackground; 539 } 540 541 /** 542 * Specifies the background drawable for this popup window. The background 543 * can be set to {@code null}. 544 * 545 * @param background the popup's background 546 * @see #getBackground() 547 * @attr ref android.R.styleable#PopupWindow_popupBackground 548 */ 549 public void setBackgroundDrawable(Drawable background) { 550 mBackground = background; 551 552 // If this is a StateListDrawable, try to find and store the drawable to be 553 // used when the drop-down is placed above its anchor view, and the one to be 554 // used when the drop-down is placed below its anchor view. We extract 555 // the drawables ourselves to work around a problem with using refreshDrawableState 556 // that it will take into account the padding of all drawables specified in a 557 // StateListDrawable, thus adding superfluous padding to drop-down views. 558 // 559 // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and 560 // at least one other drawable, intended for the 'below-anchor state'. 561 if (mBackground instanceof StateListDrawable) { 562 StateListDrawable stateList = (StateListDrawable) mBackground; 563 564 // Find the above-anchor view - this one's easy, it should be labeled as such. 565 int aboveAnchorStateIndex = stateList.findStateDrawableIndex(ABOVE_ANCHOR_STATE_SET); 566 567 // Now, for the below-anchor view, look for any other drawable specified in the 568 // StateListDrawable which is not for the above-anchor state and use that. 569 int count = stateList.getStateCount(); 570 int belowAnchorStateIndex = -1; 571 for (int i = 0; i < count; i++) { 572 if (i != aboveAnchorStateIndex) { 573 belowAnchorStateIndex = i; 574 break; 575 } 576 } 577 578 // Store the drawables we found, if we found them. Otherwise, set them both 579 // to null so that we'll just use refreshDrawableState. 580 if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) { 581 mAboveAnchorBackgroundDrawable = stateList.getStateDrawable(aboveAnchorStateIndex); 582 mBelowAnchorBackgroundDrawable = stateList.getStateDrawable(belowAnchorStateIndex); 583 } else { 584 mBelowAnchorBackgroundDrawable = null; 585 mAboveAnchorBackgroundDrawable = null; 586 } 587 } 588 } 589 590 /** 591 * @return the elevation for this popup window in pixels 592 * @see #setElevation(float) 593 * @attr ref android.R.styleable#PopupWindow_popupElevation 594 */ 595 public float getElevation() { 596 return mElevation; 597 } 598 599 /** 600 * Specifies the elevation for this popup window. 601 * 602 * @param elevation the popup's elevation in pixels 603 * @see #getElevation() 604 * @attr ref android.R.styleable#PopupWindow_popupElevation 605 */ 606 public void setElevation(float elevation) { 607 mElevation = elevation; 608 } 609 610 /** 611 * <p>Return the animation style to use the popup appears and disappears</p> 612 * 613 * @return the animation style to use the popup appears and disappears 614 */ 615 public int getAnimationStyle() { 616 return mAnimationStyle; 617 } 618 619 /** 620 * Set the flag on popup to ignore cheek press events; by default this flag 621 * is set to false 622 * which means the popup will not ignore cheek press dispatch events. 623 * 624 * <p>If the popup is showing, calling this method will take effect only 625 * the next time the popup is shown or through a manual call to one of 626 * the {@link #update()} methods.</p> 627 * 628 * @see #update() 629 */ 630 public void setIgnoreCheekPress() { 631 mIgnoreCheekPress = true; 632 } 633 634 /** 635 * <p>Change the animation style resource for this popup.</p> 636 * 637 * <p>If the popup is showing, calling this method will take effect only 638 * the next time the popup is shown or through a manual call to one of 639 * the {@link #update()} methods.</p> 640 * 641 * @param animationStyle animation style to use when the popup appears 642 * and disappears. Set to -1 for the default animation, 0 for no 643 * animation, or a resource identifier for an explicit animation. 644 * 645 * @see #update() 646 */ 647 public void setAnimationStyle(int animationStyle) { 648 mAnimationStyle = animationStyle; 649 } 650 651 /** 652 * <p>Return the view used as the content of the popup window.</p> 653 * 654 * @return a {@link android.view.View} representing the popup's content 655 * 656 * @see #setContentView(android.view.View) 657 */ 658 public View getContentView() { 659 return mContentView; 660 } 661 662 /** 663 * <p>Change the popup's content. The content is represented by an instance 664 * of {@link android.view.View}.</p> 665 * 666 * <p>This method has no effect if called when the popup is showing.</p> 667 * 668 * @param contentView the new content for the popup 669 * 670 * @see #getContentView() 671 * @see #isShowing() 672 */ 673 public void setContentView(View contentView) { 674 if (isShowing()) { 675 return; 676 } 677 678 mContentView = contentView; 679 680 if (mContext == null && mContentView != null) { 681 mContext = mContentView.getContext(); 682 } 683 684 if (mWindowManager == null && mContentView != null) { 685 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 686 } 687 688 // Setting the default for attachedInDecor based on SDK version here 689 // instead of in the constructor since we might not have the context 690 // object in the constructor. We only want to set default here if the 691 // app hasn't already set the attachedInDecor. 692 if (mContext != null && !mAttachedInDecorSet) { 693 // Attach popup window in decor frame of parent window by default for 694 // {@link Build.VERSION_CODES.LOLLIPOP_MR1} or greater. Keep current 695 // behavior of not attaching to decor frame for older SDKs. 696 setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion 697 >= Build.VERSION_CODES.LOLLIPOP_MR1); 698 } 699 700 } 701 702 /** 703 * Set a callback for all touch events being dispatched to the popup 704 * window. 705 */ 706 public void setTouchInterceptor(OnTouchListener l) { 707 mTouchInterceptor = l; 708 } 709 710 /** 711 * <p>Indicate whether the popup window can grab the focus.</p> 712 * 713 * @return true if the popup is focusable, false otherwise 714 * 715 * @see #setFocusable(boolean) 716 */ 717 public boolean isFocusable() { 718 return mFocusable; 719 } 720 721 /** 722 * <p>Changes the focusability of the popup window. When focusable, the 723 * window will grab the focus from the current focused widget if the popup 724 * contains a focusable {@link android.view.View}. By default a popup 725 * window is not focusable.</p> 726 * 727 * <p>If the popup is showing, calling this method will take effect only 728 * the next time the popup is shown or through a manual call to one of 729 * the {@link #update()} methods.</p> 730 * 731 * @param focusable true if the popup should grab focus, false otherwise. 732 * 733 * @see #isFocusable() 734 * @see #isShowing() 735 * @see #update() 736 */ 737 public void setFocusable(boolean focusable) { 738 mFocusable = focusable; 739 } 740 741 /** 742 * Return the current value in {@link #setInputMethodMode(int)}. 743 * 744 * @see #setInputMethodMode(int) 745 */ 746 public int getInputMethodMode() { 747 return mInputMethodMode; 748 749 } 750 751 /** 752 * Control how the popup operates with an input method: one of 753 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, 754 * or {@link #INPUT_METHOD_NOT_NEEDED}. 755 * 756 * <p>If the popup is showing, calling this method will take effect only 757 * the next time the popup is shown or through a manual call to one of 758 * the {@link #update()} methods.</p> 759 * 760 * @see #getInputMethodMode() 761 * @see #update() 762 */ 763 public void setInputMethodMode(int mode) { 764 mInputMethodMode = mode; 765 } 766 767 /** 768 * Sets the operating mode for the soft input area. 769 * 770 * @param mode The desired mode, see 771 * {@link android.view.WindowManager.LayoutParams#softInputMode} 772 * for the full list 773 * 774 * @see android.view.WindowManager.LayoutParams#softInputMode 775 * @see #getSoftInputMode() 776 */ 777 public void setSoftInputMode(@SoftInputModeFlags int mode) { 778 mSoftInputMode = mode; 779 } 780 781 /** 782 * Returns the current value in {@link #setSoftInputMode(int)}. 783 * 784 * @see #setSoftInputMode(int) 785 * @see android.view.WindowManager.LayoutParams#softInputMode 786 */ 787 @SoftInputModeFlags 788 public int getSoftInputMode() { 789 return mSoftInputMode; 790 } 791 792 /** 793 * <p>Indicates whether the popup window receives touch events.</p> 794 * 795 * @return true if the popup is touchable, false otherwise 796 * 797 * @see #setTouchable(boolean) 798 */ 799 public boolean isTouchable() { 800 return mTouchable; 801 } 802 803 /** 804 * <p>Changes the touchability of the popup window. When touchable, the 805 * window will receive touch events, otherwise touch events will go to the 806 * window below it. By default the window is touchable.</p> 807 * 808 * <p>If the popup is showing, calling this method will take effect only 809 * the next time the popup is shown or through a manual call to one of 810 * the {@link #update()} methods.</p> 811 * 812 * @param touchable true if the popup should receive touch events, false otherwise 813 * 814 * @see #isTouchable() 815 * @see #isShowing() 816 * @see #update() 817 */ 818 public void setTouchable(boolean touchable) { 819 mTouchable = touchable; 820 } 821 822 /** 823 * <p>Indicates whether the popup window will be informed of touch events 824 * outside of its window.</p> 825 * 826 * @return true if the popup is outside touchable, false otherwise 827 * 828 * @see #setOutsideTouchable(boolean) 829 */ 830 public boolean isOutsideTouchable() { 831 return mOutsideTouchable; 832 } 833 834 /** 835 * <p>Controls whether the pop-up will be informed of touch events outside 836 * of its window. This only makes sense for pop-ups that are touchable 837 * but not focusable, which means touches outside of the window will 838 * be delivered to the window behind. The default is false.</p> 839 * 840 * <p>If the popup is showing, calling this method will take effect only 841 * the next time the popup is shown or through a manual call to one of 842 * the {@link #update()} methods.</p> 843 * 844 * @param touchable true if the popup should receive outside 845 * touch events, false otherwise 846 * 847 * @see #isOutsideTouchable() 848 * @see #isShowing() 849 * @see #update() 850 */ 851 public void setOutsideTouchable(boolean touchable) { 852 mOutsideTouchable = touchable; 853 } 854 855 /** 856 * <p>Indicates whether clipping of the popup window is enabled.</p> 857 * 858 * @return true if the clipping is enabled, false otherwise 859 * 860 * @see #setClippingEnabled(boolean) 861 */ 862 public boolean isClippingEnabled() { 863 return mClippingEnabled; 864 } 865 866 /** 867 * <p>Allows the popup window to extend beyond the bounds of the screen. By default the 868 * window is clipped to the screen boundaries. Setting this to false will allow windows to be 869 * accurately positioned.</p> 870 * 871 * <p>If the popup is showing, calling this method will take effect only 872 * the next time the popup is shown or through a manual call to one of 873 * the {@link #update()} methods.</p> 874 * 875 * @param enabled false if the window should be allowed to extend outside of the screen 876 * @see #isShowing() 877 * @see #isClippingEnabled() 878 * @see #update() 879 */ 880 public void setClippingEnabled(boolean enabled) { 881 mClippingEnabled = enabled; 882 } 883 884 /** 885 * <p>Indicates whether this popup will be clipped to the screen and not to the 886 * containing window<p/> 887 * 888 * @return true if popup will be clipped to the screen instead of the window, false otherwise 889 * @deprecated Use {@link #isClippedToScreen()} instead 890 * @removed 891 */ 892 @Deprecated 893 public boolean isClipToScreenEnabled() { 894 return mClipToScreen; 895 } 896 897 /** 898 * <p>Clip this popup window to the screen, but not to the containing window.</p> 899 * 900 * <p>If the popup is showing, calling this method will take effect only 901 * the next time the popup is shown or through a manual call to one of 902 * the {@link #update()} methods.</p> 903 * 904 * @deprecated Use {@link #setIsClippedToScreen(boolean)} instead 905 * @removed 906 */ 907 @Deprecated 908 public void setClipToScreenEnabled(boolean enabled) { 909 mClipToScreen = enabled; 910 } 911 912 /** 913 * <p>Indicates whether this popup will be clipped to the screen and not to the 914 * containing window<p/> 915 * 916 * @return true if popup will be clipped to the screen instead of the window, false otherwise 917 * 918 * @see #setIsClippedToScreen(boolean) 919 */ 920 public boolean isClippedToScreen() { 921 return mClipToScreen; 922 } 923 924 /** 925 * <p>Clip this popup window to the screen, but not to the containing window.</p> 926 * 927 * <p>If the popup is showing, calling this method will take effect only 928 * the next time the popup is shown or through a manual call to one of 929 * the {@link #update()} methods.</p> 930 * 931 * @param enabled true to clip to the screen. 932 * 933 * @see #isClippedToScreen() 934 */ 935 public void setIsClippedToScreen(boolean enabled) { 936 mClipToScreen = enabled; 937 } 938 939 /** 940 * Allow PopupWindow to scroll the anchor's parent to provide more room 941 * for the popup. Enabled by default. 942 * 943 * @param enabled True to scroll the anchor's parent when more room is desired by the popup. 944 */ 945 @UnsupportedAppUsage 946 void setAllowScrollingAnchorParent(boolean enabled) { 947 mAllowScrollingAnchorParent = enabled; 948 } 949 950 /** @hide */ 951 protected final boolean getAllowScrollingAnchorParent() { 952 return mAllowScrollingAnchorParent; 953 } 954 955 /** 956 * <p>Indicates whether the popup window supports splitting touches.</p> 957 * 958 * @return true if the touch splitting is enabled, false otherwise 959 * 960 * @see #setSplitTouchEnabled(boolean) 961 */ 962 public boolean isSplitTouchEnabled() { 963 if (mSplitTouchEnabled < 0 && mContext != null) { 964 return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB; 965 } 966 return mSplitTouchEnabled == 1; 967 } 968 969 /** 970 * <p>Allows the popup window to split touches across other windows that also 971 * support split touch. When this flag is false, the first pointer 972 * that goes down determines the window to which all subsequent touches 973 * go until all pointers go up. When this flag is true, each pointer 974 * (not necessarily the first) that goes down determines the window 975 * to which all subsequent touches of that pointer will go until that 976 * pointer goes up thereby enabling touches with multiple pointers 977 * to be split across multiple windows.</p> 978 * 979 * @param enabled true if the split touches should be enabled, false otherwise 980 * @see #isSplitTouchEnabled() 981 */ 982 public void setSplitTouchEnabled(boolean enabled) { 983 mSplitTouchEnabled = enabled ? 1 : 0; 984 } 985 986 /** 987 * <p>Indicates whether the popup window will be forced into using absolute screen coordinates 988 * for positioning.</p> 989 * 990 * @return true if the window will always be positioned in screen coordinates. 991 * 992 * @deprecated Use {@link #isLaidOutInScreen()} instead 993 * @removed 994 */ 995 @Deprecated 996 public boolean isLayoutInScreenEnabled() { 997 return mLayoutInScreen; 998 } 999 1000 /** 1001 * <p>Allows the popup window to force the flag 1002 * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior. 1003 * This will cause the popup to be positioned in absolute screen coordinates.</p> 1004 * 1005 * @param enabled true if the popup should always be positioned in screen coordinates 1006 * @deprecated Use {@link #setIsLaidOutInScreen(boolean)} instead 1007 * @removed 1008 */ 1009 @Deprecated 1010 public void setLayoutInScreenEnabled(boolean enabled) { 1011 mLayoutInScreen = enabled; 1012 } 1013 1014 /** 1015 * <p>Indicates whether the popup window will be forced into using absolute screen coordinates 1016 * for positioning.</p> 1017 * 1018 * @return true if the window will always be positioned in screen coordinates. 1019 * 1020 * @see #setIsLaidOutInScreen(boolean) 1021 */ 1022 public boolean isLaidOutInScreen() { 1023 return mLayoutInScreen; 1024 } 1025 1026 /** 1027 * <p>Allows the popup window to force the flag 1028 * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior. 1029 * This will cause the popup to be positioned in absolute screen coordinates.</p> 1030 * 1031 * @param enabled true if the popup should always be positioned in screen coordinates 1032 * 1033 * @see #isLaidOutInScreen() 1034 */ 1035 public void setIsLaidOutInScreen(boolean enabled) { 1036 mLayoutInScreen = enabled; 1037 } 1038 1039 /** 1040 * <p>Indicates whether the popup window will be attached in the decor frame of its parent 1041 * window. 1042 * 1043 * @return true if the window will be attached to the decor frame of its parent window. 1044 * 1045 * @see #setAttachedInDecor(boolean) 1046 * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR 1047 */ 1048 public boolean isAttachedInDecor() { 1049 return mAttachedInDecor; 1050 } 1051 1052 /** 1053 * <p>This will attach the popup window to the decor frame of the parent window to avoid 1054 * overlaping with screen decorations like the navigation bar. Overrides the default behavior of 1055 * the flag {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR}. 1056 * 1057 * <p>By default the flag is set on SDK version {@link Build.VERSION_CODES#LOLLIPOP_MR1} or 1058 * greater and cleared on lesser SDK versions. 1059 * 1060 * @param enabled true if the popup should be attached to the decor frame of its parent window. 1061 * 1062 * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR 1063 */ 1064 public void setAttachedInDecor(boolean enabled) { 1065 mAttachedInDecor = enabled; 1066 mAttachedInDecorSet = true; 1067 } 1068 1069 /** 1070 * Allows the popup window to force the flag 1071 * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior. 1072 * This will cause the popup to inset its content to account for system windows overlaying 1073 * the screen, such as the status bar. 1074 * 1075 * <p>This will often be combined with {@link #setIsLaidOutInScreen(boolean)}. 1076 * 1077 * @param enabled true if the popup's views should inset content to account for system windows, 1078 * the way that decor views behave for full-screen windows. 1079 * @hide 1080 */ 1081 @UnsupportedAppUsage 1082 public void setLayoutInsetDecor(boolean enabled) { 1083 mLayoutInsetDecor = enabled; 1084 } 1085 1086 /** @hide */ 1087 protected final boolean isLayoutInsetDecor() { 1088 return mLayoutInsetDecor; 1089 } 1090 1091 /** 1092 * Set the layout type for this window. 1093 * <p> 1094 * See {@link WindowManager.LayoutParams#type} for possible values. 1095 * 1096 * @param layoutType Layout type for this window. 1097 * 1098 * @see WindowManager.LayoutParams#type 1099 */ 1100 public void setWindowLayoutType(int layoutType) { 1101 mWindowLayoutType = layoutType; 1102 } 1103 1104 /** 1105 * Returns the layout type for this window. 1106 * 1107 * @see #setWindowLayoutType(int) 1108 */ 1109 public int getWindowLayoutType() { 1110 return mWindowLayoutType; 1111 } 1112 1113 /** 1114 * <p>Indicates whether outside touches will be sent to this window 1115 * or other windows behind it<p/> 1116 * 1117 * @return true if touches will be sent to this window, false otherwise 1118 * 1119 * @see #setTouchModal(boolean) 1120 */ 1121 public boolean isTouchModal() { 1122 return !mNotTouchModal; 1123 } 1124 1125 /** 1126 * <p>Set whether this window is touch modal or if outside touches will be sent to 1127 * other windows behind it.<p/> 1128 * 1129 * <p>If the popup is showing, calling this method will take effect only 1130 * the next time the popup is shown or through a manual call to one of 1131 * the {@link #update()} methods.</p> 1132 * 1133 * @param touchModal true to sent all outside touches to this window, 1134 * false to other windows behind it 1135 * 1136 * @see #isTouchModal() 1137 */ 1138 public void setTouchModal(boolean touchModal) { 1139 mNotTouchModal = !touchModal; 1140 } 1141 1142 /** 1143 * <p>Change the width and height measure specs that are given to the 1144 * window manager by the popup. By default these are 0, meaning that 1145 * the current width or height is requested as an explicit size from 1146 * the window manager. You can supply 1147 * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or 1148 * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure 1149 * spec supplied instead, replacing the absolute width and height that 1150 * has been set in the popup.</p> 1151 * 1152 * <p>If the popup is showing, calling this method will take effect only 1153 * the next time the popup is shown.</p> 1154 * 1155 * @param widthSpec an explicit width measure spec mode, either 1156 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 1157 * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute 1158 * width. 1159 * @param heightSpec an explicit height measure spec mode, either 1160 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 1161 * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute 1162 * height. 1163 * 1164 * @deprecated Use {@link #setWidth(int)} and {@link #setHeight(int)}. 1165 */ 1166 @Deprecated 1167 public void setWindowLayoutMode(int widthSpec, int heightSpec) { 1168 mWidthMode = widthSpec; 1169 mHeightMode = heightSpec; 1170 } 1171 1172 /** 1173 * Returns the popup's requested height. May be a layout constant such as 1174 * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}. 1175 * <p> 1176 * The actual size of the popup may depend on other factors such as 1177 * clipping and window layout. 1178 * 1179 * @return the popup height in pixels or a layout constant 1180 * @see #setHeight(int) 1181 */ 1182 public int getHeight() { 1183 return mHeight; 1184 } 1185 1186 /** 1187 * Sets the popup's requested height. May be a layout constant such as 1188 * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}. 1189 * <p> 1190 * The actual size of the popup may depend on other factors such as 1191 * clipping and window layout. 1192 * <p> 1193 * If the popup is showing, calling this method will take effect the next 1194 * time the popup is shown. 1195 * 1196 * @param height the popup height in pixels or a layout constant 1197 * @see #getHeight() 1198 * @see #isShowing() 1199 */ 1200 public void setHeight(int height) { 1201 mHeight = height; 1202 } 1203 1204 /** 1205 * Returns the popup's requested width. May be a layout constant such as 1206 * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}. 1207 * <p> 1208 * The actual size of the popup may depend on other factors such as 1209 * clipping and window layout. 1210 * 1211 * @return the popup width in pixels or a layout constant 1212 * @see #setWidth(int) 1213 */ 1214 public int getWidth() { 1215 return mWidth; 1216 } 1217 1218 /** 1219 * Sets the popup's requested width. May be a layout constant such as 1220 * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}. 1221 * <p> 1222 * The actual size of the popup may depend on other factors such as 1223 * clipping and window layout. 1224 * <p> 1225 * If the popup is showing, calling this method will take effect the next 1226 * time the popup is shown. 1227 * 1228 * @param width the popup width in pixels or a layout constant 1229 * @see #getWidth() 1230 * @see #isShowing() 1231 */ 1232 public void setWidth(int width) { 1233 mWidth = width; 1234 } 1235 1236 /** 1237 * Sets whether the popup window should overlap its anchor view when 1238 * displayed as a drop-down. 1239 * <p> 1240 * If the popup is showing, calling this method will take effect only 1241 * the next time the popup is shown. 1242 * 1243 * @param overlapAnchor Whether the popup should overlap its anchor. 1244 * 1245 * @see #getOverlapAnchor() 1246 * @see #isShowing() 1247 */ 1248 public void setOverlapAnchor(boolean overlapAnchor) { 1249 mOverlapAnchor = overlapAnchor; 1250 } 1251 1252 /** 1253 * Returns whether the popup window should overlap its anchor view when 1254 * displayed as a drop-down. 1255 * 1256 * @return Whether the popup should overlap its anchor. 1257 * 1258 * @see #setOverlapAnchor(boolean) 1259 */ 1260 public boolean getOverlapAnchor() { 1261 return mOverlapAnchor; 1262 } 1263 1264 /** 1265 * <p>Indicate whether this popup window is showing on screen.</p> 1266 * 1267 * @return true if the popup is showing, false otherwise 1268 */ 1269 public boolean isShowing() { 1270 return mIsShowing; 1271 } 1272 1273 /** @hide */ 1274 protected final void setShowing(boolean isShowing) { 1275 mIsShowing = isShowing; 1276 } 1277 1278 /** @hide */ 1279 protected final void setDropDown(boolean isDropDown) { 1280 mIsDropdown = isDropDown; 1281 } 1282 1283 /** @hide */ 1284 protected final void setTransitioningToDismiss(boolean transitioningToDismiss) { 1285 mIsTransitioningToDismiss = transitioningToDismiss; 1286 } 1287 1288 /** @hide */ 1289 protected final boolean isTransitioningToDismiss() { 1290 return mIsTransitioningToDismiss; 1291 } 1292 1293 /** 1294 * <p> 1295 * Display the content view in a popup window at the specified location. If the popup window 1296 * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams} 1297 * for more information on how gravity and the x and y parameters are related. Specifying 1298 * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying 1299 * <code>Gravity.LEFT | Gravity.TOP</code>. 1300 * </p> 1301 * 1302 * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from 1303 * @param gravity the gravity which controls the placement of the popup window 1304 * @param x the popup's x location offset 1305 * @param y the popup's y location offset 1306 */ 1307 public void showAtLocation(View parent, int gravity, int x, int y) { 1308 mParentRootView = new WeakReference<>(parent.getRootView()); 1309 showAtLocation(parent.getWindowToken(), gravity, x, y); 1310 } 1311 1312 /** 1313 * Display the content view in a popup window at the specified location. 1314 * 1315 * @param token Window token to use for creating the new window 1316 * @param gravity the gravity which controls the placement of the popup window 1317 * @param x the popup's x location offset 1318 * @param y the popup's y location offset 1319 * 1320 * @hide Internal use only. Applications should use 1321 * {@link #showAtLocation(View, int, int, int)} instead. 1322 */ 1323 @UnsupportedAppUsage 1324 public void showAtLocation(IBinder token, int gravity, int x, int y) { 1325 if (isShowing() || mContentView == null) { 1326 return; 1327 } 1328 1329 TransitionManager.endTransitions(mDecorView); 1330 1331 detachFromAnchor(); 1332 1333 mIsShowing = true; 1334 mIsDropdown = false; 1335 mGravity = gravity; 1336 1337 final WindowManager.LayoutParams p = createPopupLayoutParams(token); 1338 preparePopup(p); 1339 1340 p.x = x; 1341 p.y = y; 1342 1343 invokePopup(p); 1344 } 1345 1346 /** 1347 * Display the content view in a popup window anchored to the bottom-left 1348 * corner of the anchor view. If there is not enough room on screen to show 1349 * the popup in its entirety, this method tries to find a parent scroll 1350 * view to scroll. If no parent scroll view can be scrolled, the 1351 * bottom-left corner of the popup is pinned at the top left corner of the 1352 * anchor view. 1353 * 1354 * @param anchor the view on which to pin the popup window 1355 * 1356 * @see #dismiss() 1357 */ 1358 public void showAsDropDown(View anchor) { 1359 showAsDropDown(anchor, 0, 0); 1360 } 1361 1362 /** 1363 * Display the content view in a popup window anchored to the bottom-left 1364 * corner of the anchor view offset by the specified x and y coordinates. 1365 * If there is not enough room on screen to show the popup in its entirety, 1366 * this method tries to find a parent scroll view to scroll. If no parent 1367 * scroll view can be scrolled, the bottom-left corner of the popup is 1368 * pinned at the top left corner of the anchor view. 1369 * <p> 1370 * If the view later scrolls to move <code>anchor</code> to a different 1371 * location, the popup will be moved correspondingly. 1372 * 1373 * @param anchor the view on which to pin the popup window 1374 * @param xoff A horizontal offset from the anchor in pixels 1375 * @param yoff A vertical offset from the anchor in pixels 1376 * 1377 * @see #dismiss() 1378 */ 1379 public void showAsDropDown(View anchor, int xoff, int yoff) { 1380 showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY); 1381 } 1382 1383 /** 1384 * Displays the content view in a popup window anchored to the corner of 1385 * another view. The window is positioned according to the specified 1386 * gravity and offset by the specified x and y coordinates. 1387 * <p> 1388 * If there is not enough room on screen to show the popup in its entirety, 1389 * this method tries to find a parent scroll view to scroll. If no parent 1390 * view can be scrolled, the specified vertical gravity will be ignored and 1391 * the popup will anchor itself such that it is visible. 1392 * <p> 1393 * If the view later scrolls to move <code>anchor</code> to a different 1394 * location, the popup will be moved correspondingly. 1395 * 1396 * @param anchor the view on which to pin the popup window 1397 * @param xoff A horizontal offset from the anchor in pixels 1398 * @param yoff A vertical offset from the anchor in pixels 1399 * @param gravity Alignment of the popup relative to the anchor 1400 * 1401 * @see #dismiss() 1402 */ 1403 public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { 1404 if (isShowing() || !hasContentView()) { 1405 return; 1406 } 1407 1408 TransitionManager.endTransitions(mDecorView); 1409 1410 attachToAnchor(anchor, xoff, yoff, gravity); 1411 1412 mIsShowing = true; 1413 mIsDropdown = true; 1414 1415 final WindowManager.LayoutParams p = 1416 createPopupLayoutParams(anchor.getApplicationWindowToken()); 1417 preparePopup(p); 1418 1419 final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, 1420 p.width, p.height, gravity, mAllowScrollingAnchorParent); 1421 updateAboveAnchor(aboveAnchor); 1422 p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1; 1423 1424 invokePopup(p); 1425 } 1426 1427 /** @hide */ 1428 @UnsupportedAppUsage 1429 protected final void updateAboveAnchor(boolean aboveAnchor) { 1430 if (aboveAnchor != mAboveAnchor) { 1431 mAboveAnchor = aboveAnchor; 1432 1433 if (mBackground != null && mBackgroundView != null) { 1434 // If the background drawable provided was a StateListDrawable 1435 // with above-anchor and below-anchor states, use those. 1436 // Otherwise, rely on refreshDrawableState to do the job. 1437 if (mAboveAnchorBackgroundDrawable != null) { 1438 if (mAboveAnchor) { 1439 mBackgroundView.setBackground(mAboveAnchorBackgroundDrawable); 1440 } else { 1441 mBackgroundView.setBackground(mBelowAnchorBackgroundDrawable); 1442 } 1443 } else { 1444 mBackgroundView.refreshDrawableState(); 1445 } 1446 } 1447 } 1448 } 1449 1450 /** 1451 * Indicates whether the popup is showing above (the y coordinate of the popup's bottom 1452 * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate 1453 * of the popup is greater than y coordinate of the anchor's bottom). 1454 * 1455 * The value returned 1456 * by this method is meaningful only after {@link #showAsDropDown(android.view.View)} 1457 * or {@link #showAsDropDown(android.view.View, int, int)} was invoked. 1458 * 1459 * @return True if this popup is showing above the anchor view, false otherwise. 1460 */ 1461 public boolean isAboveAnchor() { 1462 return mAboveAnchor; 1463 } 1464 1465 /** 1466 * Prepare the popup by embedding it into a new ViewGroup if the background 1467 * drawable is not null. If embedding is required, the layout parameters' 1468 * height is modified to take into account the background's padding. 1469 * 1470 * @param p the layout parameters of the popup's content view 1471 */ 1472 @UnsupportedAppUsage 1473 private void preparePopup(WindowManager.LayoutParams p) { 1474 if (mContentView == null || mContext == null || mWindowManager == null) { 1475 throw new IllegalStateException("You must specify a valid content view by " 1476 + "calling setContentView() before attempting to show the popup."); 1477 } 1478 1479 if (p.accessibilityTitle == null) { 1480 p.accessibilityTitle = mContext.getString(R.string.popup_window_default_title); 1481 } 1482 1483 // The old decor view may be transitioning out. Make sure it finishes 1484 // and cleans up before we try to create another one. 1485 if (mDecorView != null) { 1486 mDecorView.cancelTransitions(); 1487 } 1488 1489 // When a background is available, we embed the content view within 1490 // another view that owns the background drawable. 1491 if (mBackground != null) { 1492 mBackgroundView = createBackgroundView(mContentView); 1493 mBackgroundView.setBackground(mBackground); 1494 } else { 1495 mBackgroundView = mContentView; 1496 } 1497 1498 mDecorView = createDecorView(mBackgroundView); 1499 mDecorView.setIsRootNamespace(true); 1500 1501 // The background owner should be elevated so that it casts a shadow. 1502 mBackgroundView.setElevation(mElevation); 1503 1504 // We may wrap that in another view, so we'll need to manually specify 1505 // the surface insets. 1506 p.setSurfaceInsets(mBackgroundView, true /*manual*/, true /*preservePrevious*/); 1507 1508 mPopupViewInitialLayoutDirectionInherited = 1509 (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); 1510 } 1511 1512 /** 1513 * Wraps a content view in a PopupViewContainer. 1514 * 1515 * @param contentView the content view to wrap 1516 * @return a PopupViewContainer that wraps the content view 1517 */ 1518 private PopupBackgroundView createBackgroundView(View contentView) { 1519 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 1520 final int height; 1521 if (layoutParams != null && layoutParams.height == WRAP_CONTENT) { 1522 height = WRAP_CONTENT; 1523 } else { 1524 height = MATCH_PARENT; 1525 } 1526 1527 final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext); 1528 final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams( 1529 MATCH_PARENT, height); 1530 backgroundView.addView(contentView, listParams); 1531 1532 return backgroundView; 1533 } 1534 1535 /** 1536 * Wraps a content view in a FrameLayout. 1537 * 1538 * @param contentView the content view to wrap 1539 * @return a FrameLayout that wraps the content view 1540 */ 1541 private PopupDecorView createDecorView(View contentView) { 1542 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 1543 final int height; 1544 if (layoutParams != null && layoutParams.height == WRAP_CONTENT) { 1545 height = WRAP_CONTENT; 1546 } else { 1547 height = MATCH_PARENT; 1548 } 1549 1550 final PopupDecorView decorView = new PopupDecorView(mContext); 1551 decorView.addView(contentView, MATCH_PARENT, height); 1552 decorView.setClipChildren(false); 1553 decorView.setClipToPadding(false); 1554 1555 return decorView; 1556 } 1557 1558 /** 1559 * <p>Invoke the popup window by adding the content view to the window 1560 * manager.</p> 1561 * 1562 * <p>The content view must be non-null when this method is invoked.</p> 1563 * 1564 * @param p the layout parameters of the popup's content view 1565 */ 1566 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 1567 private void invokePopup(WindowManager.LayoutParams p) { 1568 if (mContext != null) { 1569 p.packageName = mContext.getPackageName(); 1570 } 1571 1572 final PopupDecorView decorView = mDecorView; 1573 decorView.setFitsSystemWindows(mLayoutInsetDecor); 1574 1575 setLayoutDirectionFromAnchor(); 1576 1577 mWindowManager.addView(decorView, p); 1578 1579 if (mEnterTransition != null) { 1580 decorView.requestEnterTransition(mEnterTransition); 1581 } 1582 } 1583 1584 private void setLayoutDirectionFromAnchor() { 1585 if (mAnchor != null) { 1586 View anchor = mAnchor.get(); 1587 if (anchor != null && mPopupViewInitialLayoutDirectionInherited) { 1588 mDecorView.setLayoutDirection(anchor.getLayoutDirection()); 1589 } 1590 } 1591 } 1592 1593 private int computeGravity() { 1594 int gravity = mGravity == Gravity.NO_GRAVITY ? Gravity.START | Gravity.TOP : mGravity; 1595 if (mIsDropdown && (mClipToScreen || mClippingEnabled)) { 1596 gravity |= Gravity.DISPLAY_CLIP_VERTICAL; 1597 } 1598 return gravity; 1599 } 1600 1601 /** 1602 * <p>Generate the layout parameters for the popup window.</p> 1603 * 1604 * @param token the window token used to bind the popup's window 1605 * 1606 * @return the layout parameters to pass to the window manager 1607 * 1608 * @hide 1609 */ 1610 @UnsupportedAppUsage 1611 protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { 1612 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 1613 1614 // These gravity settings put the view at the top left corner of the 1615 // screen. The view is then positioned to the appropriate location by 1616 // setting the x and y offsets to match the anchor's bottom-left 1617 // corner. 1618 p.gravity = computeGravity(); 1619 p.flags = computeFlags(p.flags); 1620 p.type = mWindowLayoutType; 1621 p.token = token; 1622 p.softInputMode = mSoftInputMode; 1623 p.windowAnimations = computeAnimationResource(); 1624 1625 if (mBackground != null) { 1626 p.format = mBackground.getOpacity(); 1627 } else { 1628 p.format = PixelFormat.TRANSLUCENT; 1629 } 1630 1631 if (mHeightMode < 0) { 1632 p.height = mLastHeight = mHeightMode; 1633 } else { 1634 p.height = mLastHeight = mHeight; 1635 } 1636 1637 if (mWidthMode < 0) { 1638 p.width = mLastWidth = mWidthMode; 1639 } else { 1640 p.width = mLastWidth = mWidth; 1641 } 1642 1643 p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH 1644 | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; 1645 1646 // Used for debugging. 1647 p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); 1648 1649 return p; 1650 } 1651 1652 private int computeFlags(int curFlags) { 1653 curFlags &= ~( 1654 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | 1655 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 1656 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 1657 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 1658 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 1659 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | 1660 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH); 1661 if(mIgnoreCheekPress) { 1662 curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 1663 } 1664 if (!mFocusable) { 1665 curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 1666 if (mInputMethodMode == INPUT_METHOD_NEEDED) { 1667 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1668 } 1669 } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { 1670 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1671 } 1672 if (!mTouchable) { 1673 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 1674 } 1675 if (mOutsideTouchable) { 1676 curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 1677 } 1678 if (!mClippingEnabled || mClipToScreen) { 1679 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 1680 } 1681 if (isSplitTouchEnabled()) { 1682 curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 1683 } 1684 if (mLayoutInScreen) { 1685 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 1686 } 1687 if (mLayoutInsetDecor) { 1688 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 1689 } 1690 if (mNotTouchModal) { 1691 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 1692 } 1693 if (mAttachedInDecor) { 1694 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR; 1695 } 1696 return curFlags; 1697 } 1698 1699 @UnsupportedAppUsage 1700 private int computeAnimationResource() { 1701 if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) { 1702 if (mIsDropdown) { 1703 return mAboveAnchor 1704 ? com.android.internal.R.style.Animation_DropDownUp 1705 : com.android.internal.R.style.Animation_DropDownDown; 1706 } 1707 return 0; 1708 } 1709 return mAnimationStyle; 1710 } 1711 1712 /** 1713 * Positions the popup window on screen. When the popup window is too tall 1714 * to fit under the anchor, a parent scroll view is seeked and scrolled up 1715 * to reclaim space. If scrolling is not possible or not enough, the popup 1716 * window gets moved on top of the anchor. 1717 * <p> 1718 * The results of positioning are placed in {@code outParams}. 1719 * 1720 * @param anchor the view on which the popup window must be anchored 1721 * @param outParams the layout parameters used to display the drop down 1722 * @param xOffset absolute horizontal offset from the left of the anchor 1723 * @param yOffset absolute vertical offset from the top of the anchor 1724 * @param gravity horizontal gravity specifying popup alignment 1725 * @param allowScroll whether the anchor view's parent may be scrolled 1726 * when the popup window doesn't fit on screen 1727 * @return true if the popup is translated upwards to fit on screen 1728 * 1729 * @hide 1730 */ 1731 protected boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams, 1732 int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) { 1733 final int anchorHeight = anchor.getHeight(); 1734 final int anchorWidth = anchor.getWidth(); 1735 if (mOverlapAnchor) { 1736 yOffset -= anchorHeight; 1737 } 1738 1739 // Initially, align to the bottom-left corner of the anchor plus offsets. 1740 final int[] appScreenLocation = mTmpAppLocation; 1741 final View appRootView = getAppRootView(anchor); 1742 appRootView.getLocationOnScreen(appScreenLocation); 1743 1744 final int[] screenLocation = mTmpScreenLocation; 1745 anchor.getLocationOnScreen(screenLocation); 1746 1747 final int[] drawingLocation = mTmpDrawingLocation; 1748 drawingLocation[0] = screenLocation[0] - appScreenLocation[0]; 1749 drawingLocation[1] = screenLocation[1] - appScreenLocation[1]; 1750 outParams.x = drawingLocation[0] + xOffset; 1751 outParams.y = drawingLocation[1] + anchorHeight + yOffset; 1752 1753 final Rect displayFrame = new Rect(); 1754 appRootView.getWindowVisibleDisplayFrame(displayFrame); 1755 if (width == MATCH_PARENT) { 1756 width = displayFrame.right - displayFrame.left; 1757 } 1758 if (height == MATCH_PARENT) { 1759 height = displayFrame.bottom - displayFrame.top; 1760 } 1761 1762 // Let the window manager know to align the top to y. 1763 outParams.gravity = computeGravity(); 1764 outParams.width = width; 1765 outParams.height = height; 1766 1767 // If we need to adjust for gravity RIGHT, align to the bottom-right 1768 // corner of the anchor (still accounting for offsets). 1769 final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection()) 1770 & Gravity.HORIZONTAL_GRAVITY_MASK; 1771 if (hgrav == Gravity.RIGHT) { 1772 outParams.x -= width - anchorWidth; 1773 } 1774 1775 // First, attempt to fit the popup vertically without resizing. 1776 final boolean fitsVertical = tryFitVertical(outParams, yOffset, height, 1777 anchorHeight, drawingLocation[1], screenLocation[1], displayFrame.top, 1778 displayFrame.bottom, false); 1779 1780 // Next, attempt to fit the popup horizontally without resizing. 1781 final boolean fitsHorizontal = tryFitHorizontal(outParams, xOffset, width, 1782 anchorWidth, drawingLocation[0], screenLocation[0], displayFrame.left, 1783 displayFrame.right, false); 1784 1785 // If the popup still doesn't fit, attempt to scroll the parent. 1786 if (!fitsVertical || !fitsHorizontal) { 1787 final int scrollX = anchor.getScrollX(); 1788 final int scrollY = anchor.getScrollY(); 1789 final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset, 1790 scrollY + height + anchorHeight + yOffset); 1791 if (allowScroll && anchor.requestRectangleOnScreen(r, true)) { 1792 // Reset for the new anchor position. 1793 anchor.getLocationOnScreen(screenLocation); 1794 drawingLocation[0] = screenLocation[0] - appScreenLocation[0]; 1795 drawingLocation[1] = screenLocation[1] - appScreenLocation[1]; 1796 outParams.x = drawingLocation[0] + xOffset; 1797 outParams.y = drawingLocation[1] + anchorHeight + yOffset; 1798 1799 // Preserve the gravity adjustment. 1800 if (hgrav == Gravity.RIGHT) { 1801 outParams.x -= width - anchorWidth; 1802 } 1803 } 1804 1805 // Try to fit the popup again and allowing resizing. 1806 tryFitVertical(outParams, yOffset, height, anchorHeight, drawingLocation[1], 1807 screenLocation[1], displayFrame.top, displayFrame.bottom, mClipToScreen); 1808 tryFitHorizontal(outParams, xOffset, width, anchorWidth, drawingLocation[0], 1809 screenLocation[0], displayFrame.left, displayFrame.right, mClipToScreen); 1810 } 1811 1812 // Return whether the popup's top edge is above the anchor's top edge. 1813 return outParams.y < drawingLocation[1]; 1814 } 1815 1816 private boolean tryFitVertical(@NonNull LayoutParams outParams, int yOffset, int height, 1817 int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop, 1818 int displayFrameBottom, boolean allowResize) { 1819 final int winOffsetY = screenLocationY - drawingLocationY; 1820 final int anchorTopInScreen = outParams.y + winOffsetY; 1821 final int spaceBelow = displayFrameBottom - anchorTopInScreen; 1822 if (anchorTopInScreen >= 0 && height <= spaceBelow) { 1823 return true; 1824 } 1825 1826 final int spaceAbove = anchorTopInScreen - anchorHeight - displayFrameTop; 1827 if (height <= spaceAbove) { 1828 // Move everything up. 1829 if (mOverlapAnchor) { 1830 yOffset += anchorHeight; 1831 } 1832 outParams.y = drawingLocationY - height + yOffset; 1833 1834 return true; 1835 } 1836 1837 if (positionInDisplayVertical(outParams, height, drawingLocationY, screenLocationY, 1838 displayFrameTop, displayFrameBottom, allowResize)) { 1839 return true; 1840 } 1841 1842 return false; 1843 } 1844 1845 private boolean positionInDisplayVertical(@NonNull LayoutParams outParams, int height, 1846 int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, 1847 boolean canResize) { 1848 boolean fitsInDisplay = true; 1849 1850 final int winOffsetY = screenLocationY - drawingLocationY; 1851 outParams.y += winOffsetY; 1852 outParams.height = height; 1853 1854 final int bottom = outParams.y + height; 1855 if (bottom > displayFrameBottom) { 1856 // The popup is too far down, move it back in. 1857 outParams.y -= bottom - displayFrameBottom; 1858 } 1859 1860 if (outParams.y < displayFrameTop) { 1861 // The popup is too far up, move it back in and clip if 1862 // it's still too large. 1863 outParams.y = displayFrameTop; 1864 1865 final int displayFrameHeight = displayFrameBottom - displayFrameTop; 1866 if (canResize && height > displayFrameHeight) { 1867 outParams.height = displayFrameHeight; 1868 } else { 1869 fitsInDisplay = false; 1870 } 1871 } 1872 1873 outParams.y -= winOffsetY; 1874 1875 return fitsInDisplay; 1876 } 1877 1878 private boolean tryFitHorizontal(@NonNull LayoutParams outParams, int xOffset, int width, 1879 int anchorWidth, int drawingLocationX, int screenLocationX, int displayFrameLeft, 1880 int displayFrameRight, boolean allowResize) { 1881 final int winOffsetX = screenLocationX - drawingLocationX; 1882 final int anchorLeftInScreen = outParams.x + winOffsetX; 1883 final int spaceRight = displayFrameRight - anchorLeftInScreen; 1884 if (anchorLeftInScreen >= 0 && width <= spaceRight) { 1885 return true; 1886 } 1887 1888 if (positionInDisplayHorizontal(outParams, width, drawingLocationX, screenLocationX, 1889 displayFrameLeft, displayFrameRight, allowResize)) { 1890 return true; 1891 } 1892 1893 return false; 1894 } 1895 1896 private boolean positionInDisplayHorizontal(@NonNull LayoutParams outParams, int width, 1897 int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, 1898 boolean canResize) { 1899 boolean fitsInDisplay = true; 1900 1901 // Use screen coordinates for comparison against display frame. 1902 final int winOffsetX = screenLocationX - drawingLocationX; 1903 outParams.x += winOffsetX; 1904 1905 final int right = outParams.x + width; 1906 if (right > displayFrameRight) { 1907 // The popup is too far right, move it back in. 1908 outParams.x -= right - displayFrameRight; 1909 } 1910 1911 if (outParams.x < displayFrameLeft) { 1912 // The popup is too far left, move it back in and clip if it's 1913 // still too large. 1914 outParams.x = displayFrameLeft; 1915 1916 final int displayFrameWidth = displayFrameRight - displayFrameLeft; 1917 if (canResize && width > displayFrameWidth) { 1918 outParams.width = displayFrameWidth; 1919 } else { 1920 fitsInDisplay = false; 1921 } 1922 } 1923 1924 outParams.x -= winOffsetX; 1925 1926 return fitsInDisplay; 1927 } 1928 1929 /** 1930 * Returns the maximum height that is available for the popup to be 1931 * completely shown. It is recommended that this height be the maximum for 1932 * the popup's height, otherwise it is possible that the popup will be 1933 * clipped. 1934 * 1935 * @param anchor The view on which the popup window must be anchored. 1936 * @return The maximum available height for the popup to be completely 1937 * shown. 1938 */ 1939 public int getMaxAvailableHeight(@NonNull View anchor) { 1940 return getMaxAvailableHeight(anchor, 0); 1941 } 1942 1943 /** 1944 * Returns the maximum height that is available for the popup to be 1945 * completely shown. It is recommended that this height be the maximum for 1946 * the popup's height, otherwise it is possible that the popup will be 1947 * clipped. 1948 * 1949 * @param anchor The view on which the popup window must be anchored. 1950 * @param yOffset y offset from the view's bottom edge 1951 * @return The maximum available height for the popup to be completely 1952 * shown. 1953 */ 1954 public int getMaxAvailableHeight(@NonNull View anchor, int yOffset) { 1955 return getMaxAvailableHeight(anchor, yOffset, false); 1956 } 1957 1958 /** 1959 * Returns the maximum height that is available for the popup to be 1960 * completely shown, optionally ignoring any bottom decorations such as 1961 * the input method. It is recommended that this height be the maximum for 1962 * the popup's height, otherwise it is possible that the popup will be 1963 * clipped. 1964 * 1965 * @param anchor The view on which the popup window must be anchored. 1966 * @param yOffset y offset from the view's bottom edge 1967 * @param ignoreBottomDecorations if true, the height returned will be 1968 * all the way to the bottom of the display, ignoring any 1969 * bottom decorations 1970 * @return The maximum available height for the popup to be completely 1971 * shown. 1972 */ 1973 public int getMaxAvailableHeight( 1974 @NonNull View anchor, int yOffset, boolean ignoreBottomDecorations) { 1975 Rect displayFrame = null; 1976 final Rect visibleDisplayFrame = new Rect(); 1977 1978 final View appView = getAppRootView(anchor); 1979 appView.getWindowVisibleDisplayFrame(visibleDisplayFrame); 1980 if (ignoreBottomDecorations) { 1981 // In the ignore bottom decorations case we want to 1982 // still respect all other decorations so we use the inset visible 1983 // frame on the top right and left and take the bottom 1984 // value from the full frame. 1985 displayFrame = new Rect(); 1986 anchor.getWindowDisplayFrame(displayFrame); 1987 displayFrame.top = visibleDisplayFrame.top; 1988 displayFrame.right = visibleDisplayFrame.right; 1989 displayFrame.left = visibleDisplayFrame.left; 1990 } else { 1991 displayFrame = visibleDisplayFrame; 1992 } 1993 1994 final int[] anchorPos = mTmpDrawingLocation; 1995 anchor.getLocationOnScreen(anchorPos); 1996 1997 final int bottomEdge = displayFrame.bottom; 1998 1999 final int distanceToBottom; 2000 if (mOverlapAnchor) { 2001 distanceToBottom = bottomEdge - anchorPos[1] - yOffset; 2002 } else { 2003 distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; 2004 } 2005 final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; 2006 2007 // anchorPos[1] is distance from anchor to top of screen 2008 int returnedHeight = Math.max(distanceToBottom, distanceToTop); 2009 if (mBackground != null) { 2010 mBackground.getPadding(mTempRect); 2011 returnedHeight -= mTempRect.top + mTempRect.bottom; 2012 } 2013 2014 return returnedHeight; 2015 } 2016 2017 /** 2018 * Disposes of the popup window. This method can be invoked only after 2019 * {@link #showAsDropDown(android.view.View)} has been executed. Failing 2020 * that, calling this method will have no effect. 2021 * 2022 * @see #showAsDropDown(android.view.View) 2023 */ 2024 public void dismiss() { 2025 if (!isShowing() || isTransitioningToDismiss()) { 2026 return; 2027 } 2028 2029 final PopupDecorView decorView = mDecorView; 2030 final View contentView = mContentView; 2031 2032 final ViewGroup contentHolder; 2033 final ViewParent contentParent = contentView.getParent(); 2034 if (contentParent instanceof ViewGroup) { 2035 contentHolder = ((ViewGroup) contentParent); 2036 } else { 2037 contentHolder = null; 2038 } 2039 2040 // Ensure any ongoing or pending transitions are canceled. 2041 decorView.cancelTransitions(); 2042 2043 mIsShowing = false; 2044 mIsTransitioningToDismiss = true; 2045 2046 // This method may be called as part of window detachment, in which 2047 // case the anchor view (and its root) will still return true from 2048 // isAttachedToWindow() during execution of this method; however, we 2049 // can expect the OnAttachStateChangeListener to have been called prior 2050 // to executing this method, so we can rely on that instead. 2051 final Transition exitTransition = mExitTransition; 2052 if (exitTransition != null && decorView.isLaidOut() 2053 && (mIsAnchorRootAttached || mAnchorRoot == null)) { 2054 // The decor view is non-interactive and non-IME-focusable during exit transitions. 2055 final LayoutParams p = (LayoutParams) decorView.getLayoutParams(); 2056 p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE; 2057 p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE; 2058 p.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM; 2059 mWindowManager.updateViewLayout(decorView, p); 2060 2061 final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null; 2062 final Rect epicenter = getTransitionEpicenter(); 2063 2064 // Once we start dismissing the decor view, all state (including 2065 // the anchor root) needs to be moved to the decor view since we 2066 // may open another popup while it's busy exiting. 2067 decorView.startExitTransition(exitTransition, anchorRoot, epicenter, 2068 new TransitionListenerAdapter() { 2069 @Override 2070 public void onTransitionEnd(Transition transition) { 2071 dismissImmediate(decorView, contentHolder, contentView); 2072 } 2073 }); 2074 } else { 2075 dismissImmediate(decorView, contentHolder, contentView); 2076 } 2077 2078 // Clears the anchor view. 2079 detachFromAnchor(); 2080 2081 if (mOnDismissListener != null) { 2082 mOnDismissListener.onDismiss(); 2083 } 2084 } 2085 2086 /** 2087 * Returns the window-relative epicenter bounds to be used by enter and 2088 * exit transitions. 2089 * <p> 2090 * <strong>Note:</strong> This is distinct from the rect passed to 2091 * {@link #setEpicenterBounds(Rect)}, which is anchor-relative. 2092 * 2093 * @return the window-relative epicenter bounds to be used by enter and 2094 * exit transitions 2095 * 2096 * @hide 2097 */ 2098 protected final Rect getTransitionEpicenter() { 2099 final View anchor = mAnchor != null ? mAnchor.get() : null; 2100 final View decor = mDecorView; 2101 if (anchor == null || decor == null) { 2102 return null; 2103 } 2104 2105 final int[] anchorLocation = anchor.getLocationOnScreen(); 2106 final int[] popupLocation = mDecorView.getLocationOnScreen(); 2107 2108 // Compute the position of the anchor relative to the popup. 2109 final Rect bounds = new Rect(0, 0, anchor.getWidth(), anchor.getHeight()); 2110 bounds.offset(anchorLocation[0] - popupLocation[0], anchorLocation[1] - popupLocation[1]); 2111 2112 // Use anchor-relative epicenter, if specified. 2113 if (mEpicenterBounds != null) { 2114 final int offsetX = bounds.left; 2115 final int offsetY = bounds.top; 2116 bounds.set(mEpicenterBounds); 2117 bounds.offset(offsetX, offsetY); 2118 } 2119 2120 return bounds; 2121 } 2122 2123 /** 2124 * Removes the popup from the window manager and tears down the supporting 2125 * view hierarchy, if necessary. 2126 */ 2127 private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) { 2128 // If this method gets called and the decor view doesn't have a parent, 2129 // then it was either never added or was already removed. That should 2130 // never happen, but it's worth checking to avoid potential crashes. 2131 if (decorView.getParent() != null) { 2132 mWindowManager.removeViewImmediate(decorView); 2133 } 2134 2135 if (contentHolder != null) { 2136 contentHolder.removeView(contentView); 2137 } 2138 2139 // This needs to stay until after all transitions have ended since we 2140 // need the reference to cancel transitions in preparePopup(). 2141 mDecorView = null; 2142 mBackgroundView = null; 2143 mIsTransitioningToDismiss = false; 2144 } 2145 2146 /** 2147 * Sets the listener to be called when the window is dismissed. 2148 * 2149 * @param onDismissListener The listener. 2150 */ 2151 public void setOnDismissListener(OnDismissListener onDismissListener) { 2152 mOnDismissListener = onDismissListener; 2153 } 2154 2155 /** @hide */ 2156 protected final OnDismissListener getOnDismissListener() { 2157 return mOnDismissListener; 2158 } 2159 2160 /** 2161 * Updates the state of the popup window, if it is currently being displayed, 2162 * from the currently set state. 2163 * <p> 2164 * This includes: 2165 * <ul> 2166 * <li>{@link #setClippingEnabled(boolean)}</li> 2167 * <li>{@link #setFocusable(boolean)}</li> 2168 * <li>{@link #setIgnoreCheekPress()}</li> 2169 * <li>{@link #setInputMethodMode(int)}</li> 2170 * <li>{@link #setTouchable(boolean)}</li> 2171 * <li>{@link #setAnimationStyle(int)}</li> 2172 * <li>{@link #setTouchModal(boolean)} (boolean)}</li> 2173 * <li>{@link #setIsClippedToScreen(boolean)}</li> 2174 * </ul> 2175 */ 2176 public void update() { 2177 if (!isShowing() || !hasContentView()) { 2178 return; 2179 } 2180 2181 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2182 2183 boolean update = false; 2184 2185 final int newAnim = computeAnimationResource(); 2186 if (newAnim != p.windowAnimations) { 2187 p.windowAnimations = newAnim; 2188 update = true; 2189 } 2190 2191 final int newFlags = computeFlags(p.flags); 2192 if (newFlags != p.flags) { 2193 p.flags = newFlags; 2194 update = true; 2195 } 2196 2197 final int newGravity = computeGravity(); 2198 if (newGravity != p.gravity) { 2199 p.gravity = newGravity; 2200 update = true; 2201 } 2202 2203 if (update) { 2204 update(mAnchor != null ? mAnchor.get() : null, p); 2205 } 2206 } 2207 2208 /** @hide */ 2209 protected void update(View anchor, WindowManager.LayoutParams params) { 2210 setLayoutDirectionFromAnchor(); 2211 mWindowManager.updateViewLayout(mDecorView, params); 2212 } 2213 2214 /** 2215 * Updates the dimension of the popup window. 2216 * <p> 2217 * Calling this function also updates the window with the current popup 2218 * state as described for {@link #update()}. 2219 * 2220 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2221 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2222 */ 2223 public void update(int width, int height) { 2224 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2225 update(p.x, p.y, width, height, false); 2226 } 2227 2228 /** 2229 * Updates the position and the dimension of the popup window. 2230 * <p> 2231 * Width and height can be set to -1 to update location only. Calling this 2232 * function also updates the window with the current popup state as 2233 * described for {@link #update()}. 2234 * 2235 * @param x the new x location 2236 * @param y the new y location 2237 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2238 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2239 */ 2240 public void update(int x, int y, int width, int height) { 2241 update(x, y, width, height, false); 2242 } 2243 2244 /** 2245 * Updates the position and the dimension of the popup window. 2246 * <p> 2247 * Width and height can be set to -1 to update location only. Calling this 2248 * function also updates the window with the current popup state as 2249 * described for {@link #update()}. 2250 * 2251 * @param x the new x location 2252 * @param y the new y location 2253 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2254 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2255 * @param force {@code true} to reposition the window even if the specified 2256 * position already seems to correspond to the LayoutParams, 2257 * {@code false} to only reposition if needed 2258 */ 2259 public void update(int x, int y, int width, int height, boolean force) { 2260 if (width >= 0) { 2261 mLastWidth = width; 2262 setWidth(width); 2263 } 2264 2265 if (height >= 0) { 2266 mLastHeight = height; 2267 setHeight(height); 2268 } 2269 2270 if (!isShowing() || !hasContentView()) { 2271 return; 2272 } 2273 2274 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2275 2276 boolean update = force; 2277 2278 final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; 2279 if (width != -1 && p.width != finalWidth) { 2280 p.width = mLastWidth = finalWidth; 2281 update = true; 2282 } 2283 2284 final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; 2285 if (height != -1 && p.height != finalHeight) { 2286 p.height = mLastHeight = finalHeight; 2287 update = true; 2288 } 2289 2290 if (p.x != x) { 2291 p.x = x; 2292 update = true; 2293 } 2294 2295 if (p.y != y) { 2296 p.y = y; 2297 update = true; 2298 } 2299 2300 final int newAnim = computeAnimationResource(); 2301 if (newAnim != p.windowAnimations) { 2302 p.windowAnimations = newAnim; 2303 update = true; 2304 } 2305 2306 final int newFlags = computeFlags(p.flags); 2307 if (newFlags != p.flags) { 2308 p.flags = newFlags; 2309 update = true; 2310 } 2311 2312 final int newGravity = computeGravity(); 2313 if (newGravity != p.gravity) { 2314 p.gravity = newGravity; 2315 update = true; 2316 } 2317 2318 View anchor = null; 2319 int newAccessibilityIdOfAnchor = -1; 2320 2321 if (mAnchor != null && mAnchor.get() != null) { 2322 anchor = mAnchor.get(); 2323 newAccessibilityIdOfAnchor = anchor.getAccessibilityViewId(); 2324 } 2325 2326 if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) { 2327 p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor; 2328 update = true; 2329 } 2330 2331 if (update) { 2332 update(anchor, p); 2333 } 2334 } 2335 2336 /** @hide */ 2337 protected boolean hasContentView() { 2338 return mContentView != null; 2339 } 2340 2341 /** @hide */ 2342 protected boolean hasDecorView() { 2343 return mDecorView != null; 2344 } 2345 2346 /** @hide */ 2347 protected WindowManager.LayoutParams getDecorViewLayoutParams() { 2348 return (WindowManager.LayoutParams) mDecorView.getLayoutParams(); 2349 } 2350 2351 /** 2352 * Updates the position and the dimension of the popup window. 2353 * <p> 2354 * Calling this function also updates the window with the current popup 2355 * state as described for {@link #update()}. 2356 * 2357 * @param anchor the popup's anchor view 2358 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2359 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2360 */ 2361 public void update(View anchor, int width, int height) { 2362 update(anchor, false, 0, 0, width, height); 2363 } 2364 2365 /** 2366 * Updates the position and the dimension of the popup window. 2367 * <p> 2368 * Width and height can be set to -1 to update location only. Calling this 2369 * function also updates the window with the current popup state as 2370 * described for {@link #update()}. 2371 * <p> 2372 * If the view later scrolls to move {@code anchor} to a different 2373 * location, the popup will be moved correspondingly. 2374 * 2375 * @param anchor the popup's anchor view 2376 * @param xoff x offset from the view's left edge 2377 * @param yoff y offset from the view's bottom edge 2378 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2379 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2380 */ 2381 public void update(View anchor, int xoff, int yoff, int width, int height) { 2382 update(anchor, true, xoff, yoff, width, height); 2383 } 2384 2385 private void update(View anchor, boolean updateLocation, int xoff, int yoff, 2386 int width, int height) { 2387 2388 if (!isShowing() || !hasContentView()) { 2389 return; 2390 } 2391 2392 final WeakReference<View> oldAnchor = mAnchor; 2393 final int gravity = mAnchoredGravity; 2394 2395 final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff); 2396 if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) { 2397 attachToAnchor(anchor, xoff, yoff, gravity); 2398 } else if (needsUpdate) { 2399 // No need to register again if this is a DropDown, showAsDropDown already did. 2400 mAnchorXoff = xoff; 2401 mAnchorYoff = yoff; 2402 } 2403 2404 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2405 final int oldGravity = p.gravity; 2406 final int oldWidth = p.width; 2407 final int oldHeight = p.height; 2408 final int oldX = p.x; 2409 final int oldY = p.y; 2410 2411 // If an explicit width/height has not specified, use the most recent 2412 // explicitly specified value (either from setWidth/Height or update). 2413 if (width < 0) { 2414 width = mWidth; 2415 } 2416 if (height < 0) { 2417 height = mHeight; 2418 } 2419 2420 final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, 2421 width, height, gravity, mAllowScrollingAnchorParent); 2422 updateAboveAnchor(aboveAnchor); 2423 2424 final boolean paramsChanged = oldGravity != p.gravity || oldX != p.x || oldY != p.y 2425 || oldWidth != p.width || oldHeight != p.height; 2426 2427 // If width and mWidth were both < 0 then we have a MATCH_PARENT or 2428 // WRAP_CONTENT case. findDropDownPosition will have resolved this to 2429 // absolute values, but we don't want to update mWidth/mHeight to these 2430 // absolute values. 2431 final int newWidth = width < 0 ? width : p.width; 2432 final int newHeight = height < 0 ? height : p.height; 2433 update(p.x, p.y, newWidth, newHeight, paramsChanged); 2434 } 2435 2436 /** 2437 * Listener that is called when this popup window is dismissed. 2438 */ 2439 public interface OnDismissListener { 2440 /** 2441 * Called when this popup window is dismissed. 2442 */ 2443 public void onDismiss(); 2444 } 2445 2446 /** @hide */ 2447 protected void detachFromAnchor() { 2448 final View anchor = getAnchor(); 2449 if (anchor != null) { 2450 final ViewTreeObserver vto = anchor.getViewTreeObserver(); 2451 vto.removeOnScrollChangedListener(mOnScrollChangedListener); 2452 anchor.removeOnAttachStateChangeListener(mOnAnchorDetachedListener); 2453 } 2454 2455 final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null; 2456 if (anchorRoot != null) { 2457 anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2458 anchorRoot.removeOnLayoutChangeListener(mOnLayoutChangeListener); 2459 } 2460 2461 mAnchor = null; 2462 mAnchorRoot = null; 2463 mIsAnchorRootAttached = false; 2464 } 2465 2466 /** @hide */ 2467 protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) { 2468 detachFromAnchor(); 2469 2470 final ViewTreeObserver vto = anchor.getViewTreeObserver(); 2471 if (vto != null) { 2472 vto.addOnScrollChangedListener(mOnScrollChangedListener); 2473 } 2474 anchor.addOnAttachStateChangeListener(mOnAnchorDetachedListener); 2475 2476 final View anchorRoot = anchor.getRootView(); 2477 anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2478 anchorRoot.addOnLayoutChangeListener(mOnLayoutChangeListener); 2479 2480 mAnchor = new WeakReference<>(anchor); 2481 mAnchorRoot = new WeakReference<>(anchorRoot); 2482 mIsAnchorRootAttached = anchorRoot.isAttachedToWindow(); 2483 mParentRootView = mAnchorRoot; 2484 2485 mAnchorXoff = xoff; 2486 mAnchorYoff = yoff; 2487 mAnchoredGravity = gravity; 2488 } 2489 2490 /** @hide */ 2491 protected @Nullable View getAnchor() { 2492 return mAnchor != null ? mAnchor.get() : null; 2493 } 2494 2495 private void alignToAnchor() { 2496 final View anchor = mAnchor != null ? mAnchor.get() : null; 2497 if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) { 2498 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2499 2500 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, 2501 p.width, p.height, mAnchoredGravity, false)); 2502 update(p.x, p.y, -1, -1, true); 2503 } 2504 } 2505 2506 private View getAppRootView(View anchor) { 2507 final View appWindowView = WindowManagerGlobal.getInstance().getWindowView( 2508 anchor.getApplicationWindowToken()); 2509 if (appWindowView != null) { 2510 return appWindowView; 2511 } 2512 return anchor.getRootView(); 2513 } 2514 2515 private class PopupDecorView extends FrameLayout { 2516 /** Runnable used to clean up listeners after exit transition. */ 2517 private Runnable mCleanupAfterExit; 2518 2519 public PopupDecorView(Context context) { 2520 super(context); 2521 } 2522 2523 @Override 2524 public boolean dispatchKeyEvent(KeyEvent event) { 2525 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 2526 if (getKeyDispatcherState() == null) { 2527 return super.dispatchKeyEvent(event); 2528 } 2529 2530 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 2531 final KeyEvent.DispatcherState state = getKeyDispatcherState(); 2532 if (state != null) { 2533 state.startTracking(event, this); 2534 } 2535 return true; 2536 } else if (event.getAction() == KeyEvent.ACTION_UP) { 2537 final KeyEvent.DispatcherState state = getKeyDispatcherState(); 2538 if (state != null && state.isTracking(event) && !event.isCanceled()) { 2539 dismiss(); 2540 return true; 2541 } 2542 } 2543 return super.dispatchKeyEvent(event); 2544 } else { 2545 return super.dispatchKeyEvent(event); 2546 } 2547 } 2548 2549 @Override 2550 public boolean dispatchTouchEvent(MotionEvent ev) { 2551 if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { 2552 return true; 2553 } 2554 return super.dispatchTouchEvent(ev); 2555 } 2556 2557 @Override 2558 public boolean onTouchEvent(MotionEvent event) { 2559 final int x = (int) event.getX(); 2560 final int y = (int) event.getY(); 2561 2562 if ((event.getAction() == MotionEvent.ACTION_DOWN) 2563 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 2564 dismiss(); 2565 return true; 2566 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 2567 dismiss(); 2568 return true; 2569 } else { 2570 return super.onTouchEvent(event); 2571 } 2572 } 2573 2574 /** 2575 * Requests that an enter transition run after the next layout pass. 2576 */ 2577 public void requestEnterTransition(Transition transition) { 2578 final ViewTreeObserver observer = getViewTreeObserver(); 2579 if (observer != null && transition != null) { 2580 final Transition enterTransition = transition.clone(); 2581 2582 // Postpone the enter transition after the first layout pass. 2583 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 2584 @Override 2585 public void onGlobalLayout() { 2586 final ViewTreeObserver observer = getViewTreeObserver(); 2587 if (observer != null) { 2588 observer.removeOnGlobalLayoutListener(this); 2589 } 2590 2591 final Rect epicenter = getTransitionEpicenter(); 2592 enterTransition.setEpicenterCallback(new EpicenterCallback() { 2593 @Override 2594 public Rect onGetEpicenter(Transition transition) { 2595 return epicenter; 2596 } 2597 }); 2598 startEnterTransition(enterTransition); 2599 } 2600 }); 2601 } 2602 } 2603 2604 /** 2605 * Starts the pending enter transition, if one is set. 2606 */ 2607 private void startEnterTransition(Transition enterTransition) { 2608 final int count = getChildCount(); 2609 for (int i = 0; i < count; i++) { 2610 final View child = getChildAt(i); 2611 enterTransition.addTarget(child); 2612 child.setTransitionVisibility(View.INVISIBLE); 2613 } 2614 2615 TransitionManager.beginDelayedTransition(this, enterTransition); 2616 2617 for (int i = 0; i < count; i++) { 2618 final View child = getChildAt(i); 2619 child.setTransitionVisibility(View.VISIBLE); 2620 } 2621 } 2622 2623 /** 2624 * Starts an exit transition immediately. 2625 * <p> 2626 * <strong>Note:</strong> The transition listener is guaranteed to have 2627 * its {@code onTransitionEnd} method called even if the transition 2628 * never starts. 2629 */ 2630 public void startExitTransition(@NonNull Transition transition, 2631 @Nullable final View anchorRoot, @Nullable final Rect epicenter, 2632 @NonNull final TransitionListener listener) { 2633 if (transition == null) { 2634 return; 2635 } 2636 2637 // The anchor view's window may go away while we're executing our 2638 // transition, in which case we need to end the transition 2639 // immediately and execute the listener to remove the popup. 2640 if (anchorRoot != null) { 2641 anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2642 } 2643 2644 // The cleanup runnable MUST be called even if the transition is 2645 // canceled before it starts (and thus can't call onTransitionEnd). 2646 mCleanupAfterExit = () -> { 2647 listener.onTransitionEnd(transition); 2648 2649 if (anchorRoot != null) { 2650 anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2651 } 2652 2653 // The listener was called. Our job here is done. 2654 mCleanupAfterExit = null; 2655 }; 2656 2657 final Transition exitTransition = transition.clone(); 2658 exitTransition.addListener(new TransitionListenerAdapter() { 2659 @Override 2660 public void onTransitionEnd(Transition t) { 2661 t.removeListener(this); 2662 2663 // This null check shouldn't be necessary, but it's easier 2664 // to check here than it is to test every possible case. 2665 if (mCleanupAfterExit != null) { 2666 mCleanupAfterExit.run(); 2667 } 2668 } 2669 }); 2670 exitTransition.setEpicenterCallback(new EpicenterCallback() { 2671 @Override 2672 public Rect onGetEpicenter(Transition transition) { 2673 return epicenter; 2674 } 2675 }); 2676 2677 final int count = getChildCount(); 2678 for (int i = 0; i < count; i++) { 2679 final View child = getChildAt(i); 2680 exitTransition.addTarget(child); 2681 } 2682 2683 TransitionManager.beginDelayedTransition(this, exitTransition); 2684 2685 for (int i = 0; i < count; i++) { 2686 final View child = getChildAt(i); 2687 child.setVisibility(View.INVISIBLE); 2688 } 2689 } 2690 2691 /** 2692 * Cancels all pending or current transitions. 2693 */ 2694 public void cancelTransitions() { 2695 TransitionManager.endTransitions(this); 2696 2697 // If the cleanup runnable is still around, that means the 2698 // transition never started. We should run it now to clean up. 2699 if (mCleanupAfterExit != null) { 2700 mCleanupAfterExit.run(); 2701 } 2702 } 2703 2704 private final OnAttachStateChangeListener mOnAnchorRootDetachedListener = 2705 new OnAttachStateChangeListener() { 2706 @Override 2707 public void onViewAttachedToWindow(View v) {} 2708 2709 @Override 2710 public void onViewDetachedFromWindow(View v) { 2711 v.removeOnAttachStateChangeListener(this); 2712 2713 if (isAttachedToWindow()) { 2714 TransitionManager.endTransitions(PopupDecorView.this); 2715 } 2716 } 2717 }; 2718 2719 @Override 2720 public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId) { 2721 if (mParentRootView != null) { 2722 View parentRoot = mParentRootView.get(); 2723 if (parentRoot != null) { 2724 parentRoot.requestKeyboardShortcuts(list, deviceId); 2725 } 2726 } 2727 } 2728 } 2729 2730 private class PopupBackgroundView extends FrameLayout { 2731 public PopupBackgroundView(Context context) { 2732 super(context); 2733 } 2734 2735 @Override 2736 protected int[] onCreateDrawableState(int extraSpace) { 2737 if (mAboveAnchor) { 2738 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 2739 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); 2740 return drawableState; 2741 } else { 2742 return super.onCreateDrawableState(extraSpace); 2743 } 2744 } 2745 } 2746 } 2747