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