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