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