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