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