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 1358 // The background owner should be elevated so that it casts a shadow. 1359 mBackgroundView.setElevation(mElevation); 1360 1361 // We may wrap that in another view, so we'll need to manually specify 1362 // the surface insets. 1363 p.setSurfaceInsets(mBackgroundView, true /*manual*/, true /*preservePrevious*/); 1364 1365 mPopupViewInitialLayoutDirectionInherited = 1366 (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); 1367 } 1368 1369 /** 1370 * Wraps a content view in a PopupViewContainer. 1371 * 1372 * @param contentView the content view to wrap 1373 * @return a PopupViewContainer that wraps the content view 1374 */ createBackgroundView(View contentView)1375 private PopupBackgroundView createBackgroundView(View contentView) { 1376 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 1377 final int height; 1378 if (layoutParams != null && layoutParams.height == WRAP_CONTENT) { 1379 height = WRAP_CONTENT; 1380 } else { 1381 height = MATCH_PARENT; 1382 } 1383 1384 final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext); 1385 final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams( 1386 MATCH_PARENT, height); 1387 backgroundView.addView(contentView, listParams); 1388 1389 return backgroundView; 1390 } 1391 1392 /** 1393 * Wraps a content view in a FrameLayout. 1394 * 1395 * @param contentView the content view to wrap 1396 * @return a FrameLayout that wraps the content view 1397 */ createDecorView(View contentView)1398 private PopupDecorView createDecorView(View contentView) { 1399 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 1400 final int height; 1401 if (layoutParams != null && layoutParams.height == WRAP_CONTENT) { 1402 height = WRAP_CONTENT; 1403 } else { 1404 height = MATCH_PARENT; 1405 } 1406 1407 final PopupDecorView decorView = new PopupDecorView(mContext); 1408 decorView.addView(contentView, MATCH_PARENT, height); 1409 decorView.setClipChildren(false); 1410 decorView.setClipToPadding(false); 1411 1412 return decorView; 1413 } 1414 1415 /** 1416 * <p>Invoke the popup window by adding the content view to the window 1417 * manager.</p> 1418 * 1419 * <p>The content view must be non-null when this method is invoked.</p> 1420 * 1421 * @param p the layout parameters of the popup's content view 1422 */ invokePopup(WindowManager.LayoutParams p)1423 private void invokePopup(WindowManager.LayoutParams p) { 1424 if (mContext != null) { 1425 p.packageName = mContext.getPackageName(); 1426 } 1427 1428 final PopupDecorView decorView = mDecorView; 1429 decorView.setFitsSystemWindows(mLayoutInsetDecor); 1430 1431 setLayoutDirectionFromAnchor(); 1432 1433 mWindowManager.addView(decorView, p); 1434 1435 if (mEnterTransition != null) { 1436 decorView.requestEnterTransition(mEnterTransition); 1437 } 1438 } 1439 setLayoutDirectionFromAnchor()1440 private void setLayoutDirectionFromAnchor() { 1441 if (mAnchor != null) { 1442 View anchor = mAnchor.get(); 1443 if (anchor != null && mPopupViewInitialLayoutDirectionInherited) { 1444 mDecorView.setLayoutDirection(anchor.getLayoutDirection()); 1445 } 1446 } 1447 } 1448 computeGravity()1449 private int computeGravity() { 1450 int gravity = mGravity == Gravity.NO_GRAVITY ? Gravity.START | Gravity.TOP : mGravity; 1451 if (mIsDropdown && (mClipToScreen || mClippingEnabled)) { 1452 gravity |= Gravity.DISPLAY_CLIP_VERTICAL; 1453 } 1454 return gravity; 1455 } 1456 1457 /** 1458 * <p>Generate the layout parameters for the popup window.</p> 1459 * 1460 * @param token the window token used to bind the popup's window 1461 * 1462 * @return the layout parameters to pass to the window manager 1463 * 1464 * @hide 1465 */ createPopupLayoutParams(IBinder token)1466 protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { 1467 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 1468 1469 // These gravity settings put the view at the top left corner of the 1470 // screen. The view is then positioned to the appropriate location by 1471 // setting the x and y offsets to match the anchor's bottom-left 1472 // corner. 1473 p.gravity = computeGravity(); 1474 p.flags = computeFlags(p.flags); 1475 p.type = mWindowLayoutType; 1476 p.token = token; 1477 p.softInputMode = mSoftInputMode; 1478 p.windowAnimations = computeAnimationResource(); 1479 1480 if (mBackground != null) { 1481 p.format = mBackground.getOpacity(); 1482 } else { 1483 p.format = PixelFormat.TRANSLUCENT; 1484 } 1485 1486 if (mHeightMode < 0) { 1487 p.height = mLastHeight = mHeightMode; 1488 } else { 1489 p.height = mLastHeight = mHeight; 1490 } 1491 1492 if (mWidthMode < 0) { 1493 p.width = mLastWidth = mWidthMode; 1494 } else { 1495 p.width = mLastWidth = mWidth; 1496 } 1497 1498 p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH 1499 | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; 1500 1501 // Used for debugging. 1502 p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); 1503 1504 return p; 1505 } 1506 computeFlags(int curFlags)1507 private int computeFlags(int curFlags) { 1508 curFlags &= ~( 1509 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | 1510 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 1511 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 1512 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 1513 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 1514 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | 1515 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH); 1516 if(mIgnoreCheekPress) { 1517 curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 1518 } 1519 if (!mFocusable) { 1520 curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 1521 if (mInputMethodMode == INPUT_METHOD_NEEDED) { 1522 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1523 } 1524 } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { 1525 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1526 } 1527 if (!mTouchable) { 1528 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 1529 } 1530 if (mOutsideTouchable) { 1531 curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 1532 } 1533 if (!mClippingEnabled || mClipToScreen) { 1534 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 1535 } 1536 if (isSplitTouchEnabled()) { 1537 curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 1538 } 1539 if (mLayoutInScreen) { 1540 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 1541 } 1542 if (mLayoutInsetDecor) { 1543 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 1544 } 1545 if (mNotTouchModal) { 1546 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 1547 } 1548 if (mAttachedInDecor) { 1549 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR; 1550 } 1551 return curFlags; 1552 } 1553 computeAnimationResource()1554 private int computeAnimationResource() { 1555 if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) { 1556 if (mIsDropdown) { 1557 return mAboveAnchor 1558 ? com.android.internal.R.style.Animation_DropDownUp 1559 : com.android.internal.R.style.Animation_DropDownDown; 1560 } 1561 return 0; 1562 } 1563 return mAnimationStyle; 1564 } 1565 1566 /** 1567 * Positions the popup window on screen. When the popup window is too tall 1568 * to fit under the anchor, a parent scroll view is seeked and scrolled up 1569 * to reclaim space. If scrolling is not possible or not enough, the popup 1570 * window gets moved on top of the anchor. 1571 * <p> 1572 * The results of positioning are placed in {@code outParams}. 1573 * 1574 * @param anchor the view on which the popup window must be anchored 1575 * @param outParams the layout parameters used to display the drop down 1576 * @param xOffset absolute horizontal offset from the left of the anchor 1577 * @param yOffset absolute vertical offset from the top of the anchor 1578 * @param gravity horizontal gravity specifying popup alignment 1579 * @param allowScroll whether the anchor view's parent may be scrolled 1580 * when the popup window doesn't fit on screen 1581 * @return true if the popup is translated upwards to fit on screen 1582 * 1583 * @hide 1584 */ findDropDownPosition(View anchor, WindowManager.LayoutParams outParams, int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll)1585 protected final boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams, 1586 int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) { 1587 final int anchorHeight = anchor.getHeight(); 1588 final int anchorWidth = anchor.getWidth(); 1589 if (mOverlapAnchor) { 1590 yOffset -= anchorHeight; 1591 } 1592 1593 // Initially, align to the bottom-left corner of the anchor plus offsets. 1594 final int[] appScreenLocation = mTmpAppLocation; 1595 final View appRootView = getAppRootView(anchor); 1596 appRootView.getLocationOnScreen(appScreenLocation); 1597 1598 final int[] screenLocation = mTmpScreenLocation; 1599 anchor.getLocationOnScreen(screenLocation); 1600 1601 final int[] drawingLocation = mTmpDrawingLocation; 1602 drawingLocation[0] = screenLocation[0] - appScreenLocation[0]; 1603 drawingLocation[1] = screenLocation[1] - appScreenLocation[1]; 1604 outParams.x = drawingLocation[0] + xOffset; 1605 outParams.y = drawingLocation[1] + anchorHeight + yOffset; 1606 1607 final Rect displayFrame = new Rect(); 1608 appRootView.getWindowVisibleDisplayFrame(displayFrame); 1609 if (width == MATCH_PARENT) { 1610 width = displayFrame.right - displayFrame.left; 1611 } 1612 if (height == MATCH_PARENT) { 1613 height = displayFrame.bottom - displayFrame.top; 1614 } 1615 1616 // Let the window manager know to align the top to y. 1617 outParams.gravity = computeGravity(); 1618 outParams.width = width; 1619 outParams.height = height; 1620 1621 // If we need to adjust for gravity RIGHT, align to the bottom-right 1622 // corner of the anchor (still accounting for offsets). 1623 final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection()) 1624 & Gravity.HORIZONTAL_GRAVITY_MASK; 1625 if (hgrav == Gravity.RIGHT) { 1626 outParams.x -= width - anchorWidth; 1627 } 1628 1629 // First, attempt to fit the popup vertically without resizing. 1630 final boolean fitsVertical = tryFitVertical(outParams, yOffset, height, 1631 anchorHeight, drawingLocation[1], screenLocation[1], displayFrame.top, 1632 displayFrame.bottom, false); 1633 1634 // Next, attempt to fit the popup horizontally without resizing. 1635 final boolean fitsHorizontal = tryFitHorizontal(outParams, xOffset, width, 1636 anchorWidth, drawingLocation[0], screenLocation[0], displayFrame.left, 1637 displayFrame.right, false); 1638 1639 // If the popup still doesn't fit, attempt to scroll the parent. 1640 if (!fitsVertical || !fitsHorizontal) { 1641 final int scrollX = anchor.getScrollX(); 1642 final int scrollY = anchor.getScrollY(); 1643 final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset, 1644 scrollY + height + anchorHeight + yOffset); 1645 if (allowScroll && anchor.requestRectangleOnScreen(r, true)) { 1646 // Reset for the new anchor position. 1647 anchor.getLocationOnScreen(screenLocation); 1648 drawingLocation[0] = screenLocation[0] - appScreenLocation[0]; 1649 drawingLocation[1] = screenLocation[1] - appScreenLocation[1]; 1650 outParams.x = drawingLocation[0] + xOffset; 1651 outParams.y = drawingLocation[1] + anchorHeight + yOffset; 1652 1653 // Preserve the gravity adjustment. 1654 if (hgrav == Gravity.RIGHT) { 1655 outParams.x -= width - anchorWidth; 1656 } 1657 } 1658 1659 // Try to fit the popup again and allowing resizing. 1660 tryFitVertical(outParams, yOffset, height, anchorHeight, drawingLocation[1], 1661 screenLocation[1], displayFrame.top, displayFrame.bottom, mClipToScreen); 1662 tryFitHorizontal(outParams, xOffset, width, anchorWidth, drawingLocation[0], 1663 screenLocation[0], displayFrame.left, displayFrame.right, mClipToScreen); 1664 } 1665 1666 // Return whether the popup's top edge is above the anchor's top edge. 1667 return outParams.y < drawingLocation[1]; 1668 } 1669 tryFitVertical(@onNull LayoutParams outParams, int yOffset, int height, int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, boolean allowResize)1670 private boolean tryFitVertical(@NonNull LayoutParams outParams, int yOffset, int height, 1671 int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop, 1672 int displayFrameBottom, boolean allowResize) { 1673 final int winOffsetY = screenLocationY - drawingLocationY; 1674 final int anchorTopInScreen = outParams.y + winOffsetY; 1675 final int spaceBelow = displayFrameBottom - anchorTopInScreen; 1676 if (anchorTopInScreen >= 0 && height <= spaceBelow) { 1677 return true; 1678 } 1679 1680 final int spaceAbove = anchorTopInScreen - anchorHeight - displayFrameTop; 1681 if (height <= spaceAbove) { 1682 // Move everything up. 1683 if (mOverlapAnchor) { 1684 yOffset += anchorHeight; 1685 } 1686 outParams.y = drawingLocationY - height + yOffset; 1687 1688 return true; 1689 } 1690 1691 if (positionInDisplayVertical(outParams, height, drawingLocationY, screenLocationY, 1692 displayFrameTop, displayFrameBottom, allowResize)) { 1693 return true; 1694 } 1695 1696 return false; 1697 } 1698 positionInDisplayVertical(@onNull LayoutParams outParams, int height, int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, boolean canResize)1699 private boolean positionInDisplayVertical(@NonNull LayoutParams outParams, int height, 1700 int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, 1701 boolean canResize) { 1702 boolean fitsInDisplay = true; 1703 1704 final int winOffsetY = screenLocationY - drawingLocationY; 1705 outParams.y += winOffsetY; 1706 outParams.height = height; 1707 1708 final int bottom = outParams.y + height; 1709 if (bottom > displayFrameBottom) { 1710 // The popup is too far down, move it back in. 1711 outParams.y -= bottom - displayFrameBottom; 1712 } 1713 1714 if (outParams.y < displayFrameTop) { 1715 // The popup is too far up, move it back in and clip if 1716 // it's still too large. 1717 outParams.y = displayFrameTop; 1718 1719 final int displayFrameHeight = displayFrameBottom - displayFrameTop; 1720 if (canResize && height > displayFrameHeight) { 1721 outParams.height = displayFrameHeight; 1722 } else { 1723 fitsInDisplay = false; 1724 } 1725 } 1726 1727 outParams.y -= winOffsetY; 1728 1729 return fitsInDisplay; 1730 } 1731 tryFitHorizontal(@onNull LayoutParams outParams, int xOffset, int width, int anchorWidth, int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, boolean allowResize)1732 private boolean tryFitHorizontal(@NonNull LayoutParams outParams, int xOffset, int width, 1733 int anchorWidth, int drawingLocationX, int screenLocationX, int displayFrameLeft, 1734 int displayFrameRight, boolean allowResize) { 1735 final int winOffsetX = screenLocationX - drawingLocationX; 1736 final int anchorLeftInScreen = outParams.x + winOffsetX; 1737 final int spaceRight = displayFrameRight - anchorLeftInScreen; 1738 if (anchorLeftInScreen >= 0 && width <= spaceRight) { 1739 return true; 1740 } 1741 1742 if (positionInDisplayHorizontal(outParams, width, drawingLocationX, screenLocationX, 1743 displayFrameLeft, displayFrameRight, allowResize)) { 1744 return true; 1745 } 1746 1747 return false; 1748 } 1749 positionInDisplayHorizontal(@onNull LayoutParams outParams, int width, int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, boolean canResize)1750 private boolean positionInDisplayHorizontal(@NonNull LayoutParams outParams, int width, 1751 int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, 1752 boolean canResize) { 1753 boolean fitsInDisplay = true; 1754 1755 // Use screen coordinates for comparison against display frame. 1756 final int winOffsetX = screenLocationX - drawingLocationX; 1757 outParams.x += winOffsetX; 1758 1759 final int right = outParams.x + width; 1760 if (right > displayFrameRight) { 1761 // The popup is too far right, move it back in. 1762 outParams.x -= right - displayFrameRight; 1763 } 1764 1765 if (outParams.x < displayFrameLeft) { 1766 // The popup is too far left, move it back in and clip if it's 1767 // still too large. 1768 outParams.x = displayFrameLeft; 1769 1770 final int displayFrameWidth = displayFrameRight - displayFrameLeft; 1771 if (canResize && width > displayFrameWidth) { 1772 outParams.width = displayFrameWidth; 1773 } else { 1774 fitsInDisplay = false; 1775 } 1776 } 1777 1778 outParams.x -= winOffsetX; 1779 1780 return fitsInDisplay; 1781 } 1782 1783 /** 1784 * Returns the maximum height that is available for the popup to be 1785 * completely shown. It is recommended that this height be the maximum for 1786 * the popup's height, otherwise it is possible that the popup will be 1787 * clipped. 1788 * 1789 * @param anchor The view on which the popup window must be anchored. 1790 * @return The maximum available height for the popup to be completely 1791 * shown. 1792 */ getMaxAvailableHeight(@onNull View anchor)1793 public int getMaxAvailableHeight(@NonNull View anchor) { 1794 return getMaxAvailableHeight(anchor, 0); 1795 } 1796 1797 /** 1798 * Returns the maximum height that is available for the popup to be 1799 * completely shown. It is recommended that this height be the maximum for 1800 * the popup's height, otherwise it is possible that the popup will be 1801 * clipped. 1802 * 1803 * @param anchor The view on which the popup window must be anchored. 1804 * @param yOffset y offset from the view's bottom edge 1805 * @return The maximum available height for the popup to be completely 1806 * shown. 1807 */ getMaxAvailableHeight(@onNull View anchor, int yOffset)1808 public int getMaxAvailableHeight(@NonNull View anchor, int yOffset) { 1809 return getMaxAvailableHeight(anchor, yOffset, false); 1810 } 1811 1812 /** 1813 * Returns the maximum height that is available for the popup to be 1814 * completely shown, optionally ignoring any bottom decorations such as 1815 * the input method. It is recommended that this height be the maximum for 1816 * the popup's height, otherwise it is possible that the popup will be 1817 * clipped. 1818 * 1819 * @param anchor The view on which the popup window must be anchored. 1820 * @param yOffset y offset from the view's bottom edge 1821 * @param ignoreBottomDecorations if true, the height returned will be 1822 * all the way to the bottom of the display, ignoring any 1823 * bottom decorations 1824 * @return The maximum available height for the popup to be completely 1825 * shown. 1826 */ getMaxAvailableHeight( @onNull View anchor, int yOffset, boolean ignoreBottomDecorations)1827 public int getMaxAvailableHeight( 1828 @NonNull View anchor, int yOffset, boolean ignoreBottomDecorations) { 1829 Rect displayFrame = null; 1830 final Rect visibleDisplayFrame = new Rect(); 1831 1832 final View appView = getAppRootView(anchor); 1833 appView.getWindowVisibleDisplayFrame(visibleDisplayFrame); 1834 if (ignoreBottomDecorations) { 1835 // In the ignore bottom decorations case we want to 1836 // still respect all other decorations so we use the inset visible 1837 // frame on the top right and left and take the bottom 1838 // value from the full frame. 1839 displayFrame = new Rect(); 1840 anchor.getWindowDisplayFrame(displayFrame); 1841 displayFrame.top = visibleDisplayFrame.top; 1842 displayFrame.right = visibleDisplayFrame.right; 1843 displayFrame.left = visibleDisplayFrame.left; 1844 } else { 1845 displayFrame = visibleDisplayFrame; 1846 } 1847 1848 final int[] anchorPos = mTmpDrawingLocation; 1849 anchor.getLocationOnScreen(anchorPos); 1850 1851 final int bottomEdge = displayFrame.bottom; 1852 1853 final int distanceToBottom; 1854 if (mOverlapAnchor) { 1855 distanceToBottom = bottomEdge - anchorPos[1] - yOffset; 1856 } else { 1857 distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; 1858 } 1859 final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; 1860 1861 // anchorPos[1] is distance from anchor to top of screen 1862 int returnedHeight = Math.max(distanceToBottom, distanceToTop); 1863 if (mBackground != null) { 1864 mBackground.getPadding(mTempRect); 1865 returnedHeight -= mTempRect.top + mTempRect.bottom; 1866 } 1867 1868 return returnedHeight; 1869 } 1870 1871 /** 1872 * Disposes of the popup window. This method can be invoked only after 1873 * {@link #showAsDropDown(android.view.View)} has been executed. Failing 1874 * that, calling this method will have no effect. 1875 * 1876 * @see #showAsDropDown(android.view.View) 1877 */ dismiss()1878 public void dismiss() { 1879 if (!isShowing() || isTransitioningToDismiss()) { 1880 return; 1881 } 1882 1883 final PopupDecorView decorView = mDecorView; 1884 final View contentView = mContentView; 1885 1886 final ViewGroup contentHolder; 1887 final ViewParent contentParent = contentView.getParent(); 1888 if (contentParent instanceof ViewGroup) { 1889 contentHolder = ((ViewGroup) contentParent); 1890 } else { 1891 contentHolder = null; 1892 } 1893 1894 // Ensure any ongoing or pending transitions are canceled. 1895 decorView.cancelTransitions(); 1896 1897 mIsShowing = false; 1898 mIsTransitioningToDismiss = true; 1899 1900 // This method may be called as part of window detachment, in which 1901 // case the anchor view (and its root) will still return true from 1902 // isAttachedToWindow() during execution of this method; however, we 1903 // can expect the OnAttachStateChangeListener to have been called prior 1904 // to executing this method, so we can rely on that instead. 1905 final Transition exitTransition = mExitTransition; 1906 if (exitTransition != null && decorView.isLaidOut() 1907 && (mIsAnchorRootAttached || mAnchorRoot == null)) { 1908 // The decor view is non-interactive and non-IME-focusable during exit transitions. 1909 final LayoutParams p = (LayoutParams) decorView.getLayoutParams(); 1910 p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE; 1911 p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE; 1912 p.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1913 mWindowManager.updateViewLayout(decorView, p); 1914 1915 final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null; 1916 final Rect epicenter = getTransitionEpicenter(); 1917 1918 // Once we start dismissing the decor view, all state (including 1919 // the anchor root) needs to be moved to the decor view since we 1920 // may open another popup while it's busy exiting. 1921 decorView.startExitTransition(exitTransition, anchorRoot, epicenter, 1922 new TransitionListenerAdapter() { 1923 @Override 1924 public void onTransitionEnd(Transition transition) { 1925 dismissImmediate(decorView, contentHolder, contentView); 1926 } 1927 }); 1928 } else { 1929 dismissImmediate(decorView, contentHolder, contentView); 1930 } 1931 1932 // Clears the anchor view. 1933 detachFromAnchor(); 1934 1935 if (mOnDismissListener != null) { 1936 mOnDismissListener.onDismiss(); 1937 } 1938 } 1939 1940 /** 1941 * Returns the window-relative epicenter bounds to be used by enter and 1942 * exit transitions. 1943 * <p> 1944 * <strong>Note:</strong> This is distinct from the rect passed to 1945 * {@link #setEpicenterBounds(Rect)}, which is anchor-relative. 1946 * 1947 * @return the window-relative epicenter bounds to be used by enter and 1948 * exit transitions 1949 * 1950 * @hide 1951 */ getTransitionEpicenter()1952 protected final Rect getTransitionEpicenter() { 1953 final View anchor = mAnchor != null ? mAnchor.get() : null; 1954 final View decor = mDecorView; 1955 if (anchor == null || decor == null) { 1956 return null; 1957 } 1958 1959 final int[] anchorLocation = anchor.getLocationOnScreen(); 1960 final int[] popupLocation = mDecorView.getLocationOnScreen(); 1961 1962 // Compute the position of the anchor relative to the popup. 1963 final Rect bounds = new Rect(0, 0, anchor.getWidth(), anchor.getHeight()); 1964 bounds.offset(anchorLocation[0] - popupLocation[0], anchorLocation[1] - popupLocation[1]); 1965 1966 // Use anchor-relative epicenter, if specified. 1967 if (mEpicenterBounds != null) { 1968 final int offsetX = bounds.left; 1969 final int offsetY = bounds.top; 1970 bounds.set(mEpicenterBounds); 1971 bounds.offset(offsetX, offsetY); 1972 } 1973 1974 return bounds; 1975 } 1976 1977 /** 1978 * Removes the popup from the window manager and tears down the supporting 1979 * view hierarchy, if necessary. 1980 */ dismissImmediate(View decorView, ViewGroup contentHolder, View contentView)1981 private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) { 1982 // If this method gets called and the decor view doesn't have a parent, 1983 // then it was either never added or was already removed. That should 1984 // never happen, but it's worth checking to avoid potential crashes. 1985 if (decorView.getParent() != null) { 1986 mWindowManager.removeViewImmediate(decorView); 1987 } 1988 1989 if (contentHolder != null) { 1990 contentHolder.removeView(contentView); 1991 } 1992 1993 // This needs to stay until after all transitions have ended since we 1994 // need the reference to cancel transitions in preparePopup(). 1995 mDecorView = null; 1996 mBackgroundView = null; 1997 mIsTransitioningToDismiss = false; 1998 } 1999 2000 /** 2001 * Sets the listener to be called when the window is dismissed. 2002 * 2003 * @param onDismissListener The listener. 2004 */ setOnDismissListener(OnDismissListener onDismissListener)2005 public void setOnDismissListener(OnDismissListener onDismissListener) { 2006 mOnDismissListener = onDismissListener; 2007 } 2008 2009 /** @hide */ getOnDismissListener()2010 protected final OnDismissListener getOnDismissListener() { 2011 return mOnDismissListener; 2012 } 2013 2014 /** 2015 * Updates the state of the popup window, if it is currently being displayed, 2016 * from the currently set state. 2017 * <p> 2018 * This includes: 2019 * <ul> 2020 * <li>{@link #setClippingEnabled(boolean)}</li> 2021 * <li>{@link #setFocusable(boolean)}</li> 2022 * <li>{@link #setIgnoreCheekPress()}</li> 2023 * <li>{@link #setInputMethodMode(int)}</li> 2024 * <li>{@link #setTouchable(boolean)}</li> 2025 * <li>{@link #setAnimationStyle(int)}</li> 2026 * </ul> 2027 */ update()2028 public void update() { 2029 if (!isShowing() || !hasContentView()) { 2030 return; 2031 } 2032 2033 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2034 2035 boolean update = false; 2036 2037 final int newAnim = computeAnimationResource(); 2038 if (newAnim != p.windowAnimations) { 2039 p.windowAnimations = newAnim; 2040 update = true; 2041 } 2042 2043 final int newFlags = computeFlags(p.flags); 2044 if (newFlags != p.flags) { 2045 p.flags = newFlags; 2046 update = true; 2047 } 2048 2049 final int newGravity = computeGravity(); 2050 if (newGravity != p.gravity) { 2051 p.gravity = newGravity; 2052 update = true; 2053 } 2054 2055 if (update) { 2056 update(mAnchor != null ? mAnchor.get() : null, p); 2057 } 2058 } 2059 2060 /** @hide */ update(View anchor, WindowManager.LayoutParams params)2061 protected void update(View anchor, WindowManager.LayoutParams params) { 2062 setLayoutDirectionFromAnchor(); 2063 mWindowManager.updateViewLayout(mDecorView, params); 2064 } 2065 2066 /** 2067 * Updates the dimension of the popup window. 2068 * <p> 2069 * Calling this function also updates the window with the current popup 2070 * state as described for {@link #update()}. 2071 * 2072 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2073 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2074 */ update(int width, int height)2075 public void update(int width, int height) { 2076 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2077 update(p.x, p.y, width, height, false); 2078 } 2079 2080 /** 2081 * Updates the position and the dimension of the popup window. 2082 * <p> 2083 * Width and height can be set to -1 to update location only. Calling this 2084 * function also updates the window with the current popup state as 2085 * described for {@link #update()}. 2086 * 2087 * @param x the new x location 2088 * @param y the new y location 2089 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2090 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2091 */ update(int x, int y, int width, int height)2092 public void update(int x, int y, int width, int height) { 2093 update(x, y, width, height, false); 2094 } 2095 2096 /** 2097 * Updates the position and the dimension of the popup window. 2098 * <p> 2099 * Width and height can be set to -1 to update location only. Calling this 2100 * function also updates the window with the current popup state as 2101 * described for {@link #update()}. 2102 * 2103 * @param x the new x location 2104 * @param y the new y location 2105 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2106 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2107 * @param force {@code true} to reposition the window even if the specified 2108 * position already seems to correspond to the LayoutParams, 2109 * {@code false} to only reposition if needed 2110 */ update(int x, int y, int width, int height, boolean force)2111 public void update(int x, int y, int width, int height, boolean force) { 2112 if (width >= 0) { 2113 mLastWidth = width; 2114 setWidth(width); 2115 } 2116 2117 if (height >= 0) { 2118 mLastHeight = height; 2119 setHeight(height); 2120 } 2121 2122 if (!isShowing() || !hasContentView()) { 2123 return; 2124 } 2125 2126 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2127 2128 boolean update = force; 2129 2130 final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; 2131 if (width != -1 && p.width != finalWidth) { 2132 p.width = mLastWidth = finalWidth; 2133 update = true; 2134 } 2135 2136 final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; 2137 if (height != -1 && p.height != finalHeight) { 2138 p.height = mLastHeight = finalHeight; 2139 update = true; 2140 } 2141 2142 if (p.x != x) { 2143 p.x = x; 2144 update = true; 2145 } 2146 2147 if (p.y != y) { 2148 p.y = y; 2149 update = true; 2150 } 2151 2152 final int newAnim = computeAnimationResource(); 2153 if (newAnim != p.windowAnimations) { 2154 p.windowAnimations = newAnim; 2155 update = true; 2156 } 2157 2158 final int newFlags = computeFlags(p.flags); 2159 if (newFlags != p.flags) { 2160 p.flags = newFlags; 2161 update = true; 2162 } 2163 2164 final int newGravity = computeGravity(); 2165 if (newGravity != p.gravity) { 2166 p.gravity = newGravity; 2167 update = true; 2168 } 2169 2170 View anchor = null; 2171 int newAccessibilityIdOfAnchor = -1; 2172 2173 if (mAnchor != null && mAnchor.get() != null) { 2174 anchor = mAnchor.get(); 2175 newAccessibilityIdOfAnchor = anchor.getAccessibilityViewId(); 2176 } 2177 2178 if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) { 2179 p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor; 2180 update = true; 2181 } 2182 2183 if (update) { 2184 update(anchor, p); 2185 } 2186 } 2187 2188 /** @hide */ 2189 protected boolean hasContentView() { 2190 return mContentView != null; 2191 } 2192 2193 /** @hide */ 2194 protected boolean hasDecorView() { 2195 return mDecorView != null; 2196 } 2197 2198 /** @hide */ 2199 protected WindowManager.LayoutParams getDecorViewLayoutParams() { 2200 return (WindowManager.LayoutParams) mDecorView.getLayoutParams(); 2201 } 2202 2203 /** 2204 * Updates the position and the dimension of the popup window. 2205 * <p> 2206 * Calling this function also updates the window with the current popup 2207 * state as described for {@link #update()}. 2208 * 2209 * @param anchor the popup's anchor view 2210 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2211 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2212 */ 2213 public void update(View anchor, int width, int height) { 2214 update(anchor, false, 0, 0, width, height); 2215 } 2216 2217 /** 2218 * Updates the position and the dimension of the popup window. 2219 * <p> 2220 * Width and height can be set to -1 to update location only. Calling this 2221 * function also updates the window with the current popup state as 2222 * described for {@link #update()}. 2223 * <p> 2224 * If the view later scrolls to move {@code anchor} to a different 2225 * location, the popup will be moved correspondingly. 2226 * 2227 * @param anchor the popup's anchor view 2228 * @param xoff x offset from the view's left edge 2229 * @param yoff y offset from the view's bottom edge 2230 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2231 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2232 */ 2233 public void update(View anchor, int xoff, int yoff, int width, int height) { 2234 update(anchor, true, xoff, yoff, width, height); 2235 } 2236 2237 private void update(View anchor, boolean updateLocation, int xoff, int yoff, 2238 int width, int height) { 2239 2240 if (!isShowing() || !hasContentView()) { 2241 return; 2242 } 2243 2244 final WeakReference<View> oldAnchor = mAnchor; 2245 final int gravity = mAnchoredGravity; 2246 2247 final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff); 2248 if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) { 2249 attachToAnchor(anchor, xoff, yoff, gravity); 2250 } else if (needsUpdate) { 2251 // No need to register again if this is a DropDown, showAsDropDown already did. 2252 mAnchorXoff = xoff; 2253 mAnchorYoff = yoff; 2254 } 2255 2256 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2257 final int oldGravity = p.gravity; 2258 final int oldWidth = p.width; 2259 final int oldHeight = p.height; 2260 final int oldX = p.x; 2261 final int oldY = p.y; 2262 2263 // If an explicit width/height has not specified, use the most recent 2264 // explicitly specified value (either from setWidth/Height or update). 2265 if (width < 0) { 2266 width = mWidth; 2267 } 2268 if (height < 0) { 2269 height = mHeight; 2270 } 2271 2272 final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, 2273 width, height, gravity, mAllowScrollingAnchorParent); 2274 updateAboveAnchor(aboveAnchor); 2275 2276 final boolean paramsChanged = oldGravity != p.gravity || oldX != p.x || oldY != p.y 2277 || oldWidth != p.width || oldHeight != p.height; 2278 2279 // If width and mWidth were both < 0 then we have a MATCH_PARENT or 2280 // WRAP_CONTENT case. findDropDownPosition will have resolved this to 2281 // absolute values, but we don't want to update mWidth/mHeight to these 2282 // absolute values. 2283 final int newWidth = width < 0 ? width : p.width; 2284 final int newHeight = height < 0 ? height : p.height; 2285 update(p.x, p.y, newWidth, newHeight, paramsChanged); 2286 } 2287 2288 /** 2289 * Listener that is called when this popup window is dismissed. 2290 */ 2291 public interface OnDismissListener { 2292 /** 2293 * Called when this popup window is dismissed. 2294 */ 2295 public void onDismiss(); 2296 } 2297 2298 /** @hide */ 2299 protected void detachFromAnchor() { 2300 final View anchor = getAnchor(); 2301 if (anchor != null) { 2302 final ViewTreeObserver vto = anchor.getViewTreeObserver(); 2303 vto.removeOnScrollChangedListener(mOnScrollChangedListener); 2304 anchor.removeOnAttachStateChangeListener(mOnAnchorDetachedListener); 2305 } 2306 2307 final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null; 2308 if (anchorRoot != null) { 2309 anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2310 anchorRoot.removeOnLayoutChangeListener(mOnLayoutChangeListener); 2311 } 2312 2313 mAnchor = null; 2314 mAnchorRoot = null; 2315 mIsAnchorRootAttached = false; 2316 } 2317 2318 /** @hide */ 2319 protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) { 2320 detachFromAnchor(); 2321 2322 final ViewTreeObserver vto = anchor.getViewTreeObserver(); 2323 if (vto != null) { 2324 vto.addOnScrollChangedListener(mOnScrollChangedListener); 2325 } 2326 anchor.addOnAttachStateChangeListener(mOnAnchorDetachedListener); 2327 2328 final View anchorRoot = anchor.getRootView(); 2329 anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2330 anchorRoot.addOnLayoutChangeListener(mOnLayoutChangeListener); 2331 2332 mAnchor = new WeakReference<>(anchor); 2333 mAnchorRoot = new WeakReference<>(anchorRoot); 2334 mIsAnchorRootAttached = anchorRoot.isAttachedToWindow(); 2335 mParentRootView = mAnchorRoot; 2336 2337 mAnchorXoff = xoff; 2338 mAnchorYoff = yoff; 2339 mAnchoredGravity = gravity; 2340 } 2341 2342 /** @hide */ 2343 protected @Nullable View getAnchor() { 2344 return mAnchor != null ? mAnchor.get() : null; 2345 } 2346 2347 private void alignToAnchor() { 2348 final View anchor = mAnchor != null ? mAnchor.get() : null; 2349 if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) { 2350 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2351 2352 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, 2353 p.width, p.height, mAnchoredGravity, false)); 2354 update(p.x, p.y, -1, -1, true); 2355 } 2356 } 2357 2358 private View getAppRootView(View anchor) { 2359 final View appWindowView = WindowManagerGlobal.getInstance().getWindowView( 2360 anchor.getApplicationWindowToken()); 2361 if (appWindowView != null) { 2362 return appWindowView; 2363 } 2364 return anchor.getRootView(); 2365 } 2366 2367 private class PopupDecorView extends FrameLayout { 2368 /** Runnable used to clean up listeners after exit transition. */ 2369 private Runnable mCleanupAfterExit; 2370 2371 public PopupDecorView(Context context) { 2372 super(context); 2373 } 2374 2375 @Override 2376 public boolean dispatchKeyEvent(KeyEvent event) { 2377 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 2378 if (getKeyDispatcherState() == null) { 2379 return super.dispatchKeyEvent(event); 2380 } 2381 2382 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 2383 final KeyEvent.DispatcherState state = getKeyDispatcherState(); 2384 if (state != null) { 2385 state.startTracking(event, this); 2386 } 2387 return true; 2388 } else if (event.getAction() == KeyEvent.ACTION_UP) { 2389 final KeyEvent.DispatcherState state = getKeyDispatcherState(); 2390 if (state != null && state.isTracking(event) && !event.isCanceled()) { 2391 dismiss(); 2392 return true; 2393 } 2394 } 2395 return super.dispatchKeyEvent(event); 2396 } else { 2397 return super.dispatchKeyEvent(event); 2398 } 2399 } 2400 2401 @Override 2402 public boolean dispatchTouchEvent(MotionEvent ev) { 2403 if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { 2404 return true; 2405 } 2406 return super.dispatchTouchEvent(ev); 2407 } 2408 2409 @Override 2410 public boolean onTouchEvent(MotionEvent event) { 2411 final int x = (int) event.getX(); 2412 final int y = (int) event.getY(); 2413 2414 if ((event.getAction() == MotionEvent.ACTION_DOWN) 2415 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 2416 dismiss(); 2417 return true; 2418 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 2419 dismiss(); 2420 return true; 2421 } else { 2422 return super.onTouchEvent(event); 2423 } 2424 } 2425 2426 /** 2427 * Requests that an enter transition run after the next layout pass. 2428 */ 2429 public void requestEnterTransition(Transition transition) { 2430 final ViewTreeObserver observer = getViewTreeObserver(); 2431 if (observer != null && transition != null) { 2432 final Transition enterTransition = transition.clone(); 2433 2434 // Postpone the enter transition after the first layout pass. 2435 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 2436 @Override 2437 public void onGlobalLayout() { 2438 final ViewTreeObserver observer = getViewTreeObserver(); 2439 if (observer != null) { 2440 observer.removeOnGlobalLayoutListener(this); 2441 } 2442 2443 final Rect epicenter = getTransitionEpicenter(); 2444 enterTransition.setEpicenterCallback(new EpicenterCallback() { 2445 @Override 2446 public Rect onGetEpicenter(Transition transition) { 2447 return epicenter; 2448 } 2449 }); 2450 startEnterTransition(enterTransition); 2451 } 2452 }); 2453 } 2454 } 2455 2456 /** 2457 * Starts the pending enter transition, if one is set. 2458 */ 2459 private void startEnterTransition(Transition enterTransition) { 2460 final int count = getChildCount(); 2461 for (int i = 0; i < count; i++) { 2462 final View child = getChildAt(i); 2463 enterTransition.addTarget(child); 2464 child.setVisibility(View.INVISIBLE); 2465 } 2466 2467 TransitionManager.beginDelayedTransition(this, enterTransition); 2468 2469 for (int i = 0; i < count; i++) { 2470 final View child = getChildAt(i); 2471 child.setVisibility(View.VISIBLE); 2472 } 2473 } 2474 2475 /** 2476 * Starts an exit transition immediately. 2477 * <p> 2478 * <strong>Note:</strong> The transition listener is guaranteed to have 2479 * its {@code onTransitionEnd} method called even if the transition 2480 * never starts. 2481 */ 2482 public void startExitTransition(@NonNull Transition transition, 2483 @Nullable final View anchorRoot, @Nullable final Rect epicenter, 2484 @NonNull final TransitionListener listener) { 2485 if (transition == null) { 2486 return; 2487 } 2488 2489 // The anchor view's window may go away while we're executing our 2490 // transition, in which case we need to end the transition 2491 // immediately and execute the listener to remove the popup. 2492 if (anchorRoot != null) { 2493 anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2494 } 2495 2496 // The cleanup runnable MUST be called even if the transition is 2497 // canceled before it starts (and thus can't call onTransitionEnd). 2498 mCleanupAfterExit = () -> { 2499 listener.onTransitionEnd(transition); 2500 2501 if (anchorRoot != null) { 2502 anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2503 } 2504 2505 // The listener was called. Our job here is done. 2506 mCleanupAfterExit = null; 2507 }; 2508 2509 final Transition exitTransition = transition.clone(); 2510 exitTransition.addListener(new TransitionListenerAdapter() { 2511 @Override 2512 public void onTransitionEnd(Transition t) { 2513 t.removeListener(this); 2514 2515 // This null check shouldn't be necessary, but it's easier 2516 // to check here than it is to test every possible case. 2517 if (mCleanupAfterExit != null) { 2518 mCleanupAfterExit.run(); 2519 } 2520 } 2521 }); 2522 exitTransition.setEpicenterCallback(new EpicenterCallback() { 2523 @Override 2524 public Rect onGetEpicenter(Transition transition) { 2525 return epicenter; 2526 } 2527 }); 2528 2529 final int count = getChildCount(); 2530 for (int i = 0; i < count; i++) { 2531 final View child = getChildAt(i); 2532 exitTransition.addTarget(child); 2533 } 2534 2535 TransitionManager.beginDelayedTransition(this, exitTransition); 2536 2537 for (int i = 0; i < count; i++) { 2538 final View child = getChildAt(i); 2539 child.setVisibility(View.INVISIBLE); 2540 } 2541 } 2542 2543 /** 2544 * Cancels all pending or current transitions. 2545 */ 2546 public void cancelTransitions() { 2547 TransitionManager.endTransitions(this); 2548 2549 // If the cleanup runnable is still around, that means the 2550 // transition never started. We should run it now to clean up. 2551 if (mCleanupAfterExit != null) { 2552 mCleanupAfterExit.run(); 2553 } 2554 } 2555 2556 private final OnAttachStateChangeListener mOnAnchorRootDetachedListener = 2557 new OnAttachStateChangeListener() { 2558 @Override 2559 public void onViewAttachedToWindow(View v) {} 2560 2561 @Override 2562 public void onViewDetachedFromWindow(View v) { 2563 v.removeOnAttachStateChangeListener(this); 2564 2565 TransitionManager.endTransitions(PopupDecorView.this); 2566 } 2567 }; 2568 2569 @Override 2570 public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId) { 2571 if (mParentRootView != null) { 2572 View parentRoot = mParentRootView.get(); 2573 if (parentRoot != null) { 2574 parentRoot.requestKeyboardShortcuts(list, deviceId); 2575 } 2576 } 2577 } 2578 } 2579 2580 private class PopupBackgroundView extends FrameLayout { 2581 public PopupBackgroundView(Context context) { 2582 super(context); 2583 } 2584 2585 @Override 2586 protected int[] onCreateDrawableState(int extraSpace) { 2587 if (mAboveAnchor) { 2588 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 2589 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); 2590 return drawableState; 2591 } else { 2592 return super.onCreateDrawableState(extraSpace); 2593 } 2594 } 2595 } 2596 } 2597