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