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