home | career | drupal | java | mac | mysql | perl | scala | uml | unix

Android example source code file (PopupWindow.java)

This example Android source code file (PopupWindow.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Android by Example" TM.

Java - Android tags/keywords

above_anchor_state_set, android, content, drawable, drawing, graphics, input_method_from_focusable, input_method_needed, ondismisslistener, onscrollchangedlistener, override, paint, popupviewcontainer, popupwindow, rect, statelistdrawable, ui, view, weakreference, windowmanager

The PopupWindow.java Android example source code

/*
 * 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 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 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>The popup does provide a background.

*/ public PopupWindow(Context context) { this(context, null); } /** * <p>Create a new empty, non focusable popup window of dimension (0,0).

* * <p>The popup does provide a background.

*/ 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>The popup does provide a background.

*/ 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); // 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>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. The dimension of the window are (0,0).

* * <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. 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 contentView. * 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.

* * @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

* * @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>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.

* * @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.

* * @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.

* * @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.

* * @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>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

* * @return the height MeasureSpec of the popup * * @see #setHeight(int) */ public int getHeight() { return mHeight; } /** * <p>Change the popup's height MeasureSpec

* * <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

* * @return the width MeasureSpec of the popup * * @see #setWidth(int) */ public int getWidth() { return mWidth; } /** * <p>Change the popup's width MeasureSpec

* * <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.

* * @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. * </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 anchor 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.

* * @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.

* * @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 = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 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); 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; } 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 anchor 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); } } } }

Other Android examples (source code examples)

Here is a short list of links related to this Android PopupWindow.java source code file:

new blog posts

 

Copyright 1998-2014 Alvin Alexander, alvinalexander.com
All Rights Reserved.