PopupWindow.java revision 7299807d1895ea25cbe45d32b6edfd9a5723ee7a
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.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 827 p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); 828 829 return p; 830 } 831 832 private int computeFlags(int curFlags) { 833 curFlags &= ~( 834 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | 835 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 836 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 837 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 838 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 839 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 840 if(mIgnoreCheekPress) { 841 curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 842 } 843 if (!mFocusable) { 844 curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 845 if (mInputMethodMode == INPUT_METHOD_NEEDED) { 846 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 847 } 848 } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { 849 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 850 } 851 if (!mTouchable) { 852 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 853 } 854 if (mOutsideTouchable) { 855 curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 856 } 857 if (!mClippingEnabled) { 858 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 859 } 860 return curFlags; 861 } 862 863 private int computeAnimationResource() { 864 if (mAnimationStyle == -1) { 865 if (mIsDropdown) { 866 return mAboveAnchor 867 ? com.android.internal.R.style.Animation_DropDownUp 868 : com.android.internal.R.style.Animation_DropDownDown; 869 } 870 return 0; 871 } 872 return mAnimationStyle; 873 } 874 875 /** 876 * <p>Positions the popup window on screen. When the popup window is too 877 * tall to fit under the anchor, a parent scroll view is seeked and scrolled 878 * up to reclaim space. If scrolling is not possible or not enough, the 879 * popup window gets moved on top of the anchor.</p> 880 * 881 * <p>The height must have been set on the layout parameters prior to 882 * calling this method.</p> 883 * 884 * @param anchor the view on which the popup window must be anchored 885 * @param p the layout parameters used to display the drop down 886 * 887 * @return true if the popup is translated upwards to fit on screen 888 */ 889 private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, 890 int xoff, int yoff) { 891 892 anchor.getLocationInWindow(mDrawingLocation); 893 p.x = mDrawingLocation[0] + xoff; 894 p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff; 895 896 boolean onTop = false; 897 898 p.gravity = Gravity.LEFT | Gravity.TOP; 899 900 anchor.getLocationOnScreen(mScreenLocation); 901 final Rect displayFrame = new Rect(); 902 anchor.getWindowVisibleDisplayFrame(displayFrame); 903 904 final View root = anchor.getRootView(); 905 if (p.y + mPopupHeight > displayFrame.bottom || p.x + mPopupWidth - root.getWidth() > 0) { 906 // if the drop down disappears at the bottom of the screen. we try to 907 // scroll a parent scrollview or move the drop down back up on top of 908 // the edit box 909 int scrollX = anchor.getScrollX(); 910 int scrollY = anchor.getScrollY(); 911 Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth, 912 scrollY + mPopupHeight + anchor.getMeasuredHeight()); 913 anchor.requestRectangleOnScreen(r, true); 914 915 // now we re-evaluate the space available, and decide from that 916 // whether the pop-up will go above or below the anchor. 917 anchor.getLocationInWindow(mDrawingLocation); 918 p.x = mDrawingLocation[0] + xoff; 919 p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff; 920 921 // determine whether there is more space above or below the anchor 922 anchor.getLocationOnScreen(mScreenLocation); 923 924 onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getMeasuredHeight() - yoff) < 925 (mScreenLocation[1] - yoff - displayFrame.top); 926 if (onTop) { 927 p.gravity = Gravity.LEFT | Gravity.BOTTOM; 928 p.y = root.getHeight() - mDrawingLocation[1] + yoff; 929 } else { 930 p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff; 931 } 932 } 933 934 p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; 935 936 return onTop; 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 * @return The maximum available height for the popup to be completely 947 * shown. 948 */ 949 public int getMaxAvailableHeight(View anchor) { 950 return getMaxAvailableHeight(anchor, 0); 951 } 952 953 /** 954 * Returns the maximum height that is available for the popup to be 955 * completely shown. It is recommended that this height be the maximum for 956 * the popup's height, otherwise it is possible that the popup will be 957 * clipped. 958 * 959 * @param anchor The view on which the popup window must be anchored. 960 * @param yOffset y offset from the view's bottom edge 961 * @return The maximum available height for the popup to be completely 962 * shown. 963 */ 964 public int getMaxAvailableHeight(View anchor, int yOffset) { 965 return getMaxAvailableHeight(anchor, yOffset, false); 966 } 967 968 /** 969 * Returns the maximum height that is available for the popup to be 970 * completely shown, optionally ignoring any bottom decorations such as 971 * the input method. It is recommended that this height be the maximum for 972 * the popup's height, otherwise it is possible that the popup will be 973 * clipped. 974 * 975 * @param anchor The view on which the popup window must be anchored. 976 * @param yOffset y offset from the view's bottom edge 977 * @param ignoreBottomDecorations if true, the height returned will be 978 * all the way to the bottom of the display, ignoring any 979 * bottom decorations 980 * @return The maximum available height for the popup to be completely 981 * shown. 982 * 983 * @hide Pending API council approval. 984 */ 985 public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) { 986 final Rect displayFrame = new Rect(); 987 anchor.getWindowVisibleDisplayFrame(displayFrame); 988 989 final int[] anchorPos = mDrawingLocation; 990 anchor.getLocationOnScreen(anchorPos); 991 992 int bottomEdge = displayFrame.bottom; 993 if (ignoreBottomDecorations) { 994 bottomEdge = WindowManagerImpl.getDefault().getDefaultDisplay().getHeight(); 995 } 996 final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; 997 final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; 998 999 // anchorPos[1] is distance from anchor to top of screen 1000 int returnedHeight = Math.max(distanceToBottom, distanceToTop); 1001 if (mBackground != null) { 1002 mBackground.getPadding(mTempRect); 1003 returnedHeight -= mTempRect.top + mTempRect.bottom; 1004 } 1005 1006 return returnedHeight; 1007 } 1008 1009 /** 1010 * <p>Dispose of the popup window. This method can be invoked only after 1011 * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling 1012 * this method will have no effect.</p> 1013 * 1014 * @see #showAsDropDown(android.view.View) 1015 */ 1016 public void dismiss() { 1017 if (isShowing() && mPopupView != null) { 1018 unregisterForScrollChanged(); 1019 1020 mWindowManager.removeView(mPopupView); 1021 1022 if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { 1023 ((ViewGroup) mPopupView).removeView(mContentView); 1024 } 1025 mPopupView = null; 1026 mIsShowing = false; 1027 1028 if (mOnDismissListener != null) { 1029 mOnDismissListener.onDismiss(); 1030 } 1031 } 1032 } 1033 1034 /** 1035 * Sets the listener to be called when the window is dismissed. 1036 * 1037 * @param onDismissListener The listener. 1038 */ 1039 public void setOnDismissListener(OnDismissListener onDismissListener) { 1040 mOnDismissListener = onDismissListener; 1041 } 1042 1043 /** 1044 * Updates the state of the popup window, if it is currently being displayed, 1045 * from the currently set state. This include: 1046 * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)}, 1047 * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)}, 1048 * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}. 1049 */ 1050 public void update() { 1051 if (!isShowing() || mContentView == null) { 1052 return; 1053 } 1054 1055 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1056 mPopupView.getLayoutParams(); 1057 1058 boolean update = false; 1059 1060 final int newAnim = computeAnimationResource(); 1061 if (newAnim != p.windowAnimations) { 1062 p.windowAnimations = newAnim; 1063 update = true; 1064 } 1065 1066 final int newFlags = computeFlags(p.flags); 1067 if (newFlags != p.flags) { 1068 p.flags = newFlags; 1069 update = true; 1070 } 1071 1072 if (update) { 1073 mWindowManager.updateViewLayout(mPopupView, p); 1074 } 1075 } 1076 1077 /** 1078 * <p>Updates the dimension of the popup window. Calling this function 1079 * also updates the window with the current popup state as described 1080 * for {@link #update()}.</p> 1081 * 1082 * @param width the new width 1083 * @param height the new height 1084 */ 1085 public void update(int width, int height) { 1086 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1087 mPopupView.getLayoutParams(); 1088 update(p.x, p.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 */ 1102 public void update(int x, int y, int width, int height) { 1103 update(x, y, width, height, false); 1104 } 1105 1106 /** 1107 * <p>Updates the position and the dimension of the popup window. Width and 1108 * height can be set to -1 to update location only. Calling this function 1109 * also updates the window with the current popup state as 1110 * described for {@link #update()}.</p> 1111 * 1112 * @param x the new x location 1113 * @param y the new y location 1114 * @param width the new width, can be -1 to ignore 1115 * @param height the new height, can be -1 to ignore 1116 * @param force reposition the window even if the specified position 1117 * already seems to correspond to the LayoutParams 1118 */ 1119 public void update(int x, int y, int width, int height, boolean force) { 1120 if (width != -1) { 1121 mLastWidth = width; 1122 setWidth(width); 1123 } 1124 1125 if (height != -1) { 1126 mLastHeight = height; 1127 setHeight(height); 1128 } 1129 1130 if (!isShowing() || mContentView == null) { 1131 return; 1132 } 1133 1134 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1135 mPopupView.getLayoutParams(); 1136 1137 boolean update = force; 1138 1139 final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; 1140 if (width != -1 && p.width != finalWidth) { 1141 p.width = mLastWidth = finalWidth; 1142 update = true; 1143 } 1144 1145 final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; 1146 if (height != -1 && p.height != finalHeight) { 1147 p.height = mLastHeight = finalHeight; 1148 update = true; 1149 } 1150 1151 if (p.x != x) { 1152 p.x = x; 1153 update = true; 1154 } 1155 1156 if (p.y != y) { 1157 p.y = y; 1158 update = true; 1159 } 1160 1161 final int newAnim = computeAnimationResource(); 1162 if (newAnim != p.windowAnimations) { 1163 p.windowAnimations = newAnim; 1164 update = true; 1165 } 1166 1167 final int newFlags = computeFlags(p.flags); 1168 if (newFlags != p.flags) { 1169 p.flags = newFlags; 1170 update = true; 1171 } 1172 1173 if (update) { 1174 mWindowManager.updateViewLayout(mPopupView, p); 1175 } 1176 } 1177 1178 /** 1179 * <p>Updates the position and the dimension of the popup window. Calling this 1180 * function also updates the window with the current popup state as described 1181 * for {@link #update()}.</p> 1182 * 1183 * @param anchor the popup's anchor view 1184 * @param width the new width, can be -1 to ignore 1185 * @param height the new height, can be -1 to ignore 1186 */ 1187 public void update(View anchor, int width, int height) { 1188 update(anchor, false, 0, 0, true, width, height); 1189 } 1190 1191 /** 1192 * <p>Updates the position and the dimension of the popup window. Width and 1193 * height can be set to -1 to update location only. Calling this function 1194 * also updates the window with the current popup state as 1195 * described for {@link #update()}.</p> 1196 * <p>If the view later scrolls to move <code>anchor</code> to a different 1197 * location, the popup will be moved correspondingly.</p> 1198 * 1199 * @param anchor the popup's anchor view 1200 * @param xoff x offset from the view's left edge 1201 * @param yoff y offset from the view's bottom edge 1202 * @param width the new width, can be -1 to ignore 1203 * @param height the new height, can be -1 to ignore 1204 */ 1205 public void update(View anchor, int xoff, int yoff, int width, int height) { 1206 update(anchor, true, xoff, yoff, true, width, height); 1207 } 1208 1209 private void update(View anchor, boolean updateLocation, int xoff, int yoff, 1210 boolean updateDimension, int width, int height) { 1211 1212 if (!isShowing() || mContentView == null) { 1213 return; 1214 } 1215 1216 WeakReference<View> oldAnchor = mAnchor; 1217 if (oldAnchor == null || oldAnchor.get() != anchor || 1218 (updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff))) { 1219 registerForScrollChanged(anchor, xoff, yoff); 1220 } 1221 1222 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1223 mPopupView.getLayoutParams(); 1224 1225 if (updateDimension) { 1226 if (width == -1) { 1227 width = mPopupWidth; 1228 } else { 1229 mPopupWidth = width; 1230 } 1231 if (height == -1) { 1232 height = mPopupHeight; 1233 } else { 1234 mPopupHeight = height; 1235 } 1236 } 1237 1238 if (updateLocation) { 1239 mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff); 1240 } else { 1241 mAboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff); 1242 } 1243 1244 update(p.x, p.y, width, height); 1245 } 1246 1247 /** 1248 * Listener that is called when this popup window is dismissed. 1249 */ 1250 public interface OnDismissListener { 1251 /** 1252 * Called when this popup window is dismissed. 1253 */ 1254 public void onDismiss(); 1255 } 1256 1257 private void unregisterForScrollChanged() { 1258 WeakReference<View> anchorRef = mAnchor; 1259 View anchor = null; 1260 if (anchorRef != null) { 1261 anchor = anchorRef.get(); 1262 } 1263 if (anchor != null) { 1264 ViewTreeObserver vto = anchor.getViewTreeObserver(); 1265 vto.removeOnScrollChangedListener(mOnScrollChangedListener); 1266 } 1267 mAnchor = null; 1268 } 1269 1270 private void registerForScrollChanged(View anchor, int xoff, int yoff) { 1271 unregisterForScrollChanged(); 1272 1273 mAnchor = new WeakReference<View>(anchor); 1274 ViewTreeObserver vto = anchor.getViewTreeObserver(); 1275 if (vto != null) { 1276 vto.addOnScrollChangedListener(mOnScrollChangedListener); 1277 } 1278 1279 mAnchorXoff = xoff; 1280 mAnchorYoff = yoff; 1281 } 1282 1283 private class PopupViewContainer extends FrameLayout { 1284 1285 public PopupViewContainer(Context context) { 1286 super(context); 1287 } 1288 1289 @Override 1290 protected int[] onCreateDrawableState(int extraSpace) { 1291 if (mAboveAnchor) { 1292 // 1 more needed for the above anchor state 1293 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 1294 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); 1295 return drawableState; 1296 } else { 1297 return super.onCreateDrawableState(extraSpace); 1298 } 1299 } 1300 1301 @Override 1302 public boolean dispatchKeyEvent(KeyEvent event) { 1303 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1304 dismiss(); 1305 return true; 1306 } else { 1307 return super.dispatchKeyEvent(event); 1308 } 1309 } 1310 1311 @Override 1312 public boolean dispatchTouchEvent(MotionEvent ev) { 1313 if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { 1314 return true; 1315 } 1316 return super.dispatchTouchEvent(ev); 1317 } 1318 1319 @Override 1320 public boolean onTouchEvent(MotionEvent event) { 1321 final int x = (int) event.getX(); 1322 final int y = (int) event.getY(); 1323 1324 if ((event.getAction() == MotionEvent.ACTION_DOWN) 1325 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 1326 dismiss(); 1327 return true; 1328 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 1329 dismiss(); 1330 return true; 1331 } else { 1332 return super.onTouchEvent(event); 1333 } 1334 } 1335 1336 @Override 1337 public void sendAccessibilityEvent(int eventType) { 1338 // clinets are interested in the content not the container, make it event source 1339 if (mContentView != null) { 1340 mContentView.sendAccessibilityEvent(eventType); 1341 } else { 1342 super.sendAccessibilityEvent(eventType); 1343 } 1344 } 1345 } 1346 1347} 1348