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