PopupWindow.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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.view.KeyEvent; 22import android.view.MotionEvent; 23import android.view.View; 24import android.view.WindowManager; 25import android.view.Gravity; 26import android.view.ViewGroup; 27import android.view.ViewTreeObserver; 28import android.view.ViewTreeObserver.OnScrollChangedListener; 29import android.view.View.OnTouchListener; 30import android.graphics.PixelFormat; 31import android.graphics.Rect; 32import android.graphics.drawable.Drawable; 33import android.graphics.drawable.StateListDrawable; 34import android.os.IBinder; 35import android.content.Context; 36import android.content.res.TypedArray; 37import android.util.AttributeSet; 38 39import java.lang.ref.WeakReference; 40 41/** 42 * <p>A popup window that can be used to display an arbitrary view. The popup 43 * windows is a floating container that appears on top of the current 44 * activity.</p> 45 * 46 * @see android.widget.AutoCompleteTextView 47 * @see android.widget.Spinner 48 */ 49public class PopupWindow { 50 /** 51 * Mode for {@link #setInputMethodMode(int): the requirements for the 52 * input method should be based on the focusability of the popup. That is 53 * if it is focusable than it needs to work with the input method, else 54 * it doesn't. 55 */ 56 public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; 57 58 /** 59 * Mode for {@link #setInputMethodMode(int): this popup always needs to 60 * work with an input method, regardless of whether it is focusable. This 61 * means that it will always be displayed so that the user can also operate 62 * the input method while it is shown. 63 */ 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 final Context mContext; 76 private final 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 boolean mTouchable = true; 86 private boolean mOutsideTouchable = false; 87 private boolean mClippingEnabled = true; 88 89 private OnTouchListener mTouchInterceptor; 90 91 private int mWidthMode; 92 private int mWidth; 93 private int mLastWidth; 94 private int mHeightMode; 95 private int mHeight; 96 private int mLastHeight; 97 98 private int mPopupWidth; 99 private int mPopupHeight; 100 101 private int[] mDrawingLocation = new int[2]; 102 private int[] mScreenLocation = new int[2]; 103 private Rect mTempRect = new Rect(); 104 105 private Drawable mBackground; 106 private Drawable mAboveAnchorBackgroundDrawable; 107 private Drawable mBelowAnchorBackgroundDrawable; 108 109 private boolean mAboveAnchor; 110 111 private OnDismissListener mOnDismissListener; 112 private boolean mIgnoreCheekPress = false; 113 114 private int mAnimationStyle = -1; 115 116 private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] { 117 com.android.internal.R.attr.state_above_anchor 118 }; 119 120 private WeakReference<View> mAnchor; 121 private OnScrollChangedListener mOnScrollChangedListener = 122 new OnScrollChangedListener() { 123 public void onScrollChanged() { 124 View anchor = mAnchor.get(); 125 if (anchor != null && mPopupView != null) { 126 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 127 mPopupView.getLayoutParams(); 128 129 mAboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff); 130 update(p.x, p.y, -1, -1, true); 131 } 132 } 133 }; 134 private int mAnchorXoff, mAnchorYoff; 135 136 /** 137 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 138 * 139 * <p>The popup does provide a background.</p> 140 */ 141 public PopupWindow(Context context) { 142 this(context, null); 143 } 144 145 /** 146 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 147 * 148 * <p>The popup does provide a background.</p> 149 */ 150 public PopupWindow(Context context, AttributeSet attrs) { 151 this(context, attrs, com.android.internal.R.attr.popupWindowStyle); 152 } 153 154 /** 155 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 156 * 157 * <p>The popup does provide a background.</p> 158 */ 159 public PopupWindow(Context context, AttributeSet attrs, int defStyle) { 160 mContext = context; 161 mWindowManager = (WindowManager)context.getSystemService( 162 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, 276 boolean focusable) { 277 mContext = contentView.getContext(); 278 mWindowManager = (WindowManager)mContext.getSystemService( 279 Context.WINDOW_SERVICE); 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 378 /** 379 * Set a callback for all touch events being dispatched to the popup 380 * window. 381 */ 382 public void setTouchInterceptor(OnTouchListener l) { 383 mTouchInterceptor = l; 384 } 385 386 /** 387 * <p>Indicate whether the popup window can grab the focus.</p> 388 * 389 * @return true if the popup is focusable, false otherwise 390 * 391 * @see #setFocusable(boolean) 392 */ 393 public boolean isFocusable() { 394 return mFocusable; 395 } 396 397 /** 398 * <p>Changes the focusability of the popup window. When focusable, the 399 * window will grab the focus from the current focused widget if the popup 400 * contains a focusable {@link android.view.View}. By default a popup 401 * window is not focusable.</p> 402 * 403 * <p>If the popup is showing, calling this method will take effect only 404 * the next time the popup is shown or through a manual call to one of 405 * the {@link #update()} methods.</p> 406 * 407 * @param focusable true if the popup should grab focus, false otherwise. 408 * 409 * @see #isFocusable() 410 * @see #isShowing() 411 * @see #update() 412 */ 413 public void setFocusable(boolean focusable) { 414 mFocusable = focusable; 415 } 416 417 /** 418 * Return the current value in {@link #setInputMethodMode(int)}. 419 * 420 * @see #setInputMethodMode(int) 421 */ 422 public int getInputMethodMode() { 423 return mInputMethodMode; 424 425 } 426 427 /** 428 * Control how the popup operates with an input method: one of 429 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, 430 * or {@link #INPUT_METHOD_NOT_NEEDED}. 431 * 432 * <p>If the popup is showing, calling this method will take effect only 433 * the next time the popup is shown or through a manual call to one of 434 * the {@link #update()} methods.</p> 435 * 436 * @see #getInputMethodMode() 437 * @see #update() 438 */ 439 public void setInputMethodMode(int mode) { 440 mInputMethodMode = mode; 441 } 442 443 /** 444 * <p>Indicates whether the popup window receives touch events.</p> 445 * 446 * @return true if the popup is touchable, false otherwise 447 * 448 * @see #setTouchable(boolean) 449 */ 450 public boolean isTouchable() { 451 return mTouchable; 452 } 453 454 /** 455 * <p>Changes the touchability of the popup window. When touchable, the 456 * window will receive touch events, otherwise touch events will go to the 457 * window below it. By default the window is touchable.</p> 458 * 459 * <p>If the popup is showing, calling this method will take effect only 460 * the next time the popup is shown or through a manual call to one of 461 * the {@link #update()} methods.</p> 462 * 463 * @param touchable true if the popup should receive touch events, false otherwise 464 * 465 * @see #isTouchable() 466 * @see #isShowing() 467 * @see #update() 468 */ 469 public void setTouchable(boolean touchable) { 470 mTouchable = touchable; 471 } 472 473 /** 474 * <p>Indicates whether the popup window will be informed of touch events 475 * outside of its window.</p> 476 * 477 * @return true if the popup is outside touchable, false otherwise 478 * 479 * @see #setOutsideTouchable(boolean) 480 */ 481 public boolean isOutsideTouchable() { 482 return mOutsideTouchable; 483 } 484 485 /** 486 * <p>Controls whether the pop-up will be informed of touch events outside 487 * of its window. This only makes sense for pop-ups that are touchable 488 * but not focusable, which means touches outside of the window will 489 * be delivered to the window behind. The default is false.</p> 490 * 491 * <p>If the popup is showing, calling this method will take effect only 492 * the next time the popup is shown or through a manual call to one of 493 * the {@link #update()} methods.</p> 494 * 495 * @param touchable true if the popup should receive outside 496 * touch events, false otherwise 497 * 498 * @see #isOutsideTouchable() 499 * @see #isShowing() 500 * @see #update() 501 */ 502 public void setOutsideTouchable(boolean touchable) { 503 mOutsideTouchable = touchable; 504 } 505 506 /** 507 * <p>Indicates whether clipping of the popup window is enabled.</p> 508 * 509 * @return true if the clipping is enabled, false otherwise 510 * 511 * @see #setClippingEnabled(boolean) 512 */ 513 public boolean isClippingEnabled() { 514 return mClippingEnabled; 515 } 516 517 /** 518 * <p>Allows the popup window to extend beyond the bounds of the screen. By default the 519 * window is clipped to the screen boundaries. Setting this to false will allow windows to be 520 * accurately positioned.</p> 521 * 522 * <p>If the popup is showing, calling this method will take effect only 523 * the next time the popup is shown or through a manual call to one of 524 * the {@link #update()} methods.</p> 525 * 526 * @param enabled false if the window should be allowed to extend outside of the screen 527 * @see #isShowing() 528 * @see #isClippingEnabled() 529 * @see #update() 530 */ 531 public void setClippingEnabled(boolean enabled) { 532 mClippingEnabled = enabled; 533 } 534 535 /** 536 * <p>Change the width and height measure specs that are given to the 537 * window manager by the popup. By default these are 0, meaning that 538 * the current width or height is requested as an explicit size from 539 * the window manager. You can supply 540 * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or 541 * {@link ViewGroup.LayoutParams#FILL_PARENT} to have that measure 542 * spec supplied instead, replacing the absolute width and height that 543 * has been set in the popup.</p> 544 * 545 * <p>If the popup is showing, calling this method will take effect only 546 * the next time the popup is shown.</p> 547 * 548 * @param widthSpec an explicit width measure spec mode, either 549 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 550 * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute 551 * width. 552 * @param heightSpec an explicit height measure spec mode, either 553 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 554 * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute 555 * height. 556 */ 557 public void setWindowLayoutMode(int widthSpec, int heightSpec) { 558 mWidthMode = widthSpec; 559 mHeightMode = heightSpec; 560 } 561 562 /** 563 * <p>Return this popup's height MeasureSpec</p> 564 * 565 * @return the height MeasureSpec of the popup 566 * 567 * @see #setHeight(int) 568 */ 569 public int getHeight() { 570 return mHeight; 571 } 572 573 /** 574 * <p>Change the popup's height MeasureSpec</p> 575 * 576 * <p>If the popup is showing, calling this method will take effect only 577 * the next time the popup is shown.</p> 578 * 579 * @param height the height MeasureSpec of the popup 580 * 581 * @see #getHeight() 582 * @see #isShowing() 583 */ 584 public void setHeight(int height) { 585 mHeight = height; 586 } 587 588 /** 589 * <p>Return this popup's width MeasureSpec</p> 590 * 591 * @return the width MeasureSpec of the popup 592 * 593 * @see #setWidth(int) 594 */ 595 public int getWidth() { 596 return mWidth; 597 } 598 599 /** 600 * <p>Change the popup's width MeasureSpec</p> 601 * 602 * <p>If the popup is showing, calling this method will take effect only 603 * the next time the popup is shown.</p> 604 * 605 * @param width the width MeasureSpec of the popup 606 * 607 * @see #getWidth() 608 * @see #isShowing() 609 */ 610 public void setWidth(int width) { 611 mWidth = width; 612 } 613 614 /** 615 * <p>Indicate whether this popup window is showing on screen.</p> 616 * 617 * @return true if the popup is showing, false otherwise 618 */ 619 public boolean isShowing() { 620 return mIsShowing; 621 } 622 623 /** 624 * <p> 625 * Display the content view in a popup window at the specified location. If the popup window 626 * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams} 627 * for more information on how gravity and the x and y parameters are related. Specifying 628 * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying 629 * <code>Gravity.LEFT | Gravity.TOP</code>. 630 * </p> 631 * 632 * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from 633 * @param gravity the gravity which controls the placement of the popup window 634 * @param x the popup's x location offset 635 * @param y the popup's y location offset 636 */ 637 public void showAtLocation(View parent, int gravity, int x, int y) { 638 if (isShowing() || mContentView == null) { 639 return; 640 } 641 642 unregisterForScrollChanged(); 643 644 mIsShowing = true; 645 mIsDropdown = false; 646 647 WindowManager.LayoutParams p = createPopupLayout(parent.getWindowToken()); 648 p.windowAnimations = computeAnimationResource(); 649 650 preparePopup(p); 651 if (gravity == Gravity.NO_GRAVITY) { 652 gravity = Gravity.TOP | Gravity.LEFT; 653 } 654 p.gravity = gravity; 655 p.x = x; 656 p.y = y; 657 invokePopup(p); 658 } 659 660 /** 661 * <p>Display the content view in a popup window anchored to the bottom-left 662 * corner of the anchor view. If there is not enough room on screen to show 663 * the popup in its entirety, this method tries to find a parent scroll 664 * view to scroll. If no parent scroll view can be scrolled, the bottom-left 665 * corner of the popup is pinned at the top left corner of the anchor view.</p> 666 * 667 * @param anchor the view on which to pin the popup window 668 * 669 * @see #dismiss() 670 */ 671 public void showAsDropDown(View anchor) { 672 showAsDropDown(anchor, 0, 0); 673 } 674 675 /** 676 * <p>Display the content view in a popup window anchored to the bottom-left 677 * corner of the anchor view offset by the specified x and y coordinates. 678 * If there is not enough room on screen to show 679 * the popup in its entirety, this method tries to find a parent scroll 680 * view to scroll. If no parent scroll view can be scrolled, the bottom-left 681 * corner of the popup is pinned at the top left corner of the anchor view.</p> 682 * <p>If the view later scrolls to move <code>anchor</code> to a different 683 * location, the popup will be moved correspondingly.</p> 684 * 685 * @param anchor the view on which to pin the popup window 686 * 687 * @see #dismiss() 688 */ 689 public void showAsDropDown(View anchor, int xoff, int yoff) { 690 if (isShowing() || mContentView == null) { 691 return; 692 } 693 694 registerForScrollChanged(anchor, xoff, yoff); 695 696 mIsShowing = true; 697 mIsDropdown = true; 698 699 WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken()); 700 preparePopup(p); 701 mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff); 702 703 if (mBackground != null) { 704 // If the background drawable provided was a StateListDrawable with above-anchor 705 // and below-anchor states, use those. Otherwise rely on refreshDrawableState to 706 // do the job. 707 if (mAboveAnchorBackgroundDrawable != null) { 708 if (mAboveAnchor) { 709 mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable); 710 } else { 711 mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable); 712 } 713 } else { 714 mPopupView.refreshDrawableState(); 715 } 716 } 717 718 if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; 719 if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; 720 721 p.windowAnimations = computeAnimationResource(); 722 723 invokePopup(p); 724 } 725 726 /** 727 * Indicates whether the popup is showing above (the y coordinate of the popup's bottom 728 * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate 729 * of the popup is greater than y coordinate of the anchor's bottom). 730 * 731 * The value returned 732 * by this method is meaningful only after {@link #showAsDropDown(android.view.View)} 733 * or {@link #showAsDropDown(android.view.View, int, int)} was invoked. 734 * 735 * @return True if this popup is showing above the anchor view, false otherwise. 736 */ 737 public boolean isAboveAnchor() { 738 return mAboveAnchor; 739 } 740 741 /** 742 * <p>Prepare the popup by embedding in into a new ViewGroup if the 743 * background drawable is not null. If embedding is required, the layout 744 * parameters' height is mnodified to take into account the background's 745 * padding.</p> 746 * 747 * @param p the layout parameters of the popup's content view 748 */ 749 private void preparePopup(WindowManager.LayoutParams p) { 750 if (mBackground != null) { 751 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 752 int height = ViewGroup.LayoutParams.FILL_PARENT; 753 if (layoutParams != null && 754 layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { 755 height = ViewGroup.LayoutParams.WRAP_CONTENT; 756 } 757 758 // when a background is available, we embed the content view 759 // within another view that owns the background drawable 760 PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); 761 PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( 762 ViewGroup.LayoutParams.FILL_PARENT, height 763 ); 764 popupViewContainer.setBackgroundDrawable(mBackground); 765 popupViewContainer.addView(mContentView, listParams); 766 767 mPopupView = popupViewContainer; 768 } else { 769 mPopupView = mContentView; 770 } 771 mPopupWidth = p.width; 772 mPopupHeight = p.height; 773 } 774 775 /** 776 * <p>Invoke the popup window by adding the content view to the window 777 * manager.</p> 778 * 779 * <p>The content view must be non-null when this method is invoked.</p> 780 * 781 * @param p the layout parameters of the popup's content view 782 */ 783 private void invokePopup(WindowManager.LayoutParams p) { 784 mWindowManager.addView(mPopupView, p); 785 } 786 787 /** 788 * <p>Generate the layout parameters for the popup window.</p> 789 * 790 * @param token the window token used to bind the popup's window 791 * 792 * @return the layout parameters to pass to the window manager 793 */ 794 private WindowManager.LayoutParams createPopupLayout(IBinder token) { 795 // generates the layout parameters for the drop down 796 // we want a fixed size view located at the bottom left of the anchor 797 WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 798 // these gravity settings put the view at the top left corner of the 799 // screen. The view is then positioned to the appropriate location 800 // by setting the x and y offsets to match the anchor's bottom 801 // left corner 802 p.gravity = Gravity.LEFT | Gravity.TOP; 803 p.width = mLastWidth = mWidth; 804 p.height = mLastHeight = mHeight; 805 if (mBackground != null) { 806 p.format = mBackground.getOpacity(); 807 } else { 808 p.format = PixelFormat.TRANSLUCENT; 809 } 810 p.flags = computeFlags(p.flags); 811 p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 812 p.token = token; 813 p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); 814 815 return p; 816 } 817 818 private int computeFlags(int curFlags) { 819 curFlags &= ~( 820 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | 821 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 822 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 823 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 824 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 825 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 826 if(mIgnoreCheekPress) { 827 curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 828 } 829 if (!mFocusable) { 830 curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 831 if (mInputMethodMode == INPUT_METHOD_NEEDED) { 832 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 833 } 834 } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { 835 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 836 } 837 if (!mTouchable) { 838 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 839 } 840 if (mTouchable) { 841 curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 842 } 843 if (!mClippingEnabled) { 844 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 845 } 846 return curFlags; 847 } 848 849 private int computeAnimationResource() { 850 if (mAnimationStyle == -1) { 851 if (mIsDropdown) { 852 return mAboveAnchor 853 ? com.android.internal.R.style.Animation_DropDownUp 854 : com.android.internal.R.style.Animation_DropDownDown; 855 } 856 return 0; 857 } 858 return mAnimationStyle; 859 } 860 861 /** 862 * <p>Positions the popup window on screen. When the popup window is too 863 * tall to fit under the anchor, a parent scroll view is seeked and scrolled 864 * up to reclaim space. If scrolling is not possible or not enough, the 865 * popup window gets moved on top of the anchor.</p> 866 * 867 * <p>The height must have been set on the layout parameters prior to 868 * calling this method.</p> 869 * 870 * @param anchor the view on which the popup window must be anchored 871 * @param p the layout parameters used to display the drop down 872 * 873 * @return true if the popup is translated upwards to fit on screen 874 */ 875 private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, 876 int xoff, int yoff) { 877 878 anchor.getLocationInWindow(mDrawingLocation); 879 p.x = mDrawingLocation[0] + xoff; 880 p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff; 881 882 boolean onTop = false; 883 884 p.gravity = Gravity.LEFT | Gravity.TOP; 885 886 anchor.getLocationOnScreen(mScreenLocation); 887 final Rect displayFrame = new Rect(); 888 anchor.getWindowVisibleDisplayFrame(displayFrame); 889 890 final View root = anchor.getRootView(); 891 if (p.y + mPopupHeight > displayFrame.bottom || p.x + mPopupWidth - root.getWidth() > 0) { 892 // if the drop down disappears at the bottom of the screen. we try to 893 // scroll a parent scrollview or move the drop down back up on top of 894 // the edit box 895 int scrollX = anchor.getScrollX(); 896 int scrollY = anchor.getScrollY(); 897 Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth, 898 scrollY + mPopupHeight + anchor.getMeasuredHeight()); 899 anchor.requestRectangleOnScreen(r, true); 900 901 // now we re-evaluate the space available, and decide from that 902 // whether the pop-up will go above or below the anchor. 903 anchor.getLocationInWindow(mDrawingLocation); 904 p.x = mDrawingLocation[0] + xoff; 905 p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff; 906 907 // determine whether there is more space above or below the anchor 908 anchor.getLocationOnScreen(mScreenLocation); 909 910 onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getMeasuredHeight() - yoff) < 911 (mScreenLocation[1] - yoff - displayFrame.top); 912 if (onTop) { 913 p.gravity = Gravity.LEFT | Gravity.BOTTOM; 914 p.y = root.getHeight() - mDrawingLocation[1] + yoff; 915 } else { 916 p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff; 917 } 918 } 919 920 p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; 921 922 return onTop; 923 } 924 925 /** 926 * Returns the maximum height that is available for the popup to be 927 * completely shown. It is recommended that this height be the maximum for 928 * the popup's height, otherwise it is possible that the popup will be 929 * clipped. 930 * 931 * @param anchor The view on which the popup window must be anchored. 932 * @return The maximum available height for the popup to be completely 933 * shown. 934 */ 935 public int getMaxAvailableHeight(View anchor) { 936 return getMaxAvailableHeight(anchor, 0); 937 } 938 939 /** 940 * Returns the maximum height that is available for the popup to be 941 * completely shown. It is recommended that this height be the maximum for 942 * the popup's height, otherwise it is possible that the popup will be 943 * clipped. 944 * 945 * @param anchor The view on which the popup window must be anchored. 946 * @param yOffset y offset from the view's bottom edge 947 * @return The maximum available height for the popup to be completely 948 * shown. 949 */ 950 public int getMaxAvailableHeight(View anchor, int yOffset) { 951 final Rect displayFrame = new Rect(); 952 anchor.getWindowVisibleDisplayFrame(displayFrame); 953 954 final int[] anchorPos = mDrawingLocation; 955 anchor.getLocationOnScreen(anchorPos); 956 957 final int distanceToBottom = displayFrame.bottom - 958 (anchorPos[1] + anchor.getHeight()) - yOffset; 959 final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; 960 961 // anchorPos[1] is distance from anchor to top of screen 962 int returnedHeight = Math.max(distanceToBottom, distanceToTop); 963 if (mBackground != null) { 964 mBackground.getPadding(mTempRect); 965 returnedHeight -= mTempRect.top + mTempRect.bottom; 966 } 967 968 return returnedHeight; 969 } 970 971 /** 972 * <p>Dispose of the popup window. This method can be invoked only after 973 * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling 974 * this method will have no effect.</p> 975 * 976 * @see #showAsDropDown(android.view.View) 977 */ 978 public void dismiss() { 979 if (isShowing() && mPopupView != null) { 980 unregisterForScrollChanged(); 981 982 mWindowManager.removeView(mPopupView); 983 if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { 984 ((ViewGroup) mPopupView).removeView(mContentView); 985 } 986 mPopupView = null; 987 mIsShowing = false; 988 989 if (mOnDismissListener != null) { 990 mOnDismissListener.onDismiss(); 991 } 992 } 993 } 994 995 /** 996 * Sets the listener to be called when the window is dismissed. 997 * 998 * @param onDismissListener The listener. 999 */ 1000 public void setOnDismissListener(OnDismissListener onDismissListener) { 1001 mOnDismissListener = onDismissListener; 1002 } 1003 1004 /** 1005 * Updates the state of the popup window, if it is currently being displayed, 1006 * from the currently set state. This include: 1007 * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)}, 1008 * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)}, 1009 * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}. 1010 */ 1011 public void update() { 1012 if (!isShowing() || mContentView == null) { 1013 return; 1014 } 1015 1016 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1017 mPopupView.getLayoutParams(); 1018 1019 boolean update = false; 1020 1021 final int newAnim = computeAnimationResource(); 1022 if (newAnim != p.windowAnimations) { 1023 p.windowAnimations = newAnim; 1024 update = true; 1025 } 1026 1027 final int newFlags = computeFlags(p.flags); 1028 if (newFlags != p.flags) { 1029 p.flags = newFlags; 1030 update = true; 1031 } 1032 1033 if (update) { 1034 mWindowManager.updateViewLayout(mPopupView, p); 1035 } 1036 } 1037 1038 /** 1039 * <p>Updates the position and the dimension of the popup window. Width and 1040 * height can be set to -1 to update location only. Calling this function 1041 * also updates the window with the current popup state as 1042 * described for {@link #update()}.</p> 1043 * 1044 * @param x the new x location 1045 * @param y the new y location 1046 * @param width the new width, can be -1 to ignore 1047 * @param height the new height, can be -1 to ignore 1048 */ 1049 public void update(int x, int y, int width, int height) { 1050 update(x, y, width, height, false); 1051 } 1052 1053 /** 1054 * <p>Updates the position and the dimension of the popup window. Width and 1055 * height can be set to -1 to update location only. Calling this function 1056 * also updates the window with the current popup state as 1057 * described for {@link #update()}.</p> 1058 * 1059 * @param x the new x location 1060 * @param y the new y location 1061 * @param width the new width, can be -1 to ignore 1062 * @param height the new height, can be -1 to ignore 1063 * @param force reposition the window even if the specified position 1064 * already seems to correspond to the LayoutParams 1065 * 1066 * @hide pending API council approval 1067 */ 1068 public void update(int x, int y, int width, int height, boolean force) { 1069 if (width != -1) { 1070 mLastWidth = width; 1071 setWidth(width); 1072 } 1073 1074 if (height != -1) { 1075 mLastHeight = height; 1076 setHeight(height); 1077 } 1078 1079 if (!isShowing() || mContentView == null) { 1080 return; 1081 } 1082 1083 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1084 mPopupView.getLayoutParams(); 1085 1086 boolean update = force; 1087 1088 final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; 1089 if (width != -1 && p.width != finalWidth) { 1090 p.width = mLastWidth = finalWidth; 1091 update = true; 1092 } 1093 1094 final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; 1095 if (height != -1 && p.height != finalHeight) { 1096 p.height = mLastHeight = finalHeight; 1097 update = true; 1098 } 1099 1100 if (p.x != x) { 1101 p.x = x; 1102 update = true; 1103 } 1104 1105 if (p.y != y) { 1106 p.y = y; 1107 update = true; 1108 } 1109 1110 final int newAnim = computeAnimationResource(); 1111 if (newAnim != p.windowAnimations) { 1112 p.windowAnimations = newAnim; 1113 update = true; 1114 } 1115 1116 final int newFlags = computeFlags(p.flags); 1117 if (newFlags != p.flags) { 1118 p.flags = newFlags; 1119 update = true; 1120 } 1121 1122 if (update) { 1123 mWindowManager.updateViewLayout(mPopupView, p); 1124 } 1125 } 1126 1127 /** 1128 * <p>Updates the position and the dimension of the popup window. Width and 1129 * height can be set to -1 to update location only. Calling this function 1130 * also updates the window with the current popup state as 1131 * described for {@link #update()}.</p> 1132 * 1133 * @param anchor the popup's anchor view 1134 * @param width the new width, can be -1 to ignore 1135 * @param height the new height, can be -1 to ignore 1136 */ 1137 public void update(View anchor, int width, int height) { 1138 update(anchor, 0, 0, width, height); 1139 } 1140 1141 /** 1142 * <p>Updates the position and the dimension of the popup window. Width and 1143 * height can be set to -1 to update location only. Calling this function 1144 * also updates the window with the current popup state as 1145 * described for {@link #update()}.</p> 1146 * <p>If the view later scrolls to move <code>anchor</code> to a different 1147 * location, the popup will be moved correspondingly.</p> 1148 * 1149 * @param anchor the popup's anchor view 1150 * @param xoff x offset from the view's left edge 1151 * @param yoff y offset from the view's bottom edge 1152 * @param width the new width, can be -1 to ignore 1153 * @param height the new height, can be -1 to ignore 1154 */ 1155 public void update(View anchor, int xoff, int yoff, int width, int height) { 1156 if (!isShowing() || mContentView == null) { 1157 return; 1158 } 1159 1160 WeakReference<View> oldAnchor = mAnchor; 1161 if (oldAnchor == null || oldAnchor.get() != anchor || 1162 mAnchorXoff != xoff || mAnchorYoff != yoff) { 1163 registerForScrollChanged(anchor, xoff, yoff); 1164 } 1165 1166 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1167 mPopupView.getLayoutParams(); 1168 1169 if (width == -1) { 1170 width = mPopupWidth; 1171 } else { 1172 mPopupWidth = width; 1173 } 1174 if (height == -1) { 1175 height = mPopupHeight; 1176 } else { 1177 mPopupHeight = height; 1178 } 1179 1180 mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff); 1181 update(p.x, p.y, width, height); 1182 } 1183 1184 /** 1185 * Listener that is called when this popup window is dismissed. 1186 */ 1187 public interface OnDismissListener { 1188 /** 1189 * Called when this popup window is dismissed. 1190 */ 1191 public void onDismiss(); 1192 } 1193 1194 private void unregisterForScrollChanged() { 1195 WeakReference<View> anchorRef = mAnchor; 1196 View anchor = null; 1197 if (anchorRef != null) { 1198 anchor = anchorRef.get(); 1199 } 1200 if (anchor != null) { 1201 ViewTreeObserver vto = anchor.getViewTreeObserver(); 1202 vto.removeOnScrollChangedListener(mOnScrollChangedListener); 1203 } 1204 mAnchor = null; 1205 } 1206 1207 private void registerForScrollChanged(View anchor, int xoff, int yoff) { 1208 unregisterForScrollChanged(); 1209 1210 mAnchor = new WeakReference<View>(anchor); 1211 ViewTreeObserver vto = anchor.getViewTreeObserver(); 1212 if (vto != null) { 1213 vto.addOnScrollChangedListener(mOnScrollChangedListener); 1214 } 1215 1216 mAnchorXoff = xoff; 1217 mAnchorYoff = yoff; 1218 } 1219 1220 private class PopupViewContainer extends FrameLayout { 1221 1222 public PopupViewContainer(Context context) { 1223 super(context); 1224 } 1225 1226 @Override 1227 protected int[] onCreateDrawableState(int extraSpace) { 1228 if (mAboveAnchor) { 1229 // 1 more needed for the above anchor state 1230 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 1231 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); 1232 return drawableState; 1233 } else { 1234 return super.onCreateDrawableState(extraSpace); 1235 } 1236 } 1237 1238 @Override 1239 public boolean dispatchKeyEvent(KeyEvent event) { 1240 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1241 dismiss(); 1242 return true; 1243 } else { 1244 return super.dispatchKeyEvent(event); 1245 } 1246 } 1247 1248 @Override 1249 public boolean dispatchTouchEvent(MotionEvent ev) { 1250 if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { 1251 return true; 1252 } 1253 return super.dispatchTouchEvent(ev); 1254 } 1255 1256 @Override 1257 public boolean onTouchEvent(MotionEvent event) { 1258 final int x = (int) event.getX(); 1259 final int y = (int) event.getY(); 1260 1261 if ((event.getAction() == MotionEvent.ACTION_DOWN) 1262 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 1263 dismiss(); 1264 return true; 1265 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 1266 dismiss(); 1267 return true; 1268 } else { 1269 return super.onTouchEvent(event); 1270 } 1271 } 1272 1273 } 1274 1275} 1276