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