PopupWindow.java revision 7eab094722af54717859b7dcce3cc050f059e00b
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.util.DisplayMetrics; 32import android.view.Gravity; 33import android.view.KeyEvent; 34import android.view.MotionEvent; 35import android.view.View; 36import android.view.View.OnTouchListener; 37import android.view.ViewGroup; 38import android.view.ViewTreeObserver; 39import android.view.ViewTreeObserver.OnScrollChangedListener; 40import android.view.WindowManager; 41 42import java.lang.ref.WeakReference; 43 44/** 45 * <p>A popup window that can be used to display an arbitrary view. The popup 46 * windows is a floating container that appears on top of the current 47 * activity.</p> 48 * 49 * @see android.widget.AutoCompleteTextView 50 * @see android.widget.Spinner 51 */ 52public class PopupWindow { 53 /** 54 * Mode for {@link #setInputMethodMode(int)}: the requirements for the 55 * input method should be based on the focusability of the popup. That is 56 * if it is focusable than it needs to work with the input method, else 57 * it doesn't. 58 */ 59 public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; 60 61 /** 62 * Mode for {@link #setInputMethodMode(int)}: this popup always needs to 63 * work with an input method, regardless of whether it is focusable. This 64 * means that it will always be displayed so that the user can also operate 65 * the input method while it is shown. 66 */ 67 public static final int INPUT_METHOD_NEEDED = 1; 68 69 /** 70 * Mode for {@link #setInputMethodMode(int)}: this popup never needs to 71 * work with an input method, regardless of whether it is focusable. This 72 * means that it will always be displayed to use as much space on the 73 * screen as needed, regardless of whether this covers the input method. 74 */ 75 public static final int INPUT_METHOD_NOT_NEEDED = 2; 76 77 private Context mContext; 78 private WindowManager mWindowManager; 79 80 private boolean mIsShowing; 81 private boolean mIsDropdown; 82 83 private View mContentView; 84 private View mPopupView; 85 private boolean mFocusable; 86 private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; 87 private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; 88 private boolean mTouchable = true; 89 private boolean mOutsideTouchable = false; 90 private boolean mClippingEnabled = true; 91 private int mSplitTouchEnabled = -1; 92 private boolean mLayoutInScreen; 93 private boolean mClipToScreen; 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. To 382 * apply it while a popup is showing, call </p> 383 * 384 * @param contentView the new content for the popup 385 * 386 * @see #getContentView() 387 * @see #isShowing() 388 */ 389 public void setContentView(View contentView) { 390 if (isShowing()) { 391 return; 392 } 393 394 mContentView = contentView; 395 396 if (mContext == null) { 397 mContext = mContentView.getContext(); 398 } 399 400 if (mWindowManager == null) { 401 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 402 } 403 } 404 405 /** 406 * Set a callback for all touch events being dispatched to the popup 407 * window. 408 */ 409 public void setTouchInterceptor(OnTouchListener l) { 410 mTouchInterceptor = l; 411 } 412 413 /** 414 * <p>Indicate whether the popup window can grab the focus.</p> 415 * 416 * @return true if the popup is focusable, false otherwise 417 * 418 * @see #setFocusable(boolean) 419 */ 420 public boolean isFocusable() { 421 return mFocusable; 422 } 423 424 /** 425 * <p>Changes the focusability of the popup window. When focusable, the 426 * window will grab the focus from the current focused widget if the popup 427 * contains a focusable {@link android.view.View}. By default a popup 428 * window is not focusable.</p> 429 * 430 * <p>If the popup is showing, calling this method will take effect only 431 * the next time the popup is shown or through a manual call to one of 432 * the {@link #update()} methods.</p> 433 * 434 * @param focusable true if the popup should grab focus, false otherwise. 435 * 436 * @see #isFocusable() 437 * @see #isShowing() 438 * @see #update() 439 */ 440 public void setFocusable(boolean focusable) { 441 mFocusable = focusable; 442 } 443 444 /** 445 * Return the current value in {@link #setInputMethodMode(int)}. 446 * 447 * @see #setInputMethodMode(int) 448 */ 449 public int getInputMethodMode() { 450 return mInputMethodMode; 451 452 } 453 454 /** 455 * Control how the popup operates with an input method: one of 456 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, 457 * or {@link #INPUT_METHOD_NOT_NEEDED}. 458 * 459 * <p>If the popup is showing, calling this method will take effect only 460 * the next time the popup is shown or through a manual call to one of 461 * the {@link #update()} methods.</p> 462 * 463 * @see #getInputMethodMode() 464 * @see #update() 465 */ 466 public void setInputMethodMode(int mode) { 467 mInputMethodMode = mode; 468 } 469 470 /** 471 * Sets the operating mode for the soft input area. 472 * 473 * @param mode The desired mode, see 474 * {@link android.view.WindowManager.LayoutParams#softInputMode} 475 * for the full list 476 * 477 * @see android.view.WindowManager.LayoutParams#softInputMode 478 * @see #getSoftInputMode() 479 */ 480 public void setSoftInputMode(int mode) { 481 mSoftInputMode = mode; 482 } 483 484 /** 485 * Returns the current value in {@link #setSoftInputMode(int)}. 486 * 487 * @see #setSoftInputMode(int) 488 * @see android.view.WindowManager.LayoutParams#softInputMode 489 */ 490 public int getSoftInputMode() { 491 return mSoftInputMode; 492 } 493 494 /** 495 * <p>Indicates whether the popup window receives touch events.</p> 496 * 497 * @return true if the popup is touchable, false otherwise 498 * 499 * @see #setTouchable(boolean) 500 */ 501 public boolean isTouchable() { 502 return mTouchable; 503 } 504 505 /** 506 * <p>Changes the touchability of the popup window. When touchable, the 507 * window will receive touch events, otherwise touch events will go to the 508 * window below it. By default the window is touchable.</p> 509 * 510 * <p>If the popup is showing, calling this method will take effect only 511 * the next time the popup is shown or through a manual call to one of 512 * the {@link #update()} methods.</p> 513 * 514 * @param touchable true if the popup should receive touch events, false otherwise 515 * 516 * @see #isTouchable() 517 * @see #isShowing() 518 * @see #update() 519 */ 520 public void setTouchable(boolean touchable) { 521 mTouchable = touchable; 522 } 523 524 /** 525 * <p>Indicates whether the popup window will be informed of touch events 526 * outside of its window.</p> 527 * 528 * @return true if the popup is outside touchable, false otherwise 529 * 530 * @see #setOutsideTouchable(boolean) 531 */ 532 public boolean isOutsideTouchable() { 533 return mOutsideTouchable; 534 } 535 536 /** 537 * <p>Controls whether the pop-up will be informed of touch events outside 538 * of its window. This only makes sense for pop-ups that are touchable 539 * but not focusable, which means touches outside of the window will 540 * be delivered to the window behind. The default is false.</p> 541 * 542 * <p>If the popup is showing, calling this method will take effect only 543 * the next time the popup is shown or through a manual call to one of 544 * the {@link #update()} methods.</p> 545 * 546 * @param touchable true if the popup should receive outside 547 * touch events, false otherwise 548 * 549 * @see #isOutsideTouchable() 550 * @see #isShowing() 551 * @see #update() 552 */ 553 public void setOutsideTouchable(boolean touchable) { 554 mOutsideTouchable = touchable; 555 } 556 557 /** 558 * <p>Indicates whether clipping of the popup window is enabled.</p> 559 * 560 * @return true if the clipping is enabled, false otherwise 561 * 562 * @see #setClippingEnabled(boolean) 563 */ 564 public boolean isClippingEnabled() { 565 return mClippingEnabled; 566 } 567 568 /** 569 * <p>Allows the popup window to extend beyond the bounds of the screen. By default the 570 * window is clipped to the screen boundaries. Setting this to false will allow windows to be 571 * accurately positioned.</p> 572 * 573 * <p>If the popup is showing, calling this method will take effect only 574 * the next time the popup is shown or through a manual call to one of 575 * the {@link #update()} methods.</p> 576 * 577 * @param enabled false if the window should be allowed to extend outside of the screen 578 * @see #isShowing() 579 * @see #isClippingEnabled() 580 * @see #update() 581 */ 582 public void setClippingEnabled(boolean enabled) { 583 mClippingEnabled = enabled; 584 } 585 586 /** 587 * Clip this popup window to the screen, but not to the containing window. 588 * 589 * @param enabled True to clip to the screen. 590 * @hide 591 */ 592 public void setClipToScreenEnabled(boolean enabled) { 593 mClipToScreen = enabled; 594 setClippingEnabled(!enabled); 595 } 596 597 /** 598 * <p>Indicates whether the popup window supports splitting touches.</p> 599 * 600 * @return true if the touch splitting is enabled, false otherwise 601 * 602 * @see #setSplitTouchEnabled(boolean) 603 */ 604 public boolean isSplitTouchEnabled() { 605 if (mSplitTouchEnabled < 0 && mContext != null) { 606 return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB; 607 } 608 return mSplitTouchEnabled == 1; 609 } 610 611 /** 612 * <p>Allows the popup window to split touches across other windows that also 613 * support split touch. When this flag is false, the first pointer 614 * that goes down determines the window to which all subsequent touches 615 * go until all pointers go up. When this flag is true, each pointer 616 * (not necessarily the first) that goes down determines the window 617 * to which all subsequent touches of that pointer will go until that 618 * pointer goes up thereby enabling touches with multiple pointers 619 * to be split across multiple windows.</p> 620 * 621 * @param enabled true if the split touches should be enabled, false otherwise 622 * @see #isSplitTouchEnabled() 623 */ 624 public void setSplitTouchEnabled(boolean enabled) { 625 mSplitTouchEnabled = enabled ? 1 : 0; 626 } 627 628 /** 629 * <p>Indicates whether the popup window will be forced into using absolute screen coordinates 630 * for positioning.</p> 631 * 632 * @return true if the window will always be positioned in screen coordinates. 633 * @hide 634 */ 635 public boolean isLayoutInScreenEnabled() { 636 return mLayoutInScreen; 637 } 638 639 /** 640 * <p>Allows the popup window to force the flag 641 * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior. 642 * This will cause the popup to be positioned in absolute screen coordinates.</p> 643 * 644 * @param enabled true if the popup should always be positioned in screen coordinates 645 * @hide 646 */ 647 public void setLayoutInScreenEnabled(boolean enabled) { 648 mLayoutInScreen = enabled; 649 } 650 651 /** 652 * Set the layout type for this window. Should be one of the TYPE constants defined in 653 * {@link WindowManager.LayoutParams}. 654 * 655 * @param layoutType Layout type for this window. 656 * @hide 657 */ 658 public void setWindowLayoutType(int layoutType) { 659 mWindowLayoutType = layoutType; 660 } 661 662 /** 663 * @return The layout type for this window. 664 * @hide 665 */ 666 public int getWindowLayoutType() { 667 return mWindowLayoutType; 668 } 669 670 /** 671 * <p>Change the width and height measure specs that are given to the 672 * window manager by the popup. By default these are 0, meaning that 673 * the current width or height is requested as an explicit size from 674 * the window manager. You can supply 675 * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or 676 * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure 677 * spec supplied instead, replacing the absolute width and height that 678 * has been set in the popup.</p> 679 * 680 * <p>If the popup is showing, calling this method will take effect only 681 * the next time the popup is shown.</p> 682 * 683 * @param widthSpec an explicit width measure spec mode, either 684 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 685 * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute 686 * width. 687 * @param heightSpec an explicit height measure spec mode, either 688 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 689 * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute 690 * height. 691 */ 692 public void setWindowLayoutMode(int widthSpec, int heightSpec) { 693 mWidthMode = widthSpec; 694 mHeightMode = heightSpec; 695 } 696 697 /** 698 * <p>Return this popup's height MeasureSpec</p> 699 * 700 * @return the height MeasureSpec of the popup 701 * 702 * @see #setHeight(int) 703 */ 704 public int getHeight() { 705 return mHeight; 706 } 707 708 /** 709 * <p>Change the popup's height MeasureSpec</p> 710 * 711 * <p>If the popup is showing, calling this method will take effect only 712 * the next time the popup is shown.</p> 713 * 714 * @param height the height MeasureSpec of the popup 715 * 716 * @see #getHeight() 717 * @see #isShowing() 718 */ 719 public void setHeight(int height) { 720 mHeight = height; 721 } 722 723 /** 724 * <p>Return this popup's width MeasureSpec</p> 725 * 726 * @return the width MeasureSpec of the popup 727 * 728 * @see #setWidth(int) 729 */ 730 public int getWidth() { 731 return mWidth; 732 } 733 734 /** 735 * <p>Change the popup's width MeasureSpec</p> 736 * 737 * <p>If the popup is showing, calling this method will take effect only 738 * the next time the popup is shown.</p> 739 * 740 * @param width the width MeasureSpec of the popup 741 * 742 * @see #getWidth() 743 * @see #isShowing() 744 */ 745 public void setWidth(int width) { 746 mWidth = width; 747 } 748 749 /** 750 * <p>Indicate whether this popup window is showing on screen.</p> 751 * 752 * @return true if the popup is showing, false otherwise 753 */ 754 public boolean isShowing() { 755 return mIsShowing; 756 } 757 758 /** 759 * <p> 760 * Display the content view in a popup window at the specified location. If the popup window 761 * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams} 762 * for more information on how gravity and the x and y parameters are related. Specifying 763 * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying 764 * <code>Gravity.LEFT | Gravity.TOP</code>. 765 * </p> 766 * 767 * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from 768 * @param gravity the gravity which controls the placement of the popup window 769 * @param x the popup's x location offset 770 * @param y the popup's y location offset 771 */ 772 public void showAtLocation(View parent, int gravity, int x, int y) { 773 if (isShowing() || mContentView == null) { 774 return; 775 } 776 777 unregisterForScrollChanged(); 778 779 mIsShowing = true; 780 mIsDropdown = false; 781 782 WindowManager.LayoutParams p = createPopupLayout(parent.getWindowToken()); 783 p.windowAnimations = computeAnimationResource(); 784 785 preparePopup(p); 786 if (gravity == Gravity.NO_GRAVITY) { 787 gravity = Gravity.TOP | Gravity.LEFT; 788 } 789 p.gravity = gravity; 790 p.x = x; 791 p.y = y; 792 if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; 793 if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; 794 invokePopup(p); 795 } 796 797 /** 798 * <p>Display the content view in a popup window anchored to the bottom-left 799 * corner of the anchor view. If there is not enough room on screen to show 800 * the popup in its entirety, this method tries to find a parent scroll 801 * view to scroll. If no parent scroll view can be scrolled, the bottom-left 802 * corner of the popup is pinned at the top left corner of the anchor view.</p> 803 * 804 * @param anchor the view on which to pin the popup window 805 * 806 * @see #dismiss() 807 */ 808 public void showAsDropDown(View anchor) { 809 showAsDropDown(anchor, 0, 0); 810 } 811 812 /** 813 * <p>Display the content view in a popup window anchored to the bottom-left 814 * corner of the anchor view offset by the specified x and y coordinates. 815 * If there is not enough room on screen to show 816 * the popup in its entirety, this method tries to find a parent scroll 817 * view to scroll. If no parent scroll view can be scrolled, the bottom-left 818 * corner of the popup is pinned at the top left corner of the anchor view.</p> 819 * <p>If the view later scrolls to move <code>anchor</code> to a different 820 * location, the popup will be moved correspondingly.</p> 821 * 822 * @param anchor the view on which to pin the popup window 823 * 824 * @see #dismiss() 825 */ 826 public void showAsDropDown(View anchor, int xoff, int yoff) { 827 if (isShowing() || mContentView == null) { 828 return; 829 } 830 831 registerForScrollChanged(anchor, xoff, yoff); 832 833 mIsShowing = true; 834 mIsDropdown = true; 835 836 WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken()); 837 preparePopup(p); 838 839 updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff)); 840 841 if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; 842 if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; 843 844 p.windowAnimations = computeAnimationResource(); 845 846 invokePopup(p); 847 } 848 849 private void updateAboveAnchor(boolean aboveAnchor) { 850 if (aboveAnchor != mAboveAnchor) { 851 mAboveAnchor = aboveAnchor; 852 853 if (mBackground != null) { 854 // If the background drawable provided was a StateListDrawable with above-anchor 855 // and below-anchor states, use those. Otherwise rely on refreshDrawableState to 856 // do the job. 857 if (mAboveAnchorBackgroundDrawable != null) { 858 if (mAboveAnchor) { 859 mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable); 860 } else { 861 mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable); 862 } 863 } else { 864 mPopupView.refreshDrawableState(); 865 } 866 } 867 } 868 } 869 870 /** 871 * Indicates whether the popup is showing above (the y coordinate of the popup's bottom 872 * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate 873 * of the popup is greater than y coordinate of the anchor's bottom). 874 * 875 * The value returned 876 * by this method is meaningful only after {@link #showAsDropDown(android.view.View)} 877 * or {@link #showAsDropDown(android.view.View, int, int)} was invoked. 878 * 879 * @return True if this popup is showing above the anchor view, false otherwise. 880 */ 881 public boolean isAboveAnchor() { 882 return mAboveAnchor; 883 } 884 885 /** 886 * <p>Prepare the popup by embedding in into a new ViewGroup if the 887 * background drawable is not null. If embedding is required, the layout 888 * parameters' height is mnodified to take into account the background's 889 * padding.</p> 890 * 891 * @param p the layout parameters of the popup's content view 892 */ 893 private void preparePopup(WindowManager.LayoutParams p) { 894 if (mContentView == null || mContext == null || mWindowManager == null) { 895 throw new IllegalStateException("You must specify a valid content view by " 896 + "calling setContentView() before attempting to show the popup."); 897 } 898 899 if (mBackground != null) { 900 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 901 int height = ViewGroup.LayoutParams.MATCH_PARENT; 902 if (layoutParams != null && 903 layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { 904 height = ViewGroup.LayoutParams.WRAP_CONTENT; 905 } 906 907 // when a background is available, we embed the content view 908 // within another view that owns the background drawable 909 PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); 910 PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( 911 ViewGroup.LayoutParams.MATCH_PARENT, height 912 ); 913 popupViewContainer.setBackgroundDrawable(mBackground); 914 popupViewContainer.addView(mContentView, listParams); 915 916 mPopupView = popupViewContainer; 917 } else { 918 mPopupView = mContentView; 919 } 920 mPopupWidth = p.width; 921 mPopupHeight = p.height; 922 } 923 924 /** 925 * <p>Invoke the popup window by adding the content view to the window 926 * manager.</p> 927 * 928 * <p>The content view must be non-null when this method is invoked.</p> 929 * 930 * @param p the layout parameters of the popup's content view 931 */ 932 private void invokePopup(WindowManager.LayoutParams p) { 933 p.packageName = mContext.getPackageName(); 934 mWindowManager.addView(mPopupView, p); 935 } 936 937 /** 938 * <p>Generate the layout parameters for the popup window.</p> 939 * 940 * @param token the window token used to bind the popup's window 941 * 942 * @return the layout parameters to pass to the window manager 943 */ 944 private WindowManager.LayoutParams createPopupLayout(IBinder token) { 945 // generates the layout parameters for the drop down 946 // we want a fixed size view located at the bottom left of the anchor 947 WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 948 // these gravity settings put the view at the top left corner of the 949 // screen. The view is then positioned to the appropriate location 950 // by setting the x and y offsets to match the anchor's bottom 951 // left corner 952 p.gravity = Gravity.LEFT | Gravity.TOP; 953 p.width = mLastWidth = mWidth; 954 p.height = mLastHeight = mHeight; 955 if (mBackground != null) { 956 p.format = mBackground.getOpacity(); 957 } else { 958 p.format = PixelFormat.TRANSLUCENT; 959 } 960 p.flags = computeFlags(p.flags); 961 p.type = mWindowLayoutType; 962 p.token = token; 963 p.softInputMode = mSoftInputMode; 964 p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); 965 966 return p; 967 } 968 969 private int computeFlags(int curFlags) { 970 curFlags &= ~( 971 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | 972 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 973 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 974 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 975 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 976 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | 977 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH); 978 if(mIgnoreCheekPress) { 979 curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 980 } 981 if (!mFocusable) { 982 curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 983 if (mInputMethodMode == INPUT_METHOD_NEEDED) { 984 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 985 } 986 } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { 987 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 988 } 989 if (!mTouchable) { 990 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 991 } 992 if (mOutsideTouchable) { 993 curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 994 } 995 if (!mClippingEnabled) { 996 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 997 } 998 if (isSplitTouchEnabled()) { 999 curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 1000 } 1001 if (mLayoutInScreen) { 1002 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 1003 } 1004 return curFlags; 1005 } 1006 1007 private int computeAnimationResource() { 1008 if (mAnimationStyle == -1) { 1009 if (mIsDropdown) { 1010 return mAboveAnchor 1011 ? com.android.internal.R.style.Animation_DropDownUp 1012 : com.android.internal.R.style.Animation_DropDownDown; 1013 } 1014 return 0; 1015 } 1016 return mAnimationStyle; 1017 } 1018 1019 /** 1020 * <p>Positions the popup window on screen. When the popup window is too 1021 * tall to fit under the anchor, a parent scroll view is seeked and scrolled 1022 * up to reclaim space. If scrolling is not possible or not enough, the 1023 * popup window gets moved on top of the anchor.</p> 1024 * 1025 * <p>The height must have been set on the layout parameters prior to 1026 * calling this method.</p> 1027 * 1028 * @param anchor the view on which the popup window must be anchored 1029 * @param p the layout parameters used to display the drop down 1030 * 1031 * @return true if the popup is translated upwards to fit on screen 1032 */ 1033 private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, 1034 int xoff, int yoff) { 1035 1036 anchor.getLocationInWindow(mDrawingLocation); 1037 p.x = mDrawingLocation[0] + xoff; 1038 p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; 1039 1040 boolean onTop = false; 1041 1042 p.gravity = Gravity.LEFT | Gravity.TOP; 1043 1044 anchor.getLocationOnScreen(mScreenLocation); 1045 final Rect displayFrame = new Rect(); 1046 anchor.getWindowVisibleDisplayFrame(displayFrame); 1047 1048 final View root = anchor.getRootView(); 1049 if (p.y + mPopupHeight > displayFrame.bottom || p.x + mPopupWidth - root.getWidth() > 0) { 1050 // if the drop down disappears at the bottom of the screen. we try to 1051 // scroll a parent scrollview or move the drop down back up on top of 1052 // the edit box 1053 int scrollX = anchor.getScrollX(); 1054 int scrollY = anchor.getScrollY(); 1055 Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff, 1056 scrollY + mPopupHeight + anchor.getHeight() + yoff); 1057 anchor.requestRectangleOnScreen(r, true); 1058 1059 // now we re-evaluate the space available, and decide from that 1060 // whether the pop-up will go above or below the anchor. 1061 anchor.getLocationInWindow(mDrawingLocation); 1062 p.x = mDrawingLocation[0] + xoff; 1063 p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; 1064 1065 // determine whether there is more space above or below the anchor 1066 anchor.getLocationOnScreen(mScreenLocation); 1067 1068 onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getHeight() - yoff) < 1069 (mScreenLocation[1] - yoff - displayFrame.top); 1070 if (onTop) { 1071 p.gravity = Gravity.LEFT | Gravity.BOTTOM; 1072 p.y = root.getHeight() - mDrawingLocation[1] + yoff; 1073 } else { 1074 p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; 1075 } 1076 } 1077 1078 if (mClipToScreen) { 1079 final int displayFrameWidth = displayFrame.right - displayFrame.left; 1080 1081 int right = p.x + p.width; 1082 if (right > displayFrameWidth) { 1083 p.x -= right - displayFrameWidth; 1084 } 1085 if (p.x < displayFrame.left) { 1086 p.x = displayFrame.left; 1087 p.width = Math.min(p.width, displayFrameWidth); 1088 } 1089 1090 p.y = Math.max(p.y, displayFrame.top); 1091 } 1092 1093 p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; 1094 1095 return onTop; 1096 } 1097 1098 /** 1099 * Returns the maximum height that is available for the popup to be 1100 * completely shown. It is recommended that this height be the maximum for 1101 * the popup's height, otherwise it is possible that the popup will be 1102 * clipped. 1103 * 1104 * @param anchor The view on which the popup window must be anchored. 1105 * @return The maximum available height for the popup to be completely 1106 * shown. 1107 */ 1108 public int getMaxAvailableHeight(View anchor) { 1109 return getMaxAvailableHeight(anchor, 0); 1110 } 1111 1112 /** 1113 * Returns the maximum height that is available for the popup to be 1114 * completely shown. It is recommended that this height be the maximum for 1115 * the popup's height, otherwise it is possible that the popup will be 1116 * clipped. 1117 * 1118 * @param anchor The view on which the popup window must be anchored. 1119 * @param yOffset y offset from the view's bottom edge 1120 * @return The maximum available height for the popup to be completely 1121 * shown. 1122 */ 1123 public int getMaxAvailableHeight(View anchor, int yOffset) { 1124 return getMaxAvailableHeight(anchor, yOffset, false); 1125 } 1126 1127 /** 1128 * Returns the maximum height that is available for the popup to be 1129 * completely shown, optionally ignoring any bottom decorations such as 1130 * the input method. It is recommended that this height be the maximum for 1131 * the popup's height, otherwise it is possible that the popup will be 1132 * clipped. 1133 * 1134 * @param anchor The view on which the popup window must be anchored. 1135 * @param yOffset y offset from the view's bottom edge 1136 * @param ignoreBottomDecorations if true, the height returned will be 1137 * all the way to the bottom of the display, ignoring any 1138 * bottom decorations 1139 * @return The maximum available height for the popup to be completely 1140 * shown. 1141 * 1142 * @hide Pending API council approval. 1143 */ 1144 public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) { 1145 final Rect displayFrame = new Rect(); 1146 anchor.getWindowVisibleDisplayFrame(displayFrame); 1147 1148 final int[] anchorPos = mDrawingLocation; 1149 anchor.getLocationOnScreen(anchorPos); 1150 1151 int bottomEdge = displayFrame.bottom; 1152 if (ignoreBottomDecorations) { 1153 Resources res = anchor.getContext().getResources(); 1154 bottomEdge = res.getDisplayMetrics().heightPixels - 1155 (int) res.getDimension(com.android.internal.R.dimen.screen_margin_bottom); 1156 } 1157 final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; 1158 final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; 1159 1160 // anchorPos[1] is distance from anchor to top of screen 1161 int returnedHeight = Math.max(distanceToBottom, distanceToTop); 1162 if (mBackground != null) { 1163 mBackground.getPadding(mTempRect); 1164 returnedHeight -= mTempRect.top + mTempRect.bottom; 1165 } 1166 1167 return returnedHeight; 1168 } 1169 1170 /** 1171 * <p>Dispose of the popup window. This method can be invoked only after 1172 * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling 1173 * this method will have no effect.</p> 1174 * 1175 * @see #showAsDropDown(android.view.View) 1176 */ 1177 public void dismiss() { 1178 if (isShowing() && mPopupView != null) { 1179 unregisterForScrollChanged(); 1180 1181 try { 1182 mWindowManager.removeView(mPopupView); 1183 } finally { 1184 if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { 1185 ((ViewGroup) mPopupView).removeView(mContentView); 1186 } 1187 mPopupView = null; 1188 mIsShowing = false; 1189 1190 if (mOnDismissListener != null) { 1191 mOnDismissListener.onDismiss(); 1192 } 1193 } 1194 } 1195 } 1196 1197 /** 1198 * Sets the listener to be called when the window is dismissed. 1199 * 1200 * @param onDismissListener The listener. 1201 */ 1202 public void setOnDismissListener(OnDismissListener onDismissListener) { 1203 mOnDismissListener = onDismissListener; 1204 } 1205 1206 /** 1207 * Updates the state of the popup window, if it is currently being displayed, 1208 * from the currently set state. This include: 1209 * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)}, 1210 * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)}, 1211 * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}. 1212 */ 1213 public void update() { 1214 if (!isShowing() || mContentView == null) { 1215 return; 1216 } 1217 1218 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1219 mPopupView.getLayoutParams(); 1220 1221 boolean update = false; 1222 1223 final int newAnim = computeAnimationResource(); 1224 if (newAnim != p.windowAnimations) { 1225 p.windowAnimations = newAnim; 1226 update = true; 1227 } 1228 1229 final int newFlags = computeFlags(p.flags); 1230 if (newFlags != p.flags) { 1231 p.flags = newFlags; 1232 update = true; 1233 } 1234 1235 if (update) { 1236 mWindowManager.updateViewLayout(mPopupView, p); 1237 } 1238 } 1239 1240 /** 1241 * <p>Updates the dimension of the popup window. Calling this function 1242 * also updates the window with the current popup state as described 1243 * for {@link #update()}.</p> 1244 * 1245 * @param width the new width 1246 * @param height the new height 1247 */ 1248 public void update(int width, int height) { 1249 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1250 mPopupView.getLayoutParams(); 1251 update(p.x, p.y, width, height, false); 1252 } 1253 1254 /** 1255 * <p>Updates the position and the dimension of the popup window. Width and 1256 * height can be set to -1 to update location only. Calling this function 1257 * also updates the window with the current popup state as 1258 * described for {@link #update()}.</p> 1259 * 1260 * @param x the new x location 1261 * @param y the new y location 1262 * @param width the new width, can be -1 to ignore 1263 * @param height the new height, can be -1 to ignore 1264 */ 1265 public void update(int x, int y, int width, int height) { 1266 update(x, y, width, height, false); 1267 } 1268 1269 /** 1270 * <p>Updates the position and the dimension of the popup window. Width and 1271 * height can be set to -1 to update location only. Calling this function 1272 * also updates the window with the current popup state as 1273 * described for {@link #update()}.</p> 1274 * 1275 * @param x the new x location 1276 * @param y the new y location 1277 * @param width the new width, can be -1 to ignore 1278 * @param height the new height, can be -1 to ignore 1279 * @param force reposition the window even if the specified position 1280 * already seems to correspond to the LayoutParams 1281 */ 1282 public void update(int x, int y, int width, int height, boolean force) { 1283 if (width != -1) { 1284 mLastWidth = width; 1285 setWidth(width); 1286 } 1287 1288 if (height != -1) { 1289 mLastHeight = height; 1290 setHeight(height); 1291 } 1292 1293 if (!isShowing() || mContentView == null) { 1294 return; 1295 } 1296 1297 WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); 1298 1299 boolean update = force; 1300 1301 final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; 1302 if (width != -1 && p.width != finalWidth) { 1303 p.width = mLastWidth = finalWidth; 1304 update = true; 1305 } 1306 1307 final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; 1308 if (height != -1 && p.height != finalHeight) { 1309 p.height = mLastHeight = finalHeight; 1310 update = true; 1311 } 1312 1313 if (p.x != x) { 1314 p.x = x; 1315 update = true; 1316 } 1317 1318 if (p.y != y) { 1319 p.y = y; 1320 update = true; 1321 } 1322 1323 final int newAnim = computeAnimationResource(); 1324 if (newAnim != p.windowAnimations) { 1325 p.windowAnimations = newAnim; 1326 update = true; 1327 } 1328 1329 final int newFlags = computeFlags(p.flags); 1330 if (newFlags != p.flags) { 1331 p.flags = newFlags; 1332 update = true; 1333 } 1334 1335 if (update) { 1336 mWindowManager.updateViewLayout(mPopupView, p); 1337 } 1338 } 1339 1340 /** 1341 * <p>Updates the position and the dimension of the popup window. Calling this 1342 * function also updates the window with the current popup state as described 1343 * for {@link #update()}.</p> 1344 * 1345 * @param anchor the popup's anchor view 1346 * @param width the new width, can be -1 to ignore 1347 * @param height the new height, can be -1 to ignore 1348 */ 1349 public void update(View anchor, int width, int height) { 1350 update(anchor, false, 0, 0, true, width, height); 1351 } 1352 1353 /** 1354 * <p>Updates the position and the dimension of the popup window. Width and 1355 * height can be set to -1 to update location only. Calling this function 1356 * also updates the window with the current popup state as 1357 * described for {@link #update()}.</p> 1358 * <p>If the view later scrolls to move <code>anchor</code> to a different 1359 * location, the popup will be moved correspondingly.</p> 1360 * 1361 * @param anchor the popup's anchor view 1362 * @param xoff x offset from the view's left edge 1363 * @param yoff y offset from the view's bottom edge 1364 * @param width the new width, can be -1 to ignore 1365 * @param height the new height, can be -1 to ignore 1366 */ 1367 public void update(View anchor, int xoff, int yoff, int width, int height) { 1368 update(anchor, true, xoff, yoff, true, width, height); 1369 } 1370 1371 private void update(View anchor, boolean updateLocation, int xoff, int yoff, 1372 boolean updateDimension, int width, int height) { 1373 1374 if (!isShowing() || mContentView == null) { 1375 return; 1376 } 1377 1378 WeakReference<View> oldAnchor = mAnchor; 1379 if (oldAnchor == null || oldAnchor.get() != anchor || 1380 (updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff))) { 1381 registerForScrollChanged(anchor, xoff, yoff); 1382 } 1383 1384 WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); 1385 1386 if (updateDimension) { 1387 if (width == -1) { 1388 width = mPopupWidth; 1389 } else { 1390 mPopupWidth = width; 1391 } 1392 if (height == -1) { 1393 height = mPopupHeight; 1394 } else { 1395 mPopupHeight = height; 1396 } 1397 } 1398 1399 int x = p.x; 1400 int y = p.y; 1401 1402 if (updateLocation) { 1403 updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff)); 1404 } else { 1405 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff)); 1406 } 1407 1408 update(p.x, p.y, width, height, x != p.x || y != p.y); 1409 } 1410 1411 /** 1412 * Listener that is called when this popup window is dismissed. 1413 */ 1414 public interface OnDismissListener { 1415 /** 1416 * Called when this popup window is dismissed. 1417 */ 1418 public void onDismiss(); 1419 } 1420 1421 private void unregisterForScrollChanged() { 1422 WeakReference<View> anchorRef = mAnchor; 1423 View anchor = null; 1424 if (anchorRef != null) { 1425 anchor = anchorRef.get(); 1426 } 1427 if (anchor != null) { 1428 ViewTreeObserver vto = anchor.getViewTreeObserver(); 1429 vto.removeOnScrollChangedListener(mOnScrollChangedListener); 1430 } 1431 mAnchor = null; 1432 } 1433 1434 private void registerForScrollChanged(View anchor, int xoff, int yoff) { 1435 unregisterForScrollChanged(); 1436 1437 mAnchor = new WeakReference<View>(anchor); 1438 ViewTreeObserver vto = anchor.getViewTreeObserver(); 1439 if (vto != null) { 1440 vto.addOnScrollChangedListener(mOnScrollChangedListener); 1441 } 1442 1443 mAnchorXoff = xoff; 1444 mAnchorYoff = yoff; 1445 } 1446 1447 private class PopupViewContainer extends FrameLayout { 1448 private static final String TAG = "PopupWindow.PopupViewContainer"; 1449 1450 public PopupViewContainer(Context context) { 1451 super(context); 1452 } 1453 1454 @Override 1455 protected int[] onCreateDrawableState(int extraSpace) { 1456 if (mAboveAnchor) { 1457 // 1 more needed for the above anchor state 1458 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 1459 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); 1460 return drawableState; 1461 } else { 1462 return super.onCreateDrawableState(extraSpace); 1463 } 1464 } 1465 1466 @Override 1467 public boolean dispatchKeyEvent(KeyEvent event) { 1468 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1469 if (event.getAction() == KeyEvent.ACTION_DOWN 1470 && event.getRepeatCount() == 0) { 1471 getKeyDispatcherState().startTracking(event, this); 1472 return true; 1473 } else if (event.getAction() == KeyEvent.ACTION_UP 1474 && getKeyDispatcherState().isTracking(event) && !event.isCanceled()) { 1475 dismiss(); 1476 return true; 1477 } 1478 return super.dispatchKeyEvent(event); 1479 } else { 1480 return super.dispatchKeyEvent(event); 1481 } 1482 } 1483 1484 @Override 1485 public boolean dispatchTouchEvent(MotionEvent ev) { 1486 if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { 1487 return true; 1488 } 1489 return super.dispatchTouchEvent(ev); 1490 } 1491 1492 @Override 1493 public boolean onTouchEvent(MotionEvent event) { 1494 final int x = (int) event.getX(); 1495 final int y = (int) event.getY(); 1496 1497 if ((event.getAction() == MotionEvent.ACTION_DOWN) 1498 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 1499 dismiss(); 1500 return true; 1501 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 1502 dismiss(); 1503 return true; 1504 } else { 1505 return super.onTouchEvent(event); 1506 } 1507 } 1508 1509 @Override 1510 public void sendAccessibilityEvent(int eventType) { 1511 // clinets are interested in the content not the container, make it event source 1512 if (mContentView != null) { 1513 mContentView.sendAccessibilityEvent(eventType); 1514 } else { 1515 super.sendAccessibilityEvent(eventType); 1516 } 1517 } 1518 } 1519 1520} 1521