| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.widget; |
| |
| import com.android.internal.R; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.graphics.PixelFormat; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.StateListDrawable; |
| import android.os.Build; |
| import android.os.IBinder; |
| import android.util.AttributeSet; |
| import android.view.Gravity; |
| import android.view.KeyEvent; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.OnTouchListener; |
| import android.view.ViewGroup; |
| import android.view.ViewTreeObserver; |
| import android.view.ViewTreeObserver.OnScrollChangedListener; |
| import android.view.WindowManager; |
| |
| import java.lang.ref.WeakReference; |
| |
| /** |
| * <p>A popup window that can be used to display an arbitrary view. The popup |
| * windows is a floating container that appears on top of the current |
| * activity.</p> |
| * |
| * @see android.widget.AutoCompleteTextView |
| * @see android.widget.Spinner |
| */ |
| public class PopupWindow { |
| /** |
| * Mode for {@link #setInputMethodMode(int)}: the requirements for the |
| * input method should be based on the focusability of the popup. That is |
| * if it is focusable than it needs to work with the input method, else |
| * it doesn't. |
| */ |
| public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; |
| |
| /** |
| * Mode for {@link #setInputMethodMode(int)}: this popup always needs to |
| * work with an input method, regardless of whether it is focusable. This |
| * means that it will always be displayed so that the user can also operate |
| * the input method while it is shown. |
| */ |
| public static final int INPUT_METHOD_NEEDED = 1; |
| |
| /** |
| * Mode for {@link #setInputMethodMode(int)}: this popup never needs to |
| * work with an input method, regardless of whether it is focusable. This |
| * means that it will always be displayed to use as much space on the |
| * screen as needed, regardless of whether this covers the input method. |
| */ |
| public static final int INPUT_METHOD_NOT_NEEDED = 2; |
| |
| private Context mContext; |
| private WindowManager mWindowManager; |
| |
| private boolean mIsShowing; |
| private boolean mIsDropdown; |
| |
| private View mContentView; |
| private View mPopupView; |
| private boolean mFocusable; |
| private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; |
| private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; |
| private boolean mTouchable = true; |
| private boolean mOutsideTouchable = false; |
| private boolean mClippingEnabled = true; |
| private int mSplitTouchEnabled = -1; |
| private boolean mLayoutInScreen; |
| private boolean mClipToScreen; |
| private boolean mAllowScrollingAnchorParent = true; |
| private boolean mLayoutInsetDecor = false; |
| private boolean mNotTouchModal; |
| |
| private OnTouchListener mTouchInterceptor; |
| |
| private int mWidthMode; |
| private int mWidth; |
| private int mLastWidth; |
| private int mHeightMode; |
| private int mHeight; |
| private int mLastHeight; |
| |
| private int mPopupWidth; |
| private int mPopupHeight; |
| |
| private int[] mDrawingLocation = new int[2]; |
| private int[] mScreenLocation = new int[2]; |
| private Rect mTempRect = new Rect(); |
| |
| private Drawable mBackground; |
| private Drawable mAboveAnchorBackgroundDrawable; |
| private Drawable mBelowAnchorBackgroundDrawable; |
| |
| private boolean mAboveAnchor; |
| private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; |
| |
| private OnDismissListener mOnDismissListener; |
| private boolean mIgnoreCheekPress = false; |
| |
| private int mAnimationStyle = -1; |
| |
| private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] { |
| com.android.internal.R.attr.state_above_anchor |
| }; |
| |
| private WeakReference<View> mAnchor; |
| private OnScrollChangedListener mOnScrollChangedListener = |
| new OnScrollChangedListener() { |
| public void onScrollChanged() { |
| View anchor = mAnchor != null ? mAnchor.get() : null; |
| if (anchor != null && mPopupView != null) { |
| WindowManager.LayoutParams p = (WindowManager.LayoutParams) |
| mPopupView.getLayoutParams(); |
| |
| updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff)); |
| update(p.x, p.y, -1, -1, true); |
| } |
| } |
| }; |
| private int mAnchorXoff, mAnchorYoff; |
| |
| private boolean mPopupViewInitialLayoutDirectionInherited; |
| |
| /** |
| * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> |
| * |
| * <p>The popup does provide a background.</p> |
| */ |
| public PopupWindow(Context context) { |
| this(context, null); |
| } |
| |
| /** |
| * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> |
| * |
| * <p>The popup does provide a background.</p> |
| */ |
| public PopupWindow(Context context, AttributeSet attrs) { |
| this(context, attrs, com.android.internal.R.attr.popupWindowStyle); |
| } |
| |
| /** |
| * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> |
| * |
| * <p>The popup does provide a background.</p> |
| */ |
| public PopupWindow(Context context, AttributeSet attrs, int defStyle) { |
| this(context, attrs, defStyle, 0); |
| } |
| |
| /** |
| * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p> |
| * |
| * <p>The popup does not provide a background.</p> |
| */ |
| public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { |
| mContext = context; |
| mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); |
| |
| TypedArray a = |
| context.obtainStyledAttributes( |
| attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes); |
| |
| mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground); |
| |
| final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1); |
| mAnimationStyle = animStyle == com.android.internal.R.style.Animation_PopupWindow ? -1 : |
| animStyle; |
| |
| // If this is a StateListDrawable, try to find and store the drawable to be |
| // used when the drop-down is placed above its anchor view, and the one to be |
| // used when the drop-down is placed below its anchor view. We extract |
| // the drawables ourselves to work around a problem with using refreshDrawableState |
| // that it will take into account the padding of all drawables specified in a |
| // StateListDrawable, thus adding superfluous padding to drop-down views. |
| // |
| // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and |
| // at least one other drawable, intended for the 'below-anchor state'. |
| if (mBackground instanceof StateListDrawable) { |
| StateListDrawable background = (StateListDrawable) mBackground; |
| |
| // Find the above-anchor view - this one's easy, it should be labeled as such. |
| int aboveAnchorStateIndex = background.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET); |
| |
| // Now, for the below-anchor view, look for any other drawable specified in the |
| // StateListDrawable which is not for the above-anchor state and use that. |
| int count = background.getStateCount(); |
| int belowAnchorStateIndex = -1; |
| for (int i = 0; i < count; i++) { |
| if (i != aboveAnchorStateIndex) { |
| belowAnchorStateIndex = i; |
| break; |
| } |
| } |
| |
| // Store the drawables we found, if we found them. Otherwise, set them both |
| // to null so that we'll just use refreshDrawableState. |
| if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) { |
| mAboveAnchorBackgroundDrawable = background.getStateDrawable(aboveAnchorStateIndex); |
| mBelowAnchorBackgroundDrawable = background.getStateDrawable(belowAnchorStateIndex); |
| } else { |
| mBelowAnchorBackgroundDrawable = null; |
| mAboveAnchorBackgroundDrawable = null; |
| } |
| } |
| |
| a.recycle(); |
| } |
| |
| /** |
| * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> |
| * |
| * <p>The popup does not provide any background. This should be handled |
| * by the content view.</p> |
| */ |
| public PopupWindow() { |
| this(null, 0, 0); |
| } |
| |
| /** |
| * <p>Create a new non focusable popup window which can display the |
| * <tt>contentView</tt>. The dimension of the window are (0,0).</p> |
| * |
| * <p>The popup does not provide any background. This should be handled |
| * by the content view.</p> |
| * |
| * @param contentView the popup's content |
| */ |
| public PopupWindow(View contentView) { |
| this(contentView, 0, 0); |
| } |
| |
| /** |
| * <p>Create a new empty, non focusable popup window. The dimension of the |
| * window must be passed to this constructor.</p> |
| * |
| * <p>The popup does not provide any background. This should be handled |
| * by the content view.</p> |
| * |
| * @param width the popup's width |
| * @param height the popup's height |
| */ |
| public PopupWindow(int width, int height) { |
| this(null, width, height); |
| } |
| |
| /** |
| * <p>Create a new non focusable popup window which can display the |
| * <tt>contentView</tt>. The dimension of the window must be passed to |
| * this constructor.</p> |
| * |
| * <p>The popup does not provide any background. This should be handled |
| * by the content view.</p> |
| * |
| * @param contentView the popup's content |
| * @param width the popup's width |
| * @param height the popup's height |
| */ |
| public PopupWindow(View contentView, int width, int height) { |
| this(contentView, width, height, false); |
| } |
| |
| /** |
| * <p>Create a new popup window which can display the <tt>contentView</tt>. |
| * The dimension of the window must be passed to this constructor.</p> |
| * |
| * <p>The popup does not provide any background. This should be handled |
| * by the content view.</p> |
| * |
| * @param contentView the popup's content |
| * @param width the popup's width |
| * @param height the popup's height |
| * @param focusable true if the popup can be focused, false otherwise |
| */ |
| public PopupWindow(View contentView, int width, int height, boolean focusable) { |
| if (contentView != null) { |
| mContext = contentView.getContext(); |
| mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); |
| } |
| setContentView(contentView); |
| setWidth(width); |
| setHeight(height); |
| setFocusable(focusable); |
| } |
| |
| /** |
| * <p>Return the drawable used as the popup window's background.</p> |
| * |
| * @return the background drawable or null |
| */ |
| public Drawable getBackground() { |
| return mBackground; |
| } |
| |
| /** |
| * <p>Change the background drawable for this popup window. The background |
| * can be set to null.</p> |
| * |
| * @param background the popup's background |
| */ |
| public void setBackgroundDrawable(Drawable background) { |
| mBackground = background; |
| } |
| |
| /** |
| * <p>Return the animation style to use the popup appears and disappears</p> |
| * |
| * @return the animation style to use the popup appears and disappears |
| */ |
| public int getAnimationStyle() { |
| return mAnimationStyle; |
| } |
| |
| /** |
| * Set the flag on popup to ignore cheek press eventt; by default this flag |
| * is set to false |
| * which means the pop wont ignore cheek press dispatch events. |
| * |
| * <p>If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown or through a manual call to one of |
| * the {@link #update()} methods.</p> |
| * |
| * @see #update() |
| */ |
| public void setIgnoreCheekPress() { |
| mIgnoreCheekPress = true; |
| } |
| |
| |
| /** |
| * <p>Change the animation style resource for this popup.</p> |
| * |
| * <p>If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown or through a manual call to one of |
| * the {@link #update()} methods.</p> |
| * |
| * @param animationStyle animation style to use when the popup appears |
| * and disappears. Set to -1 for the default animation, 0 for no |
| * animation, or a resource identifier for an explicit animation. |
| * |
| * @see #update() |
| */ |
| public void setAnimationStyle(int animationStyle) { |
| mAnimationStyle = animationStyle; |
| } |
| |
| /** |
| * <p>Return the view used as the content of the popup window.</p> |
| * |
| * @return a {@link android.view.View} representing the popup's content |
| * |
| * @see #setContentView(android.view.View) |
| */ |
| public View getContentView() { |
| return mContentView; |
| } |
| |
| /** |
| * <p>Change the popup's content. The content is represented by an instance |
| * of {@link android.view.View}.</p> |
| * |
| * <p>This method has no effect if called when the popup is showing.</p> |
| * |
| * @param contentView the new content for the popup |
| * |
| * @see #getContentView() |
| * @see #isShowing() |
| */ |
| public void setContentView(View contentView) { |
| if (isShowing()) { |
| return; |
| } |
| |
| mContentView = contentView; |
| |
| if (mContext == null && mContentView != null) { |
| mContext = mContentView.getContext(); |
| } |
| |
| if (mWindowManager == null && mContentView != null) { |
| mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); |
| } |
| } |
| |
| /** |
| * Set a callback for all touch events being dispatched to the popup |
| * window. |
| */ |
| public void setTouchInterceptor(OnTouchListener l) { |
| mTouchInterceptor = l; |
| } |
| |
| /** |
| * <p>Indicate whether the popup window can grab the focus.</p> |
| * |
| * @return true if the popup is focusable, false otherwise |
| * |
| * @see #setFocusable(boolean) |
| */ |
| public boolean isFocusable() { |
| return mFocusable; |
| } |
| |
| /** |
| * <p>Changes the focusability of the popup window. When focusable, the |
| * window will grab the focus from the current focused widget if the popup |
| * contains a focusable {@link android.view.View}. By default a popup |
| * window is not focusable.</p> |
| * |
| * <p>If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown or through a manual call to one of |
| * the {@link #update()} methods.</p> |
| * |
| * @param focusable true if the popup should grab focus, false otherwise. |
| * |
| * @see #isFocusable() |
| * @see #isShowing() |
| * @see #update() |
| */ |
| public void setFocusable(boolean focusable) { |
| mFocusable = focusable; |
| } |
| |
| /** |
| * Return the current value in {@link #setInputMethodMode(int)}. |
| * |
| * @see #setInputMethodMode(int) |
| */ |
| public int getInputMethodMode() { |
| return mInputMethodMode; |
| |
| } |
| |
| /** |
| * Control how the popup operates with an input method: one of |
| * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, |
| * or {@link #INPUT_METHOD_NOT_NEEDED}. |
| * |
| * <p>If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown or through a manual call to one of |
| * the {@link #update()} methods.</p> |
| * |
| * @see #getInputMethodMode() |
| * @see #update() |
| */ |
| public void setInputMethodMode(int mode) { |
| mInputMethodMode = mode; |
| } |
| |
| /** |
| * Sets the operating mode for the soft input area. |
| * |
| * @param mode The desired mode, see |
| * {@link android.view.WindowManager.LayoutParams#softInputMode} |
| * for the full list |
| * |
| * @see android.view.WindowManager.LayoutParams#softInputMode |
| * @see #getSoftInputMode() |
| */ |
| public void setSoftInputMode(int mode) { |
| mSoftInputMode = mode; |
| } |
| |
| /** |
| * Returns the current value in {@link #setSoftInputMode(int)}. |
| * |
| * @see #setSoftInputMode(int) |
| * @see android.view.WindowManager.LayoutParams#softInputMode |
| */ |
| public int getSoftInputMode() { |
| return mSoftInputMode; |
| } |
| |
| /** |
| * <p>Indicates whether the popup window receives touch events.</p> |
| * |
| * @return true if the popup is touchable, false otherwise |
| * |
| * @see #setTouchable(boolean) |
| */ |
| public boolean isTouchable() { |
| return mTouchable; |
| } |
| |
| /** |
| * <p>Changes the touchability of the popup window. When touchable, the |
| * window will receive touch events, otherwise touch events will go to the |
| * window below it. By default the window is touchable.</p> |
| * |
| * <p>If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown or through a manual call to one of |
| * the {@link #update()} methods.</p> |
| * |
| * @param touchable true if the popup should receive touch events, false otherwise |
| * |
| * @see #isTouchable() |
| * @see #isShowing() |
| * @see #update() |
| */ |
| public void setTouchable(boolean touchable) { |
| mTouchable = touchable; |
| } |
| |
| /** |
| * <p>Indicates whether the popup window will be informed of touch events |
| * outside of its window.</p> |
| * |
| * @return true if the popup is outside touchable, false otherwise |
| * |
| * @see #setOutsideTouchable(boolean) |
| */ |
| public boolean isOutsideTouchable() { |
| return mOutsideTouchable; |
| } |
| |
| /** |
| * <p>Controls whether the pop-up will be informed of touch events outside |
| * of its window. This only makes sense for pop-ups that are touchable |
| * but not focusable, which means touches outside of the window will |
| * be delivered to the window behind. The default is false.</p> |
| * |
| * <p>If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown or through a manual call to one of |
| * the {@link #update()} methods.</p> |
| * |
| * @param touchable true if the popup should receive outside |
| * touch events, false otherwise |
| * |
| * @see #isOutsideTouchable() |
| * @see #isShowing() |
| * @see #update() |
| */ |
| public void setOutsideTouchable(boolean touchable) { |
| mOutsideTouchable = touchable; |
| } |
| |
| /** |
| * <p>Indicates whether clipping of the popup window is enabled.</p> |
| * |
| * @return true if the clipping is enabled, false otherwise |
| * |
| * @see #setClippingEnabled(boolean) |
| */ |
| public boolean isClippingEnabled() { |
| return mClippingEnabled; |
| } |
| |
| /** |
| * <p>Allows the popup window to extend beyond the bounds of the screen. By default the |
| * window is clipped to the screen boundaries. Setting this to false will allow windows to be |
| * accurately positioned.</p> |
| * |
| * <p>If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown or through a manual call to one of |
| * the {@link #update()} methods.</p> |
| * |
| * @param enabled false if the window should be allowed to extend outside of the screen |
| * @see #isShowing() |
| * @see #isClippingEnabled() |
| * @see #update() |
| */ |
| public void setClippingEnabled(boolean enabled) { |
| mClippingEnabled = enabled; |
| } |
| |
| /** |
| * Clip this popup window to the screen, but not to the containing window. |
| * |
| * @param enabled True to clip to the screen. |
| * @hide |
| */ |
| public void setClipToScreenEnabled(boolean enabled) { |
| mClipToScreen = enabled; |
| setClippingEnabled(!enabled); |
| } |
| |
| /** |
| * Allow PopupWindow to scroll the anchor's parent to provide more room |
| * for the popup. Enabled by default. |
| * |
| * @param enabled True to scroll the anchor's parent when more room is desired by the popup. |
| */ |
| void setAllowScrollingAnchorParent(boolean enabled) { |
| mAllowScrollingAnchorParent = enabled; |
| } |
| |
| /** |
| * <p>Indicates whether the popup window supports splitting touches.</p> |
| * |
| * @return true if the touch splitting is enabled, false otherwise |
| * |
| * @see #setSplitTouchEnabled(boolean) |
| */ |
| public boolean isSplitTouchEnabled() { |
| if (mSplitTouchEnabled < 0 && mContext != null) { |
| return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB; |
| } |
| return mSplitTouchEnabled == 1; |
| } |
| |
| /** |
| * <p>Allows the popup window to split touches across other windows that also |
| * support split touch. When this flag is false, the first pointer |
| * that goes down determines the window to which all subsequent touches |
| * go until all pointers go up. When this flag is true, each pointer |
| * (not necessarily the first) that goes down determines the window |
| * to which all subsequent touches of that pointer will go until that |
| * pointer goes up thereby enabling touches with multiple pointers |
| * to be split across multiple windows.</p> |
| * |
| * @param enabled true if the split touches should be enabled, false otherwise |
| * @see #isSplitTouchEnabled() |
| */ |
| public void setSplitTouchEnabled(boolean enabled) { |
| mSplitTouchEnabled = enabled ? 1 : 0; |
| } |
| |
| /** |
| * <p>Indicates whether the popup window will be forced into using absolute screen coordinates |
| * for positioning.</p> |
| * |
| * @return true if the window will always be positioned in screen coordinates. |
| * @hide |
| */ |
| public boolean isLayoutInScreenEnabled() { |
| return mLayoutInScreen; |
| } |
| |
| /** |
| * <p>Allows the popup window to force the flag |
| * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior. |
| * This will cause the popup to be positioned in absolute screen coordinates.</p> |
| * |
| * @param enabled true if the popup should always be positioned in screen coordinates |
| * @hide |
| */ |
| public void setLayoutInScreenEnabled(boolean enabled) { |
| mLayoutInScreen = enabled; |
| } |
| |
| /** |
| * Allows the popup window to force the flag |
| * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior. |
| * This will cause the popup to inset its content to account for system windows overlaying |
| * the screen, such as the status bar. |
| * |
| * <p>This will often be combined with {@link #setLayoutInScreenEnabled(boolean)}. |
| * |
| * @param enabled true if the popup's views should inset content to account for system windows, |
| * the way that decor views behave for full-screen windows. |
| * @hide |
| */ |
| public void setLayoutInsetDecor(boolean enabled) { |
| mLayoutInsetDecor = enabled; |
| } |
| |
| /** |
| * Set the layout type for this window. Should be one of the TYPE constants defined in |
| * {@link WindowManager.LayoutParams}. |
| * |
| * @param layoutType Layout type for this window. |
| * @hide |
| */ |
| public void setWindowLayoutType(int layoutType) { |
| mWindowLayoutType = layoutType; |
| } |
| |
| /** |
| * @return The layout type for this window. |
| * @hide |
| */ |
| public int getWindowLayoutType() { |
| return mWindowLayoutType; |
| } |
| |
| /** |
| * Set whether this window is touch modal or if outside touches will be sent to |
| * other windows behind it. |
| * @hide |
| */ |
| public void setTouchModal(boolean touchModal) { |
| mNotTouchModal = !touchModal; |
| } |
| |
| /** |
| * <p>Change the width and height measure specs that are given to the |
| * window manager by the popup. By default these are 0, meaning that |
| * the current width or height is requested as an explicit size from |
| * the window manager. You can supply |
| * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or |
| * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure |
| * spec supplied instead, replacing the absolute width and height that |
| * has been set in the popup.</p> |
| * |
| * <p>If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown.</p> |
| * |
| * @param widthSpec an explicit width measure spec mode, either |
| * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, |
| * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute |
| * width. |
| * @param heightSpec an explicit height measure spec mode, either |
| * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, |
| * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute |
| * height. |
| */ |
| public void setWindowLayoutMode(int widthSpec, int heightSpec) { |
| mWidthMode = widthSpec; |
| mHeightMode = heightSpec; |
| } |
| |
| /** |
| * <p>Return this popup's height MeasureSpec</p> |
| * |
| * @return the height MeasureSpec of the popup |
| * |
| * @see #setHeight(int) |
| */ |
| public int getHeight() { |
| return mHeight; |
| } |
| |
| /** |
| * <p>Change the popup's height MeasureSpec</p> |
| * |
| * <p>If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown.</p> |
| * |
| * @param height the height MeasureSpec of the popup |
| * |
| * @see #getHeight() |
| * @see #isShowing() |
| */ |
| public void setHeight(int height) { |
| mHeight = height; |
| } |
| |
| /** |
| * <p>Return this popup's width MeasureSpec</p> |
| * |
| * @return the width MeasureSpec of the popup |
| * |
| * @see #setWidth(int) |
| */ |
| public int getWidth() { |
| return mWidth; |
| } |
| |
| /** |
| * <p>Change the popup's width MeasureSpec</p> |
| * |
| * <p>If the popup is showing, calling this method will take effect only |
| * the next time the popup is shown.</p> |
| * |
| * @param width the width MeasureSpec of the popup |
| * |
| * @see #getWidth() |
| * @see #isShowing() |
| */ |
| public void setWidth(int width) { |
| mWidth = width; |
| } |
| |
| /** |
| * <p>Indicate whether this popup window is showing on screen.</p> |
| * |
| * @return true if the popup is showing, false otherwise |
| */ |
| public boolean isShowing() { |
| return mIsShowing; |
| } |
| |
| /** |
| * <p> |
| * Display the content view in a popup window at the specified location. If the popup window |
| * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams} |
| * for more information on how gravity and the x and y parameters are related. Specifying |
| * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying |
| * <code>Gravity.LEFT | Gravity.TOP</code>. |
| * </p> |
| * |
| * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from |
| * @param gravity the gravity which controls the placement of the popup window |
| * @param x the popup's x location offset |
| * @param y the popup's y location offset |
| */ |
| public void showAtLocation(View parent, int gravity, int x, int y) { |
| showAtLocation(parent.getWindowToken(), gravity, x, y); |
| } |
| |
| /** |
| * Display the content view in a popup window at the specified location. |
| * |
| * @param token Window token to use for creating the new window |
| * @param gravity the gravity which controls the placement of the popup window |
| * @param x the popup's x location offset |
| * @param y the popup's y location offset |
| * |
| * @hide Internal use only. Applications should use |
| * {@link #showAtLocation(View, int, int, int)} instead. |
| */ |
| public void showAtLocation(IBinder token, int gravity, int x, int y) { |
| if (isShowing() || mContentView == null) { |
| return; |
| } |
| |
| unregisterForScrollChanged(); |
| |
| mIsShowing = true; |
| mIsDropdown = false; |
| |
| WindowManager.LayoutParams p = createPopupLayout(token); |
| p.windowAnimations = computeAnimationResource(); |
| |
| preparePopup(p); |
| if (gravity == Gravity.NO_GRAVITY) { |
| gravity = Gravity.TOP | Gravity.START; |
| } |
| p.gravity = gravity; |
| p.x = x; |
| p.y = y; |
| if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; |
| if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; |
| invokePopup(p); |
| } |
| |
| /** |
| * <p>Display the content view in a popup window anchored to the bottom-left |
| * corner of the anchor view. If there is not enough room on screen to show |
| * the popup in its entirety, this method tries to find a parent scroll |
| * view to scroll. If no parent scroll view can be scrolled, the bottom-left |
| * corner of the popup is pinned at the top left corner of the anchor view.</p> |
| * |
| * @param anchor the view on which to pin the popup window |
| * |
| * @see #dismiss() |
| */ |
| public void showAsDropDown(View anchor) { |
| showAsDropDown(anchor, 0, 0); |
| } |
| |
| /** |
| * <p>Display the content view in a popup window anchored to the bottom-left |
| * corner of the anchor view offset by the specified x and y coordinates. |
| * If there is not enough room on screen to show |
| * the popup in its entirety, this method tries to find a parent scroll |
| * view to scroll. If no parent scroll view can be scrolled, the bottom-left |
| * corner of the popup is pinned at the top left corner of the anchor view.</p> |
| * <p>If the view later scrolls to move <code>anchor</code> to a different |
| * location, the popup will be moved correspondingly.</p> |
| * |
| * @param anchor the view on which to pin the popup window |
| * |
| * @see #dismiss() |
| */ |
| public void showAsDropDown(View anchor, int xoff, int yoff) { |
| if (isShowing() || mContentView == null) { |
| return; |
| } |
| |
| registerForScrollChanged(anchor, xoff, yoff); |
| |
| mIsShowing = true; |
| mIsDropdown = true; |
| |
| WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken()); |
| preparePopup(p); |
| |
| updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff)); |
| |
| if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; |
| if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; |
| |
| p.windowAnimations = computeAnimationResource(); |
| |
| invokePopup(p); |
| } |
| |
| private void updateAboveAnchor(boolean aboveAnchor) { |
| if (aboveAnchor != mAboveAnchor) { |
| mAboveAnchor = aboveAnchor; |
| |
| if (mBackground != null) { |
| // If the background drawable provided was a StateListDrawable with above-anchor |
| // and below-anchor states, use those. Otherwise rely on refreshDrawableState to |
| // do the job. |
| if (mAboveAnchorBackgroundDrawable != null) { |
| if (mAboveAnchor) { |
| mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable); |
| } else { |
| mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable); |
| } |
| } else { |
| mPopupView.refreshDrawableState(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Indicates whether the popup is showing above (the y coordinate of the popup's bottom |
| * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate |
| * of the popup is greater than y coordinate of the anchor's bottom). |
| * |
| * The value returned |
| * by this method is meaningful only after {@link #showAsDropDown(android.view.View)} |
| * or {@link #showAsDropDown(android.view.View, int, int)} was invoked. |
| * |
| * @return True if this popup is showing above the anchor view, false otherwise. |
| */ |
| public boolean isAboveAnchor() { |
| return mAboveAnchor; |
| } |
| |
| /** |
| * <p>Prepare the popup by embedding in into a new ViewGroup if the |
| * background drawable is not null. If embedding is required, the layout |
| * parameters' height is mnodified to take into account the background's |
| * padding.</p> |
| * |
| * @param p the layout parameters of the popup's content view |
| */ |
| private void preparePopup(WindowManager.LayoutParams p) { |
| if (mContentView == null || mContext == null || mWindowManager == null) { |
| throw new IllegalStateException("You must specify a valid content view by " |
| + "calling setContentView() before attempting to show the popup."); |
| } |
| |
| if (mBackground != null) { |
| final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); |
| int height = ViewGroup.LayoutParams.MATCH_PARENT; |
| if (layoutParams != null && |
| layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { |
| height = ViewGroup.LayoutParams.WRAP_CONTENT; |
| } |
| |
| // when a background is available, we embed the content view |
| // within another view that owns the background drawable |
| PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); |
| PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( |
| ViewGroup.LayoutParams.MATCH_PARENT, height |
| ); |
| popupViewContainer.setBackgroundDrawable(mBackground); |
| popupViewContainer.addView(mContentView, listParams); |
| |
| mPopupView = popupViewContainer; |
| } else { |
| mPopupView = mContentView; |
| } |
| mPopupViewInitialLayoutDirectionInherited = |
| (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); |
| mPopupWidth = p.width; |
| mPopupHeight = p.height; |
| } |
| |
| /** |
| * <p>Invoke the popup window by adding the content view to the window |
| * manager.</p> |
| * |
| * <p>The content view must be non-null when this method is invoked.</p> |
| * |
| * @param p the layout parameters of the popup's content view |
| */ |
| private void invokePopup(WindowManager.LayoutParams p) { |
| if (mContext != null) { |
| p.packageName = mContext.getPackageName(); |
| } |
| mPopupView.setFitsSystemWindows(mLayoutInsetDecor); |
| setLayoutDirectionFromAnchor(); |
| mWindowManager.addView(mPopupView, p); |
| } |
| |
| private void setLayoutDirectionFromAnchor() { |
| if (mAnchor != null) { |
| View anchor = mAnchor.get(); |
| if (anchor != null && mPopupViewInitialLayoutDirectionInherited) { |
| mPopupView.setLayoutDirection(anchor.getLayoutDirection()); |
| } |
| } |
| } |
| |
| /** |
| * <p>Generate the layout parameters for the popup window.</p> |
| * |
| * @param token the window token used to bind the popup's window |
| * |
| * @return the layout parameters to pass to the window manager |
| */ |
| private WindowManager.LayoutParams createPopupLayout(IBinder token) { |
| // generates the layout parameters for the drop down |
| // we want a fixed size view located at the bottom left of the anchor |
| WindowManager.LayoutParams p = new WindowManager.LayoutParams(); |
| // these gravity settings put the view at the top left corner of the |
| // screen. The view is then positioned to the appropriate location |
| // by setting the x and y offsets to match the anchor's bottom |
| // left corner |
| p.gravity = Gravity.START | Gravity.TOP; |
| p.width = mLastWidth = mWidth; |
| p.height = mLastHeight = mHeight; |
| if (mBackground != null) { |
| p.format = mBackground.getOpacity(); |
| } else { |
| p.format = PixelFormat.TRANSLUCENT; |
| } |
| p.flags = computeFlags(p.flags); |
| p.type = mWindowLayoutType; |
| p.token = token; |
| p.softInputMode = mSoftInputMode; |
| p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); |
| |
| return p; |
| } |
| |
| private int computeFlags(int curFlags) { |
| curFlags &= ~( |
| WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | |
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | |
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | |
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | |
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | |
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | |
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH); |
| if(mIgnoreCheekPress) { |
| curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; |
| } |
| if (!mFocusable) { |
| curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; |
| if (mInputMethodMode == INPUT_METHOD_NEEDED) { |
| curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; |
| } |
| } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { |
| curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; |
| } |
| if (!mTouchable) { |
| curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; |
| } |
| if (mOutsideTouchable) { |
| curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; |
| } |
| if (!mClippingEnabled) { |
| curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; |
| } |
| if (isSplitTouchEnabled()) { |
| curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; |
| } |
| if (mLayoutInScreen) { |
| curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; |
| } |
| if (mLayoutInsetDecor) { |
| curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; |
| } |
| if (mNotTouchModal) { |
| curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; |
| } |
| return curFlags; |
| } |
| |
| private int computeAnimationResource() { |
| if (mAnimationStyle == -1) { |
| if (mIsDropdown) { |
| return mAboveAnchor |
| ? com.android.internal.R.style.Animation_DropDownUp |
| : com.android.internal.R.style.Animation_DropDownDown; |
| } |
| return 0; |
| } |
| return mAnimationStyle; |
| } |
| |
| /** |
| * <p>Positions the popup window on screen. When the popup window is too |
| * tall to fit under the anchor, a parent scroll view is seeked and scrolled |
| * up to reclaim space. If scrolling is not possible or not enough, the |
| * popup window gets moved on top of the anchor.</p> |
| * |
| * <p>The height must have been set on the layout parameters prior to |
| * calling this method.</p> |
| * |
| * @param anchor the view on which the popup window must be anchored |
| * @param p the layout parameters used to display the drop down |
| * |
| * @return true if the popup is translated upwards to fit on screen |
| */ |
| private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, |
| int xoff, int yoff) { |
| |
| final int anchorHeight = anchor.getHeight(); |
| anchor.getLocationInWindow(mDrawingLocation); |
| p.x = mDrawingLocation[0] + xoff; |
| p.y = mDrawingLocation[1] + anchorHeight + yoff; |
| |
| boolean onTop = false; |
| |
| p.gravity = Gravity.START | Gravity.TOP; |
| |
| anchor.getLocationOnScreen(mScreenLocation); |
| final Rect displayFrame = new Rect(); |
| anchor.getWindowVisibleDisplayFrame(displayFrame); |
| |
| int screenY = mScreenLocation[1] + anchorHeight + yoff; |
| |
| final View root = anchor.getRootView(); |
| if (screenY + mPopupHeight > displayFrame.bottom || |
| p.x + mPopupWidth - root.getWidth() > 0) { |
| // if the drop down disappears at the bottom of the screen. we try to |
| // scroll a parent scrollview or move the drop down back up on top of |
| // the edit box |
| if (mAllowScrollingAnchorParent) { |
| int scrollX = anchor.getScrollX(); |
| int scrollY = anchor.getScrollY(); |
| Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff, |
| scrollY + mPopupHeight + anchor.getHeight() + yoff); |
| anchor.requestRectangleOnScreen(r, true); |
| } |
| |
| // now we re-evaluate the space available, and decide from that |
| // whether the pop-up will go above or below the anchor. |
| anchor.getLocationInWindow(mDrawingLocation); |
| p.x = mDrawingLocation[0] + xoff; |
| p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; |
| |
| // determine whether there is more space above or below the anchor |
| anchor.getLocationOnScreen(mScreenLocation); |
| |
| onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getHeight() - yoff) < |
| (mScreenLocation[1] - yoff - displayFrame.top); |
| if (onTop) { |
| p.gravity = Gravity.START | Gravity.BOTTOM; |
| p.y = root.getHeight() - mDrawingLocation[1] + yoff; |
| } else { |
| p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; |
| } |
| } |
| |
| if (mClipToScreen) { |
| final int displayFrameWidth = displayFrame.right - displayFrame.left; |
| |
| int right = p.x + p.width; |
| if (right > displayFrameWidth) { |
| p.x -= right - displayFrameWidth; |
| } |
| if (p.x < displayFrame.left) { |
| p.x = displayFrame.left; |
| p.width = Math.min(p.width, displayFrameWidth); |
| } |
| |
| if (onTop) { |
| int popupTop = mScreenLocation[1] + yoff - mPopupHeight; |
| if (popupTop < 0) { |
| p.y += popupTop; |
| } |
| } else { |
| p.y = Math.max(p.y, displayFrame.top); |
| } |
| } |
| |
| p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; |
| |
| return onTop; |
| } |
| |
| /** |
| * Returns the maximum height that is available for the popup to be |
| * completely shown. It is recommended that this height be the maximum for |
| * the popup's height, otherwise it is possible that the popup will be |
| * clipped. |
| * |
| * @param anchor The view on which the popup window must be anchored. |
| * @return The maximum available height for the popup to be completely |
| * shown. |
| */ |
| public int getMaxAvailableHeight(View anchor) { |
| return getMaxAvailableHeight(anchor, 0); |
| } |
| |
| /** |
| * Returns the maximum height that is available for the popup to be |
| * completely shown. It is recommended that this height be the maximum for |
| * the popup's height, otherwise it is possible that the popup will be |
| * clipped. |
| * |
| * @param anchor The view on which the popup window must be anchored. |
| * @param yOffset y offset from the view's bottom edge |
| * @return The maximum available height for the popup to be completely |
| * shown. |
| */ |
| public int getMaxAvailableHeight(View anchor, int yOffset) { |
| return getMaxAvailableHeight(anchor, yOffset, false); |
| } |
| |
| /** |
| * Returns the maximum height that is available for the popup to be |
| * completely shown, optionally ignoring any bottom decorations such as |
| * the input method. It is recommended that this height be the maximum for |
| * the popup's height, otherwise it is possible that the popup will be |
| * clipped. |
| * |
| * @param anchor The view on which the popup window must be anchored. |
| * @param yOffset y offset from the view's bottom edge |
| * @param ignoreBottomDecorations if true, the height returned will be |
| * all the way to the bottom of the display, ignoring any |
| * bottom decorations |
| * @return The maximum available height for the popup to be completely |
| * shown. |
| * |
| * @hide Pending API council approval. |
| */ |
| public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) { |
| final Rect displayFrame = new Rect(); |
| anchor.getWindowVisibleDisplayFrame(displayFrame); |
| |
| final int[] anchorPos = mDrawingLocation; |
| anchor.getLocationOnScreen(anchorPos); |
| |
| int bottomEdge = displayFrame.bottom; |
| if (ignoreBottomDecorations) { |
| Resources res = anchor.getContext().getResources(); |
| bottomEdge = res.getDisplayMetrics().heightPixels; |
| } |
| final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; |
| final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; |
| |
| // anchorPos[1] is distance from anchor to top of screen |
| int returnedHeight = Math.max(distanceToBottom, distanceToTop); |
| if (mBackground != null) { |
| mBackground.getPadding(mTempRect); |
| returnedHeight -= mTempRect.top + mTempRect.bottom; |
| } |
| |
| return returnedHeight; |
| } |
| |
| /** |
| * <p>Dispose of the popup window. This method can be invoked only after |
| * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling |
| * this method will have no effect.</p> |
| * |
| * @see #showAsDropDown(android.view.View) |
| */ |
| public void dismiss() { |
| if (isShowing() && mPopupView != null) { |
| mIsShowing = false; |
| |
| unregisterForScrollChanged(); |
| |
| try { |
| mWindowManager.removeViewImmediate(mPopupView); |
| } finally { |
| if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { |
| ((ViewGroup) mPopupView).removeView(mContentView); |
| } |
| mPopupView = null; |
| |
| if (mOnDismissListener != null) { |
| mOnDismissListener.onDismiss(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sets the listener to be called when the window is dismissed. |
| * |
| * @param onDismissListener The listener. |
| */ |
| public void setOnDismissListener(OnDismissListener onDismissListener) { |
| mOnDismissListener = onDismissListener; |
| } |
| |
| /** |
| * Updates the state of the popup window, if it is currently being displayed, |
| * from the currently set state. This include: |
| * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)}, |
| * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)}, |
| * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}. |
| */ |
| public void update() { |
| if (!isShowing() || mContentView == null) { |
| return; |
| } |
| |
| WindowManager.LayoutParams p = (WindowManager.LayoutParams) |
| mPopupView.getLayoutParams(); |
| |
| boolean update = false; |
| |
| final int newAnim = computeAnimationResource(); |
| if (newAnim != p.windowAnimations) { |
| p.windowAnimations = newAnim; |
| update = true; |
| } |
| |
| final int newFlags = computeFlags(p.flags); |
| if (newFlags != p.flags) { |
| p.flags = newFlags; |
| update = true; |
| } |
| |
| if (update) { |
| setLayoutDirectionFromAnchor(); |
| mWindowManager.updateViewLayout(mPopupView, p); |
| } |
| } |
| |
| /** |
| * <p>Updates the dimension of the popup window. Calling this function |
| * also updates the window with the current popup state as described |
| * for {@link #update()}.</p> |
| * |
| * @param width the new width |
| * @param height the new height |
| */ |
| public void update(int width, int height) { |
| WindowManager.LayoutParams p = (WindowManager.LayoutParams) |
| mPopupView.getLayoutParams(); |
| update(p.x, p.y, width, height, false); |
| } |
| |
| /** |
| * <p>Updates the position and the dimension of the popup window. Width and |
| * height can be set to -1 to update location only. Calling this function |
| * also updates the window with the current popup state as |
| * described for {@link #update()}.</p> |
| * |
| * @param x the new x location |
| * @param y the new y location |
| * @param width the new width, can be -1 to ignore |
| * @param height the new height, can be -1 to ignore |
| */ |
| public void update(int x, int y, int width, int height) { |
| update(x, y, width, height, false); |
| } |
| |
| /** |
| * <p>Updates the position and the dimension of the popup window. Width and |
| * height can be set to -1 to update location only. Calling this function |
| * also updates the window with the current popup state as |
| * described for {@link #update()}.</p> |
| * |
| * @param x the new x location |
| * @param y the new y location |
| * @param width the new width, can be -1 to ignore |
| * @param height the new height, can be -1 to ignore |
| * @param force reposition the window even if the specified position |
| * already seems to correspond to the LayoutParams |
| */ |
| public void update(int x, int y, int width, int height, boolean force) { |
| if (width != -1) { |
| mLastWidth = width; |
| setWidth(width); |
| } |
| |
| if (height != -1) { |
| mLastHeight = height; |
| setHeight(height); |
| } |
| |
| if (!isShowing() || mContentView == null) { |
| return; |
| } |
| |
| WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); |
| |
| boolean update = force; |
| |
| final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; |
| if (width != -1 && p.width != finalWidth) { |
| p.width = mLastWidth = finalWidth; |
| update = true; |
| } |
| |
| final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; |
| if (height != -1 && p.height != finalHeight) { |
| p.height = mLastHeight = finalHeight; |
| update = true; |
| } |
| |
| if (p.x != x) { |
| p.x = x; |
| update = true; |
| } |
| |
| if (p.y != y) { |
| p.y = y; |
| update = true; |
| } |
| |
| final int newAnim = computeAnimationResource(); |
| if (newAnim != p.windowAnimations) { |
| p.windowAnimations = newAnim; |
| update = true; |
| } |
| |
| final int newFlags = computeFlags(p.flags); |
| if (newFlags != p.flags) { |
| p.flags = newFlags; |
| update = true; |
| } |
| |
| if (update) { |
| setLayoutDirectionFromAnchor(); |
| mWindowManager.updateViewLayout(mPopupView, p); |
| } |
| } |
| |
| /** |
| * <p>Updates the position and the dimension of the popup window. Calling this |
| * function also updates the window with the current popup state as described |
| * for {@link #update()}.</p> |
| * |
| * @param anchor the popup's anchor view |
| * @param width the new width, can be -1 to ignore |
| * @param height the new height, can be -1 to ignore |
| */ |
| public void update(View anchor, int width, int height) { |
| update(anchor, false, 0, 0, true, width, height); |
| } |
| |
| /** |
| * <p>Updates the position and the dimension of the popup window. Width and |
| * height can be set to -1 to update location only. Calling this function |
| * also updates the window with the current popup state as |
| * described for {@link #update()}.</p> |
| * |
| * <p>If the view later scrolls to move <code>anchor</code> to a different |
| * location, the popup will be moved correspondingly.</p> |
| * |
| * @param anchor the popup's anchor view |
| * @param xoff x offset from the view's left edge |
| * @param yoff y offset from the view's bottom edge |
| * @param width the new width, can be -1 to ignore |
| * @param height the new height, can be -1 to ignore |
| */ |
| public void update(View anchor, int xoff, int yoff, int width, int height) { |
| update(anchor, true, xoff, yoff, true, width, height); |
| } |
| |
| private void update(View anchor, boolean updateLocation, int xoff, int yoff, |
| boolean updateDimension, int width, int height) { |
| |
| if (!isShowing() || mContentView == null) { |
| return; |
| } |
| |
| WeakReference<View> oldAnchor = mAnchor; |
| final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff); |
| if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) { |
| registerForScrollChanged(anchor, xoff, yoff); |
| } else if (needsUpdate) { |
| // No need to register again if this is a DropDown, showAsDropDown already did. |
| mAnchorXoff = xoff; |
| mAnchorYoff = yoff; |
| } |
| |
| WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); |
| |
| if (updateDimension) { |
| if (width == -1) { |
| width = mPopupWidth; |
| } else { |
| mPopupWidth = width; |
| } |
| if (height == -1) { |
| height = mPopupHeight; |
| } else { |
| mPopupHeight = height; |
| } |
| } |
| |
| int x = p.x; |
| int y = p.y; |
| |
| if (updateLocation) { |
| updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff)); |
| } else { |
| updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff)); |
| } |
| |
| update(p.x, p.y, width, height, x != p.x || y != p.y); |
| } |
| |
| /** |
| * Listener that is called when this popup window is dismissed. |
| */ |
| public interface OnDismissListener { |
| /** |
| * Called when this popup window is dismissed. |
| */ |
| public void onDismiss(); |
| } |
| |
| private void unregisterForScrollChanged() { |
| WeakReference<View> anchorRef = mAnchor; |
| View anchor = null; |
| if (anchorRef != null) { |
| anchor = anchorRef.get(); |
| } |
| if (anchor != null) { |
| ViewTreeObserver vto = anchor.getViewTreeObserver(); |
| vto.removeOnScrollChangedListener(mOnScrollChangedListener); |
| } |
| mAnchor = null; |
| } |
| |
| private void registerForScrollChanged(View anchor, int xoff, int yoff) { |
| unregisterForScrollChanged(); |
| |
| mAnchor = new WeakReference<View>(anchor); |
| ViewTreeObserver vto = anchor.getViewTreeObserver(); |
| if (vto != null) { |
| vto.addOnScrollChangedListener(mOnScrollChangedListener); |
| } |
| |
| mAnchorXoff = xoff; |
| mAnchorYoff = yoff; |
| } |
| |
| private class PopupViewContainer extends FrameLayout { |
| private static final String TAG = "PopupWindow.PopupViewContainer"; |
| |
| public PopupViewContainer(Context context) { |
| super(context); |
| } |
| |
| @Override |
| protected int[] onCreateDrawableState(int extraSpace) { |
| if (mAboveAnchor) { |
| // 1 more needed for the above anchor state |
| final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); |
| View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); |
| return drawableState; |
| } else { |
| return super.onCreateDrawableState(extraSpace); |
| } |
| } |
| |
| @Override |
| public boolean dispatchKeyEvent(KeyEvent event) { |
| if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { |
| if (getKeyDispatcherState() == null) { |
| return super.dispatchKeyEvent(event); |
| } |
| |
| if (event.getAction() == KeyEvent.ACTION_DOWN |
| && event.getRepeatCount() == 0) { |
| KeyEvent.DispatcherState state = getKeyDispatcherState(); |
| if (state != null) { |
| state.startTracking(event, this); |
| } |
| return true; |
| } else if (event.getAction() == KeyEvent.ACTION_UP) { |
| KeyEvent.DispatcherState state = getKeyDispatcherState(); |
| if (state != null && state.isTracking(event) && !event.isCanceled()) { |
| dismiss(); |
| return true; |
| } |
| } |
| return super.dispatchKeyEvent(event); |
| } else { |
| return super.dispatchKeyEvent(event); |
| } |
| } |
| |
| @Override |
| public boolean dispatchTouchEvent(MotionEvent ev) { |
| if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { |
| return true; |
| } |
| return super.dispatchTouchEvent(ev); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| final int x = (int) event.getX(); |
| final int y = (int) event.getY(); |
| |
| if ((event.getAction() == MotionEvent.ACTION_DOWN) |
| && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { |
| dismiss(); |
| return true; |
| } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { |
| dismiss(); |
| return true; |
| } else { |
| return super.onTouchEvent(event); |
| } |
| } |
| |
| @Override |
| public void sendAccessibilityEvent(int eventType) { |
| // clinets are interested in the content not the container, make it event source |
| if (mContentView != null) { |
| mContentView.sendAccessibilityEvent(eventType); |
| } else { |
| super.sendAccessibilityEvent(eventType); |
| } |
| } |
| } |
| |
| } |