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