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