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