PopupWindow.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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.View.OnTouchListener; 28import android.graphics.PixelFormat; 29import android.graphics.Rect; 30import android.graphics.drawable.Drawable; 31import android.os.IBinder; 32import android.content.Context; 33import android.content.res.TypedArray; 34import android.util.AttributeSet; 35 36/** 37 * <p>A popup window that can be used to display an arbitrary view. The popup 38 * windows is a floating container that appears on top of the current 39 * activity.</p> 40 * 41 * @see android.widget.AutoCompleteTextView 42 * @see android.widget.Spinner 43 */ 44public class PopupWindow { 45 /** 46 * Mode for {@link #setInputMethodMode(int): the requirements for the 47 * input method should be based on the focusability of the popup. That is 48 * if it is focusable than it needs to work with the input method, else 49 * it doesn't. 50 */ 51 public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; 52 53 /** 54 * Mode for {@link #setInputMethodMode(int): this popup always needs to 55 * work with an input method, regardless of whether it is focusable. This 56 * means that it will always be displayed so that the user can also operate 57 * the input method while it is shown. 58 */ 59 60 public static final int INPUT_METHOD_NEEDED = 1; 61 62 /** 63 * Mode for {@link #setInputMethodMode(int): this popup never needs to 64 * work with an input method, regardless of whether it is focusable. This 65 * means that it will always be displayed to use as much space on the 66 * screen as needed, regardless of whether this covers the input method. 67 */ 68 public static final int INPUT_METHOD_NOT_NEEDED = 2; 69 70 private final Context mContext; 71 private final WindowManager mWindowManager; 72 73 private boolean mIsShowing; 74 private boolean mIsDropdown; 75 76 private View mContentView; 77 private View mPopupView; 78 private boolean mFocusable; 79 private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; 80 private boolean mTouchable = true; 81 private boolean mOutsideTouchable = false; 82 private boolean mClippingEnabled = true; 83 84 private OnTouchListener mTouchInterceptor; 85 86 private int mWidthMode; 87 private int mWidth; 88 private int mHeightMode; 89 private int mHeight; 90 91 private int mPopupWidth; 92 private int mPopupHeight; 93 94 private int[] mDrawingLocation = new int[2]; 95 private int[] mScreenLocation = new int[2]; 96 private Rect mTempRect = new Rect(); 97 98 private Drawable mBackground; 99 100 private boolean mAboveAnchor; 101 102 private OnDismissListener mOnDismissListener; 103 private boolean mIgnoreCheekPress = false; 104 105 private int mAnimationStyle = -1; 106 107 private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] { 108 com.android.internal.R.attr.state_above_anchor 109 }; 110 111 /** 112 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 113 * 114 * <p>The popup does provide a background.</p> 115 */ 116 public PopupWindow(Context context) { 117 this(context, null); 118 } 119 120 /** 121 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 122 * 123 * <p>The popup does provide a background.</p> 124 */ 125 public PopupWindow(Context context, AttributeSet attrs) { 126 this(context, attrs, com.android.internal.R.attr.popupWindowStyle); 127 } 128 129 /** 130 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 131 * 132 * <p>The popup does provide a background.</p> 133 */ 134 public PopupWindow(Context context, AttributeSet attrs, int defStyle) { 135 mContext = context; 136 mWindowManager = (WindowManager)context.getSystemService( 137 Context.WINDOW_SERVICE); 138 139 TypedArray a = 140 context.obtainStyledAttributes( 141 attrs, com.android.internal.R.styleable.PopupWindow, defStyle, 0); 142 143 mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground); 144 145 a.recycle(); 146 } 147 148 /** 149 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 150 * 151 * <p>The popup does not provide any background. This should be handled 152 * by the content view.</p> 153 */ 154 public PopupWindow() { 155 this(null, 0, 0); 156 } 157 158 /** 159 * <p>Create a new non focusable popup window which can display the 160 * <tt>contentView</tt>. The dimension of the window are (0,0).</p> 161 * 162 * <p>The popup does not provide any background. This should be handled 163 * by the content view.</p> 164 * 165 * @param contentView the popup's content 166 */ 167 public PopupWindow(View contentView) { 168 this(contentView, 0, 0); 169 } 170 171 /** 172 * <p>Create a new empty, non focusable popup window. The dimension of the 173 * window must be passed to this constructor.</p> 174 * 175 * <p>The popup does not provide any background. This should be handled 176 * by the content view.</p> 177 * 178 * @param width the popup's width 179 * @param height the popup's height 180 */ 181 public PopupWindow(int width, int height) { 182 this(null, width, height); 183 } 184 185 /** 186 * <p>Create a new non focusable popup window which can display the 187 * <tt>contentView</tt>. The dimension of the window must be passed to 188 * this constructor.</p> 189 * 190 * <p>The popup does not provide any background. This should be handled 191 * by the content view.</p> 192 * 193 * @param contentView the popup's content 194 * @param width the popup's width 195 * @param height the popup's height 196 */ 197 public PopupWindow(View contentView, int width, int height) { 198 this(contentView, width, height, false); 199 } 200 201 /** 202 * <p>Create a new popup window which can display the <tt>contentView</tt>. 203 * The dimension of the window must be passed to this constructor.</p> 204 * 205 * <p>The popup does not provide any background. This should be handled 206 * by the content view.</p> 207 * 208 * @param contentView the popup's content 209 * @param width the popup's width 210 * @param height the popup's height 211 * @param focusable true if the popup can be focused, false otherwise 212 */ 213 public PopupWindow(View contentView, int width, int height, 214 boolean focusable) { 215 mContext = contentView.getContext(); 216 mWindowManager = (WindowManager)mContext.getSystemService( 217 Context.WINDOW_SERVICE); 218 setContentView(contentView); 219 setWidth(width); 220 setHeight(height); 221 setFocusable(focusable); 222 } 223 224 /** 225 * <p>Return the drawable used as the popup window's background.</p> 226 * 227 * @return the background drawable or null 228 */ 229 public Drawable getBackground() { 230 return mBackground; 231 } 232 233 /** 234 * <p>Change the background drawable for this popup window. The background 235 * can be set to null.</p> 236 * 237 * @param background the popup's background 238 */ 239 public void setBackgroundDrawable(Drawable background) { 240 mBackground = background; 241 } 242 243 /** 244 * <p>Return the animation style to use the popup appears and disappears</p> 245 * 246 * @return the animation style to use the popup appears and disappears 247 */ 248 public int getAnimationStyle() { 249 return mAnimationStyle; 250 } 251 252 /** 253 * Set the flag on popup to ignore cheek press eventt; by default this flag 254 * is set to false 255 * which means the pop wont ignore cheek press dispatch events. 256 * 257 * <p>If the popup is showing, calling this method will take effect only 258 * the next time the popup is shown or through a manual call to one of 259 * the {@link #update()} methods.</p> 260 * 261 * @see #update() 262 */ 263 public void setIgnoreCheekPress() { 264 mIgnoreCheekPress = true; 265 } 266 267 268 /** 269 * <p>Change the animation style resource for this popup.</p> 270 * 271 * <p>If the popup is showing, calling this method will take effect only 272 * the next time the popup is shown or through a manual call to one of 273 * the {@link #update()} methods.</p> 274 * 275 * @param animationStyle animation style to use when the popup appears 276 * and disappears. Set to -1 for the default animation, 0 for no 277 * animation, or a resource identifier for an explicit animation. 278 * 279 * @see #update() 280 */ 281 public void setAnimationStyle(int animationStyle) { 282 mAnimationStyle = animationStyle; 283 } 284 285 /** 286 * <p>Return the view used as the content of the popup window.</p> 287 * 288 * @return a {@link android.view.View} representing the popup's content 289 * 290 * @see #setContentView(android.view.View) 291 */ 292 public View getContentView() { 293 return mContentView; 294 } 295 296 /** 297 * <p>Change the popup's content. The content is represented by an instance 298 * of {@link android.view.View}.</p> 299 * 300 * <p>This method has no effect if called when the popup is showing. To 301 * apply it while a popup is showing, call </p> 302 * 303 * @param contentView the new content for the popup 304 * 305 * @see #getContentView() 306 * @see #isShowing() 307 */ 308 public void setContentView(View contentView) { 309 if (isShowing()) { 310 return; 311 } 312 313 mContentView = contentView; 314 } 315 316 /** 317 * Set a callback for all touch events being dispatched to the popup 318 * window. 319 */ 320 public void setTouchInterceptor(OnTouchListener l) { 321 mTouchInterceptor = l; 322 } 323 324 /** 325 * <p>Indicate whether the popup window can grab the focus.</p> 326 * 327 * @return true if the popup is focusable, false otherwise 328 * 329 * @see #setFocusable(boolean) 330 */ 331 public boolean isFocusable() { 332 return mFocusable; 333 } 334 335 /** 336 * <p>Changes the focusability of the popup window. When focusable, the 337 * window will grab the focus from the current focused widget if the popup 338 * contains a focusable {@link android.view.View}. By default a popup 339 * window is not focusable.</p> 340 * 341 * <p>If the popup is showing, calling this method will take effect only 342 * the next time the popup is shown or through a manual call to one of 343 * the {@link #update()} methods.</p> 344 * 345 * @param focusable true if the popup should grab focus, false otherwise. 346 * 347 * @see #isFocusable() 348 * @see #isShowing() 349 * @see #update() 350 */ 351 public void setFocusable(boolean focusable) { 352 mFocusable = focusable; 353 } 354 355 /** 356 * Return the current value in {@link #setInputMethodMode(int)}. 357 * 358 * @see #setInputMethodMode(int) 359 */ 360 public int getInputMethodMode() { 361 return mInputMethodMode; 362 363 } 364 365 /** 366 * Control how the popup operates with an input method: one of 367 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, 368 * or {@link #INPUT_METHOD_NOT_NEEDED}. 369 * 370 * <p>If the popup is showing, calling this method will take effect only 371 * the next time the popup is shown or through a manual call to one of 372 * the {@link #update()} methods.</p> 373 * 374 * @see #getInputMethodMode() 375 * @see #update() 376 */ 377 public void setInputMethodMode(int mode) { 378 mInputMethodMode = mode; 379 } 380 381 /** 382 * <p>Indicates whether the popup window receives touch events.</p> 383 * 384 * @return true if the popup is touchable, false otherwise 385 * 386 * @see #setTouchable(boolean) 387 */ 388 public boolean isTouchable() { 389 return mTouchable; 390 } 391 392 /** 393 * <p>Changes the touchability of the popup window. When touchable, the 394 * window will receive touch events, otherwise touch events will go to the 395 * window below it. By default the window is touchable.</p> 396 * 397 * <p>If the popup is showing, calling this method will take effect only 398 * the next time the popup is shown or through a manual call to one of 399 * the {@link #update()} methods.</p> 400 * 401 * @param touchable true if the popup should receive touch events, false otherwise 402 * 403 * @see #isTouchable() 404 * @see #isShowing() 405 * @see #update() 406 */ 407 public void setTouchable(boolean touchable) { 408 mTouchable = touchable; 409 } 410 411 /** 412 * <p>Indicates whether the popup window will be informed of touch events 413 * outside of its window.</p> 414 * 415 * @return true if the popup is outside touchable, false otherwise 416 * 417 * @see #setOutsideTouchable(boolean) 418 */ 419 public boolean isOutsideTouchable() { 420 return mOutsideTouchable; 421 } 422 423 /** 424 * <p>Controls whether the pop-up will be informed of touch events outside 425 * of its window. This only makes sense for pop-ups that are touchable 426 * but not focusable, which means touches outside of the window will 427 * be delivered to the window behind. The default is false.</p> 428 * 429 * <p>If the popup is showing, calling this method will take effect only 430 * the next time the popup is shown or through a manual call to one of 431 * the {@link #update()} methods.</p> 432 * 433 * @param touchable true if the popup should receive outside 434 * touch events, false otherwise 435 * 436 * @see #isOutsideTouchable() 437 * @see #isShowing() 438 * @see #update() 439 */ 440 public void setOutsideTouchable(boolean touchable) { 441 mOutsideTouchable = touchable; 442 } 443 444 /** 445 * <p>Indicates whether clipping of the popup window is enabled.</p> 446 * 447 * @return true if the clipping is enabled, false otherwise 448 * 449 * @see #setClippingEnabled(boolean) 450 */ 451 public boolean isClippingEnabled() { 452 return mClippingEnabled; 453 } 454 455 /** 456 * <p>Allows the popup window to extend beyond the bounds of the screen. By default the 457 * window is clipped to the screen boundaries. Setting this to false will allow windows to be 458 * accurately positioned.</p> 459 * 460 * <p>If the popup is showing, calling this method will take effect only 461 * the next time the popup is shown or through a manual call to one of 462 * the {@link #update()} methods.</p> 463 * 464 * @param enabled false if the window should be allowed to extend outside of the screen 465 * @see #isShowing() 466 * @see #isClippingEnabled() 467 * @see #update() 468 */ 469 public void setClippingEnabled(boolean enabled) { 470 mClippingEnabled = enabled; 471 } 472 473 /** 474 * <p>Change the width and height measure specs that are given to the 475 * window manager by the popup. By default these are 0, meaning that 476 * the current width or height is requested as an explicit size from 477 * the window manager. You can supply 478 * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or 479 * {@link ViewGroup.LayoutParams#FILL_PARENT} to have that measure 480 * spec supplied instead, replacing the absolute width and height that 481 * has been set in the popup.</p> 482 * 483 * <p>If the popup is showing, calling this method will take effect only 484 * the next time the popup is shown.</p> 485 * 486 * @param widthSpec an explicit width measure spec mode, either 487 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 488 * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute 489 * width. 490 * @param heightSpec an explicit height measure spec mode, either 491 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 492 * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute 493 * height. 494 */ 495 public void setWindowLayoutMode(int widthSpec, int heightSpec) { 496 mWidthMode = widthSpec; 497 mHeightMode = heightSpec; 498 } 499 500 /** 501 * <p>Return this popup's height MeasureSpec</p> 502 * 503 * @return the height MeasureSpec of the popup 504 * 505 * @see #setHeight(int) 506 */ 507 public int getHeight() { 508 return mHeight; 509 } 510 511 /** 512 * <p>Change the popup's height MeasureSpec</p> 513 * 514 * <p>If the popup is showing, calling this method will take effect only 515 * the next time the popup is shown.</p> 516 * 517 * @param height the height MeasureSpec of the popup 518 * 519 * @see #getHeight() 520 * @see #isShowing() 521 */ 522 public void setHeight(int height) { 523 mHeight = height; 524 } 525 526 /** 527 * <p>Return this popup's width MeasureSpec</p> 528 * 529 * @return the width MeasureSpec of the popup 530 * 531 * @see #setWidth(int) 532 */ 533 public int getWidth() { 534 return mWidth; 535 } 536 537 /** 538 * <p>Change the popup's width MeasureSpec</p> 539 * 540 * <p>If the popup is showing, calling this method will take effect only 541 * the next time the popup is shown.</p> 542 * 543 * @param width the width MeasureSpec of the popup 544 * 545 * @see #getWidth() 546 * @see #isShowing() 547 */ 548 public void setWidth(int width) { 549 mWidth = width; 550 } 551 552 /** 553 * <p>Indicate whether this popup window is showing on screen.</p> 554 * 555 * @return true if the popup is showing, false otherwise 556 */ 557 public boolean isShowing() { 558 return mIsShowing; 559 } 560 561 /** 562 * <p> 563 * Display the content view in a popup window at the specified location. If the popup window 564 * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams} 565 * for more information on how gravity and the x and y parameters are related. Specifying 566 * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying 567 * <code>Gravity.LEFT | Gravity.TOP</code>. 568 * </p> 569 * 570 * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from 571 * @param gravity the gravity which controls the placement of the popup window 572 * @param x the popup's x location offset 573 * @param y the popup's y location offset 574 */ 575 public void showAtLocation(View parent, int gravity, int x, int y) { 576 if (isShowing() || mContentView == null) { 577 return; 578 } 579 580 mIsShowing = true; 581 mIsDropdown = false; 582 583 WindowManager.LayoutParams p = createPopupLayout(parent.getWindowToken()); 584 p.windowAnimations = computeAnimationResource(); 585 586 preparePopup(p); 587 if (gravity == Gravity.NO_GRAVITY) { 588 gravity = Gravity.TOP | Gravity.LEFT; 589 } 590 p.gravity = gravity; 591 p.x = x; 592 p.y = y; 593 invokePopup(p); 594 } 595 596 /** 597 * <p>Display the content view in a popup window anchored to the bottom-left 598 * corner of the anchor view. If there is not enough room on screen to show 599 * the popup in its entirety, this method tries to find a parent scroll 600 * view to scroll. If no parent scroll view can be scrolled, the bottom-left 601 * corner of the popup is pinned at the top left corner of the anchor view.</p> 602 * 603 * @param anchor the view on which to pin the popup window 604 * 605 * @see #dismiss() 606 */ 607 public void showAsDropDown(View anchor) { 608 showAsDropDown(anchor, 0, 0); 609 } 610 611 /** 612 * <p>Display the content view in a popup window anchored to the bottom-left 613 * corner of the anchor view offset by the specified x and y coordinates. 614 * If there is not enough room on screen to show 615 * the popup in its entirety, this method tries to find a parent scroll 616 * view to scroll. If no parent scroll view can be scrolled, the bottom-left 617 * corner of the popup is pinned at the top left corner of the anchor view.</p> 618 * 619 * @param anchor the view on which to pin the popup window 620 * 621 * @see #dismiss() 622 */ 623 public void showAsDropDown(View anchor, int xoff, int yoff) { 624 if (isShowing() || mContentView == null) { 625 return; 626 } 627 628 mIsShowing = true; 629 mIsDropdown = true; 630 631 WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken()); 632 preparePopup(p); 633 if (mBackground != null) { 634 mPopupView.refreshDrawableState(); 635 } 636 mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff); 637 if (mHeightMode < 0) p.height = mHeightMode; 638 if (mWidthMode < 0) p.width = mWidthMode; 639 p.windowAnimations = computeAnimationResource(); 640 invokePopup(p); 641 } 642 643 /** 644 * <p>Prepare the popup by embedding in into a new ViewGroup if the 645 * background drawable is not null. If embedding is required, the layout 646 * parameters' height is mnodified to take into account the background's 647 * padding.</p> 648 * 649 * @param p the layout parameters of the popup's content view 650 */ 651 private void preparePopup(WindowManager.LayoutParams p) { 652 if (mBackground != null) { 653 // when a background is available, we embed the content view 654 // within another view that owns the background drawable 655 PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); 656 PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( 657 ViewGroup.LayoutParams.FILL_PARENT, 658 ViewGroup.LayoutParams.FILL_PARENT 659 ); 660 popupViewContainer.setBackgroundDrawable(mBackground); 661 popupViewContainer.addView(mContentView, listParams); 662 663 if (p.height >= 0) { 664 // accomodate the popup's height to take into account the 665 // background's padding 666 p.height += popupViewContainer.getPaddingTop() + 667 popupViewContainer.getPaddingBottom(); 668 } 669 if (p.width >= 0) { 670 // accomodate the popup's width to take into account the 671 // background's padding 672 p.width += popupViewContainer.getPaddingLeft() + 673 popupViewContainer.getPaddingRight(); 674 } 675 mPopupView = popupViewContainer; 676 } else { 677 mPopupView = mContentView; 678 } 679 mPopupWidth = p.width; 680 mPopupHeight = p.height; 681 } 682 683 /** 684 * <p>Invoke the popup window by adding the content view to the window 685 * manager.</p> 686 * 687 * <p>The content view must be non-null when this method is invoked.</p> 688 * 689 * @param p the layout parameters of the popup's content view 690 */ 691 private void invokePopup(WindowManager.LayoutParams p) { 692 mWindowManager.addView(mPopupView, p); 693 } 694 695 /** 696 * <p>Generate the layout parameters for the popup window.</p> 697 * 698 * @param token the window token used to bind the popup's window 699 * 700 * @return the layout parameters to pass to the window manager 701 */ 702 private WindowManager.LayoutParams createPopupLayout(IBinder token) { 703 // generates the layout parameters for the drop down 704 // we want a fixed size view located at the bottom left of the anchor 705 WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 706 // these gravity settings put the view at the top left corner of the 707 // screen. The view is then positioned to the appropriate location 708 // by setting the x and y offsets to match the anchor's bottom 709 // left corner 710 p.gravity = Gravity.LEFT | Gravity.TOP; 711 p.width = mWidth; 712 p.height = mHeight; 713 if (mBackground != null) { 714 p.format = mBackground.getOpacity(); 715 } else { 716 p.format = PixelFormat.TRANSLUCENT; 717 } 718 p.flags = computeFlags(p.flags); 719 p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 720 p.token = token; 721 722 return p; 723 } 724 725 private int computeFlags(int curFlags) { 726 curFlags &= ~( 727 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | 728 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 729 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 730 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 731 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 732 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 733 if(mIgnoreCheekPress) { 734 curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 735 } 736 if (!mFocusable) { 737 curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 738 if (mInputMethodMode == INPUT_METHOD_NEEDED) { 739 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 740 } 741 } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { 742 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 743 } 744 if (!mTouchable) { 745 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 746 } 747 if (mTouchable) { 748 curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 749 } 750 if (!mClippingEnabled) { 751 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 752 } 753 return curFlags; 754 } 755 756 private int computeAnimationResource() { 757 if (mAnimationStyle == -1) { 758 if (mIsDropdown) { 759 return mAboveAnchor 760 ? com.android.internal.R.style.Animation_DropDownUp 761 : com.android.internal.R.style.Animation_DropDownDown; 762 } 763 return 0; 764 } 765 return mAnimationStyle; 766 } 767 768 /** 769 * <p>Positions the popup window on screen. When the popup window is too 770 * tall to fit under the anchor, a parent scroll view is seeked and scrolled 771 * up to reclaim space. If scrolling is not possible or not enough, the 772 * popup window gets moved on top of the anchor.</p> 773 * 774 * <p>The height must have been set on the layout parameters prior to 775 * calling this method.</p> 776 * 777 * @param anchor the view on which the popup window must be anchored 778 * @param p the layout parameters used to display the drop down 779 * 780 * @return true if the popup is translated upwards to fit on screen 781 */ 782 private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff, int yoff) { 783 anchor.getLocationInWindow(mDrawingLocation); 784 p.x = mDrawingLocation[0] + xoff; 785 p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff; 786 787 boolean onTop = false; 788 789 p.gravity = Gravity.LEFT | Gravity.TOP; 790 791 anchor.getLocationOnScreen(mScreenLocation); 792 final Rect displayFrame = new Rect(); 793 anchor.getWindowVisibleDisplayFrame(displayFrame); 794 795 final View root = anchor.getRootView(); 796 if (mScreenLocation[1] + anchor.getMeasuredHeight() + yoff + mPopupHeight > displayFrame.bottom 797 || p.x + mPopupWidth - root.getWidth() > 0) { 798 // if the drop down disappears at the bottom of the screen. we try to 799 // scroll a parent scrollview or move the drop down back up on top of 800 // the edit box 801 int scrollX = anchor.getScrollX(); 802 int scrollY = anchor.getScrollY(); 803 Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth, 804 scrollY + mPopupHeight + anchor.getMeasuredHeight()); 805 anchor.requestRectangleOnScreen(r, true); 806 807 // now we re-evaluate the space available, and decide from that 808 // whether the pop-up will go above or below the anchor. 809 anchor.getLocationInWindow(mDrawingLocation); 810 p.x = mDrawingLocation[0] + xoff; 811 p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff; 812 813 // determine whether there is more space above or below the anchor 814 anchor.getLocationOnScreen(mScreenLocation); 815 816 onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getMeasuredHeight() - yoff) 817 < (mScreenLocation[1] - yoff - displayFrame.top); 818 if (onTop) { 819 p.gravity = Gravity.LEFT | Gravity.BOTTOM; 820 p.y = root.getHeight() - mDrawingLocation[1] - yoff; 821 } else { 822 p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff; 823 } 824 } 825 826 p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; 827 828 return onTop; 829 } 830 831 /** 832 * Returns the maximum height that is available for the popup to be 833 * completely shown. It is recommended that this height be the maximum for 834 * the popup's height, otherwise it is possible that the popup will be 835 * clipped. 836 * 837 * @param anchor The view on which the popup window must be anchored. 838 * @return The maximum available height for the popup to be completely 839 * shown. 840 */ 841 public int getMaxAvailableHeight(View anchor) { 842 final Rect displayFrame = new Rect(); 843 anchor.getWindowVisibleDisplayFrame(displayFrame); 844 845 final int[] anchorPos = mDrawingLocation; 846 anchor.getLocationOnScreen(anchorPos); 847 848 final int distanceToBottom = displayFrame.bottom 849 - (anchorPos[1] + anchor.getHeight()); 850 final int distanceToTop = anchorPos[1] - displayFrame.top; 851 852 // anchorPos[1] is distance from anchor to top of screen 853 int returnedHeight = Math.max(distanceToBottom, distanceToTop); 854 if (mBackground != null) { 855 mBackground.getPadding(mTempRect); 856 returnedHeight -= mTempRect.top + mTempRect.bottom; 857 } 858 859 return returnedHeight; 860 } 861 862 /** 863 * <p>Dispose of the popup window. This method can be invoked only after 864 * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling 865 * this method will have no effect.</p> 866 * 867 * @see #showAsDropDown(android.view.View) 868 */ 869 public void dismiss() { 870 if (isShowing() && mPopupView != null) { 871 mWindowManager.removeView(mPopupView); 872 if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { 873 ((ViewGroup) mPopupView).removeView(mContentView); 874 } 875 mPopupView = null; 876 mIsShowing = false; 877 878 if (mOnDismissListener != null) { 879 mOnDismissListener.onDismiss(); 880 } 881 } 882 } 883 884 /** 885 * Sets the listener to be called when the window is dismissed. 886 * 887 * @param onDismissListener The listener. 888 */ 889 public void setOnDismissListener(OnDismissListener onDismissListener) { 890 mOnDismissListener = onDismissListener; 891 } 892 893 /** 894 * Updates the state of the popup window, if it is currently being displayed, 895 * from the currently set state. This include: 896 * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)}, 897 * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)}, 898 * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}. 899 */ 900 public void update() { 901 if (!isShowing() || mContentView == null) { 902 return; 903 } 904 905 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 906 mPopupView.getLayoutParams(); 907 908 boolean update = false; 909 910 final int newAnim = computeAnimationResource(); 911 if (newAnim != p.windowAnimations) { 912 p.windowAnimations = newAnim; 913 update = true; 914 } 915 916 final int newFlags = computeFlags(p.flags); 917 if (newFlags != p.flags) { 918 p.flags = newFlags; 919 update = true; 920 } 921 922 if (update) { 923 mWindowManager.updateViewLayout(mPopupView, p); 924 } 925 } 926 927 /** 928 * <p>Updates the position and the dimension of the popup window. Width and 929 * height can be set to -1 to update location only. Calling this function 930 * also updates the window with the current popup state as 931 * described for {@link #update()}.</p> 932 * 933 * @param x the new x location 934 * @param y the new y location 935 * @param width the new width, can be -1 to ignore 936 * @param height the new height, can be -1 to ignore 937 */ 938 public void update(int x, int y, int width, int height) { 939 if (width != -1) { 940 setWidth(width); 941 } 942 943 if (height != -1) { 944 setHeight(height); 945 } 946 947 if (!isShowing() || mContentView == null) { 948 return; 949 } 950 951 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 952 mPopupView.getLayoutParams(); 953 954 boolean update = false; 955 956 final int finalWidth = mWidthMode < 0 ? mWidthMode : p.width; 957 if (width != -1 && p.width != finalWidth) { 958 p.width = finalWidth; 959 update = true; 960 } 961 962 final int finalHeight = mHeightMode < 0 ? mHeightMode : p.height; 963 if (height != -1 && p.height != finalHeight) { 964 p.height = finalHeight; 965 update = true; 966 } 967 968 if (p.x != x) { 969 p.x = x; 970 update = true; 971 } 972 973 if (p.y != y) { 974 p.y = y; 975 update = true; 976 } 977 978 final int newAnim = computeAnimationResource(); 979 if (newAnim != p.windowAnimations) { 980 p.windowAnimations = newAnim; 981 update = true; 982 } 983 984 final int newFlags = computeFlags(p.flags); 985 if (newFlags != p.flags) { 986 p.flags = newFlags; 987 update = true; 988 } 989 990 if (update) { 991 if (mPopupView != mContentView) { 992 final View popupViewContainer = mPopupView; 993 if (p.height >= 0) { 994 // accomodate the popup's height to take into account the 995 // background's padding 996 p.height += popupViewContainer.getPaddingTop() + 997 popupViewContainer.getPaddingBottom(); 998 } 999 if (p.width >= 0) { 1000 // accomodate the popup's width to take into account the 1001 // background's padding 1002 p.width += popupViewContainer.getPaddingLeft() + 1003 popupViewContainer.getPaddingRight(); 1004 } 1005 } 1006 1007 mWindowManager.updateViewLayout(mPopupView, p); 1008 } 1009 } 1010 1011 /** 1012 * <p>Updates the position and the dimension of the popup window. Width and 1013 * height can be set to -1 to update location only. Calling this function 1014 * also updates the window with the current popup state as 1015 * described for {@link #update()}.</p> 1016 * 1017 * @param anchor the popup's anchor view 1018 * @param width the new width, can be -1 to ignore 1019 * @param height the new height, can be -1 to ignore 1020 */ 1021 public void update(View anchor, int width, int height) { 1022 update(anchor, 0, 0, width, height); 1023 } 1024 1025 /** 1026 * <p>Updates the position and the dimension of the popup window. Width and 1027 * height can be set to -1 to update location only. Calling this function 1028 * also updates the window with the current popup state as 1029 * described for {@link #update()}.</p> 1030 * 1031 * @param anchor the popup's anchor view 1032 * @param xoff x offset from the view's left edge 1033 * @param yoff y offset from the view's bottom edge 1034 * @param width the new width, can be -1 to ignore 1035 * @param height the new height, can be -1 to ignore 1036 */ 1037 public void update(View anchor, int xoff, int yoff, int width, int height) { 1038 if (!isShowing() || mContentView == null) { 1039 return; 1040 } 1041 1042 WindowManager.LayoutParams p = (WindowManager.LayoutParams) 1043 mPopupView.getLayoutParams(); 1044 1045 if (width == -1) { 1046 width = mPopupWidth; 1047 } else { 1048 mPopupWidth = width; 1049 } 1050 if (height == -1) { 1051 height = mPopupHeight; 1052 } else { 1053 mPopupHeight = height; 1054 } 1055 1056 findDropDownPosition(anchor, p, xoff, yoff); 1057 update(p.x, p.y, width, height); 1058 } 1059 1060 /** 1061 * Listener that is called when this popup window is dismissed. 1062 */ 1063 public interface OnDismissListener { 1064 /** 1065 * Called when this popup window is dismissed. 1066 */ 1067 public void onDismiss(); 1068 } 1069 1070 private class PopupViewContainer extends FrameLayout { 1071 1072 public PopupViewContainer(Context context) { 1073 super(context); 1074 } 1075 1076 @Override 1077 protected int[] onCreateDrawableState(int extraSpace) { 1078 if (mAboveAnchor) { 1079 // 1 more needed for the above anchor state 1080 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 1081 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); 1082 return drawableState; 1083 } else { 1084 return super.onCreateDrawableState(extraSpace); 1085 } 1086 } 1087 1088 @Override 1089 public boolean dispatchKeyEvent(KeyEvent event) { 1090 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1091 dismiss(); 1092 return true; 1093 } else { 1094 return super.dispatchKeyEvent(event); 1095 } 1096 } 1097 1098 @Override 1099 public boolean dispatchTouchEvent(MotionEvent ev) { 1100 if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { 1101 return true; 1102 } 1103 return super.dispatchTouchEvent(ev); 1104 } 1105 1106 @Override 1107 public boolean onTouchEvent(MotionEvent event) { 1108 final int x = (int) event.getX(); 1109 final int y = (int) event.getY(); 1110 1111 if ((event.getAction() == MotionEvent.ACTION_DOWN) 1112 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 1113 dismiss(); 1114 return true; 1115 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 1116 dismiss(); 1117 return true; 1118 } else { 1119 return super.onTouchEvent(event); 1120 } 1121 } 1122 1123 } 1124 1125} 1126