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