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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 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 */ 449 public void setEpicenterBounds(Rect bounds) { 450 mEpicenterBounds = bounds; 451 } 452 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 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 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 */ 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 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 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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