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