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