1476 lines
52 KiB
Java
1476 lines
52 KiB
Java
|
/*
|
||
|
* 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.TypedArray;
|
||
|
import android.view.KeyEvent;
|
||
|
import android.view.MotionEvent;
|
||
|
import android.view.View;
|
||
|
import android.view.WindowManager;
|
||
|
import android.view.Gravity;
|
||
|
import android.view.ViewGroup;
|
||
|
import android.view.ViewTreeObserver;
|
||
|
import android.view.ViewTreeObserver.OnScrollChangedListener;
|
||
|
import android.view.View.OnTouchListener;
|
||
|
import android.graphics.PixelFormat;
|
||
|
import android.graphics.Rect;
|
||
|
import android.graphics.drawable.Drawable;
|
||
|
import android.graphics.drawable.StateListDrawable;
|
||
|
import android.os.IBinder;
|
||
|
import android.util.AttributeSet;
|
||
|
|
||
|
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;
|
||
|
private boolean mTouchable = true;
|
||
|
private boolean mOutsideTouchable = false;
|
||
|
private boolean mClippingEnabled = true;
|
||
|
private boolean mSplitTouchEnabled;
|
||
|
private boolean mLayoutInScreen;
|
||
|
|
||
|
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.get();
|
||
|
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;
|
||
|
|
||
|
/**
|
||
|
* <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) {
|
||
|
mContext = context;
|
||
|
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
|
||
|
|
||
|
TypedArray a =
|
||
|
context.obtainStyledAttributes(
|
||
|
attrs, com.android.internal.R.styleable.PopupWindow, defStyle, 0);
|
||
|
|
||
|
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. To
|
||
|
* apply it while a popup is showing, call </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) {
|
||
|
mContext = mContentView.getContext();
|
||
|
}
|
||
|
|
||
|
if (mWindowManager == 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>Indicates whether the popup window supports splitting touches.</p>
|
||
|
*
|
||
|
* @return true if the touch splitting is enabled, false otherwise
|
||
|
*
|
||
|
* @see #setSplitTouchEnabled(boolean)
|
||
|
* @hide
|
||
|
*/
|
||
|
public boolean isSplitTouchEnabled() {
|
||
|
return mSplitTouchEnabled;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>Allows the popup window to split touches across other windows that also
|
||
|
* support split touch. When this flag is not set, the first pointer
|
||
|
* that goes down determines the window to which all subsequent touches
|
||
|
* go until all pointers go up. When this flag is set, 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()
|
||
|
* @hide
|
||
|
*/
|
||
|
public void setSplitTouchEnabled(boolean enabled) {
|
||
|
mSplitTouchEnabled = enabled;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <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) {
|
||
|
if (isShowing() || mContentView == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
unregisterForScrollChanged();
|
||
|
|
||
|
mIsShowing = true;
|
||
|
mIsDropdown = false;
|
||
|
|
||
|
WindowManager.LayoutParams p = createPopupLayout(parent.getWindowToken());
|
||
|
p.windowAnimations = computeAnimationResource();
|
||
|
|
||
|
preparePopup(p);
|
||
|
if (gravity == Gravity.NO_GRAVITY) {
|
||
|
gravity = Gravity.TOP | Gravity.LEFT;
|
||
|
}
|
||
|
p.gravity = gravity;
|
||
|
p.x = x;
|
||
|
p.y = y;
|
||
|
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;
|
||
|
}
|
||
|
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) {
|
||
|
p.packageName = mContext.getPackageName();
|
||
|
mWindowManager.addView(mPopupView, p);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <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.LEFT | 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 (mSplitTouchEnabled) {
|
||
|
curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
|
||
|
}
|
||
|
if (mLayoutInScreen) {
|
||
|
curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
|
||
|
}
|
||
|
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) {
|
||
|
|
||
|
anchor.getLocationInWindow(mDrawingLocation);
|
||
|
p.x = mDrawingLocation[0] + xoff;
|
||
|
p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
|
||
|
|
||
|
boolean onTop = false;
|
||
|
|
||
|
p.gravity = Gravity.LEFT | Gravity.TOP;
|
||
|
|
||
|
anchor.getLocationOnScreen(mScreenLocation);
|
||
|
final Rect displayFrame = new Rect();
|
||
|
anchor.getWindowVisibleDisplayFrame(displayFrame);
|
||
|
|
||
|
final View root = anchor.getRootView();
|
||
|
if (p.y + 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
|
||
|
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.LEFT | Gravity.BOTTOM;
|
||
|
p.y = root.getHeight() - mDrawingLocation[1] + yoff;
|
||
|
} else {
|
||
|
p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
bottomEdge = anchor.getContext().getResources().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) {
|
||
|
unregisterForScrollChanged();
|
||
|
|
||
|
try {
|
||
|
mWindowManager.removeView(mPopupView);
|
||
|
} finally {
|
||
|
if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
|
||
|
((ViewGroup) mPopupView).removeView(mContentView);
|
||
|
}
|
||
|
mPopupView = null;
|
||
|
mIsShowing = false;
|
||
|
|
||
|
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) {
|
||
|
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) {
|
||
|
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;
|
||
|
if (oldAnchor == null || oldAnchor.get() != anchor ||
|
||
|
(updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff))) {
|
||
|
registerForScrollChanged(anchor, xoff, 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 {
|
||
|
|
||
|
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 (event.getAction() == KeyEvent.ACTION_DOWN
|
||
|
&& event.getRepeatCount() == 0) {
|
||
|
getKeyDispatcherState().startTracking(event, this);
|
||
|
return true;
|
||
|
} else if (event.getAction() == KeyEvent.ACTION_UP
|
||
|
&& getKeyDispatcherState().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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|