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