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