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