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