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