/*
 * Copyright (C) 2010 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 android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.AdapterView.OnItemSelectedListener;

import com.android.internal.R;
import com.android.internal.view.menu.ShowableListMenu;

A ListPopupWindow anchors itself to a host view and displays a list of choices.

ListPopupWindow contains a number of tricky behaviors surrounding positioning, scrolling parents to fit the dropdown, interacting sanely with the IME if present, and others.

See Also:
/** * A ListPopupWindow anchors itself to a host view and displays a * list of choices. * * <p>ListPopupWindow contains a number of tricky behaviors surrounding * positioning, scrolling parents to fit the dropdown, interacting * sanely with the IME if present, and others. * * @see android.widget.AutoCompleteTextView * @see android.widget.Spinner */
public class ListPopupWindow implements ShowableListMenu { private static final String TAG = "ListPopupWindow"; private static final boolean DEBUG = false;
This value controls the length of time that the user must leave a pointer down without scrolling to expand the autocomplete dropdown list to cover the IME.
/** * This value controls the length of time that the user * must leave a pointer down without scrolling to expand * the autocomplete dropdown list to cover the IME. */
private static final int EXPAND_LIST_TIMEOUT = 250; private Context mContext; private ListAdapter mAdapter; private DropDownListView mDropDownList; private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT; private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT; private int mDropDownHorizontalOffset; private int mDropDownVerticalOffset; private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; private boolean mDropDownVerticalOffsetSet; private boolean mIsAnimatedFromAnchor = true; private boolean mOverlapAnchor; private boolean mOverlapAnchorSet; private int mDropDownGravity = Gravity.NO_GRAVITY; private boolean mDropDownAlwaysVisible = false; private boolean mForceIgnoreOutsideTouch = false; int mListItemExpandMaximum = Integer.MAX_VALUE; private View mPromptView; private int mPromptPosition = POSITION_PROMPT_ABOVE; private DataSetObserver mObserver; private View mDropDownAnchorView; private Drawable mDropDownListHighlight; private AdapterView.OnItemClickListener mItemClickListener; private AdapterView.OnItemSelectedListener mItemSelectedListener; private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable(); private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor(); private final PopupScrollListener mScrollListener = new PopupScrollListener(); private final ListSelectorHider mHideSelector = new ListSelectorHider(); private Runnable mShowDropDownRunnable; private final Handler mHandler; private final Rect mTempRect = new Rect();
Optional anchor-relative bounds to be used as the transition epicenter. When null, the anchor bounds are used as the epicenter.
/** * Optional anchor-relative bounds to be used as the transition epicenter. * When {@code null}, the anchor bounds are used as the epicenter. */
private Rect mEpicenterBounds; private boolean mModal; PopupWindow mPopup;
The provided prompt view should appear above list content.
See Also:
/** * The provided prompt view should appear above list content. * * @see #setPromptPosition(int) * @see #getPromptPosition() * @see #setPromptView(View) */
public static final int POSITION_PROMPT_ABOVE = 0;
The provided prompt view should appear below list content.
See Also:
/** * The provided prompt view should appear below list content. * * @see #setPromptPosition(int) * @see #getPromptPosition() * @see #setPromptView(View) */
public static final int POSITION_PROMPT_BELOW = 1;
Alias for LayoutParams.MATCH_PARENT. If used to specify a popup width, the popup will match the width of the anchor view. If used to specify a popup height, the popup will fill available space.
/** * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}. * If used to specify a popup width, the popup will match the width of the anchor view. * If used to specify a popup height, the popup will fill available space. */
public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
Alias for LayoutParams.WRAP_CONTENT. If used to specify a popup width, the popup will use the width of its content.
/** * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}. * If used to specify a popup width, the popup will use the width of its content. */
public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
Mode for 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.
/** * 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 = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
Mode for 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.
/** * 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 = PopupWindow.INPUT_METHOD_NEEDED;
Mode for 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.
/** * 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 = PopupWindow.INPUT_METHOD_NOT_NEEDED;
Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds should be set using setBackgroundDrawable(Drawable).
Params:
  • context – Context used for contained views.
/** * Create a new, empty popup window capable of displaying items from a ListAdapter. * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. * * @param context Context used for contained views. */
public ListPopupWindow(@NonNull Context context) { this(context, null, com.android.internal.R.attr.listPopupWindowStyle, 0); }
Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds should be set using setBackgroundDrawable(Drawable).
Params:
  • context – Context used for contained views.
  • attrs – Attributes from inflating parent views used to style the popup.
/** * Create a new, empty popup window capable of displaying items from a ListAdapter. * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. * * @param context Context used for contained views. * @param attrs Attributes from inflating parent views used to style the popup. */
public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.listPopupWindowStyle, 0); }
Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds should be set using setBackgroundDrawable(Drawable).
Params:
  • context – Context used for contained views.
  • attrs – Attributes from inflating parent views used to style the popup.
  • defStyleAttr – Default style attribute to use for popup content.
/** * Create a new, empty popup window capable of displaying items from a ListAdapter. * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. * * @param context Context used for contained views. * @param attrs Attributes from inflating parent views used to style the popup. * @param defStyleAttr Default style attribute to use for popup content. */
public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { this(context, attrs, defStyleAttr, 0); }
Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds should be set using setBackgroundDrawable(Drawable).
Params:
  • context – Context used for contained views.
  • attrs – Attributes from inflating parent views used to style the popup.
  • defStyleAttr – Style attribute to read for default styling of popup content.
  • defStyleRes – Style resource ID to use for default styling of popup content.
/** * Create a new, empty popup window capable of displaying items from a ListAdapter. * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}. * * @param context Context used for contained views. * @param attrs Attributes from inflating parent views used to style the popup. * @param defStyleAttr Style attribute to read for default styling of popup content. * @param defStyleRes Style resource ID to use for default styling of popup content. */
public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { mContext = context; mHandler = new Handler(context.getMainLooper()); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow, defStyleAttr, defStyleRes); mDropDownHorizontalOffset = a.getDimensionPixelOffset( R.styleable.ListPopupWindow_dropDownHorizontalOffset, 0); mDropDownVerticalOffset = a.getDimensionPixelOffset( R.styleable.ListPopupWindow_dropDownVerticalOffset, 0); if (mDropDownVerticalOffset != 0) { mDropDownVerticalOffsetSet = true; } a.recycle(); mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes); mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); }
Sets the adapter that provides the data and the views to represent the data in this popup window.
Params:
  • adapter – The adapter to use to create this window's content.
/** * Sets the adapter that provides the data and the views to represent the data * in this popup window. * * @param adapter The adapter to use to create this window's content. */
public void setAdapter(@Nullable ListAdapter adapter) { if (mObserver == null) { mObserver = new PopupDataSetObserver(); } else if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mObserver); } mAdapter = adapter; if (mAdapter != null) { adapter.registerDataSetObserver(mObserver); } if (mDropDownList != null) { mDropDownList.setAdapter(mAdapter); } }
Set where the optional prompt view should appear. The default is POSITION_PROMPT_ABOVE.
Params:
  • position – A position constant declaring where the prompt should be displayed.
See Also:
/** * Set where the optional prompt view should appear. The default is * {@link #POSITION_PROMPT_ABOVE}. * * @param position A position constant declaring where the prompt should be displayed. * * @see #POSITION_PROMPT_ABOVE * @see #POSITION_PROMPT_BELOW */
public void setPromptPosition(int position) { mPromptPosition = position; }
See Also:
Returns:Where the optional prompt view should appear.
/** * @return Where the optional prompt view should appear. * * @see #POSITION_PROMPT_ABOVE * @see #POSITION_PROMPT_BELOW */
public int getPromptPosition() { return mPromptPosition; }
Set whether this window should be modal when shown.

If a popup window is modal, it will receive all touch and key input. If the user touches outside the popup window's content area the popup window will be dismissed.

Params:
  • modal – true if the popup window should be modal, false otherwise.
/** * Set whether this window should be modal when shown. * * <p>If a popup window is modal, it will receive all touch and key input. * If the user touches outside the popup window's content area the popup window * will be dismissed. * * @param modal {@code true} if the popup window should be modal, {@code false} otherwise. */
public void setModal(boolean modal) { mModal = modal; mPopup.setFocusable(modal); }
Returns whether the popup window will be modal when shown.
Returns:true if the popup window will be modal, false otherwise.
/** * Returns whether the popup window will be modal when shown. * * @return {@code true} if the popup window will be modal, {@code false} otherwise. */
public boolean isModal() { return mModal; }
Forces outside touches to be ignored. Normally if isDropDownAlwaysVisible() is false, we allow outside touch to dismiss the dropdown. If this is set to true, then we ignore outside touch even when the drop down is not set to always visible.
@hideUsed only by AutoCompleteTextView to handle some internal special cases.
/** * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we * ignore outside touch even when the drop down is not set to always visible. * * @hide Used only by AutoCompleteTextView to handle some internal special cases. */
public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) { mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch; }
Sets whether the drop-down should remain visible under certain conditions. The drop-down will occupy the entire screen below getAnchorView regardless of the size or content of the list. getBackground() will fill any space that is not used by the list.
Params:
  • dropDownAlwaysVisible – Whether to keep the drop-down visible.
@hideOnly used by AutoCompleteTextView under special conditions.
/** * Sets whether the drop-down should remain visible under certain conditions. * * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless * of the size or content of the list. {@link #getBackground()} will fill any space * that is not used by the list. * * @param dropDownAlwaysVisible Whether to keep the drop-down visible. * * @hide Only used by AutoCompleteTextView under special conditions. */
public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) { mDropDownAlwaysVisible = dropDownAlwaysVisible; }
Returns:Whether the drop-down is visible under special conditions.
@hideOnly used by AutoCompleteTextView under special conditions.
/** * @return Whether the drop-down is visible under special conditions. * * @hide Only used by AutoCompleteTextView under special conditions. */
public boolean isDropDownAlwaysVisible() { return mDropDownAlwaysVisible; }
Sets the operating mode for the soft input area.
Params:
See Also:
/** * 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) { mPopup.setSoftInputMode(mode); }
Returns the current value in setSoftInputMode(int).
See Also:
/** * Returns the current value in {@link #setSoftInputMode(int)}. * * @see #setSoftInputMode(int) * @see android.view.WindowManager.LayoutParams#softInputMode */
public int getSoftInputMode() { return mPopup.getSoftInputMode(); }
Sets a drawable to use as the list item selector.
Params:
  • selector – List selector drawable to use in the popup.
/** * Sets a drawable to use as the list item selector. * * @param selector List selector drawable to use in the popup. */
public void setListSelector(Drawable selector) { mDropDownListHighlight = selector; }
Returns:The background drawable for the popup window.
/** * @return The background drawable for the popup window. */
public @Nullable Drawable getBackground() { return mPopup.getBackground(); }
Sets a drawable to be the background for the popup window.
Params:
  • d – A drawable to set as the background.
/** * Sets a drawable to be the background for the popup window. * * @param d A drawable to set as the background. */
public void setBackgroundDrawable(@Nullable Drawable d) { mPopup.setBackgroundDrawable(d); }
Set an animation style to use when the popup window is shown or dismissed.
Params:
  • animationStyle – Animation style to use.
/** * Set an animation style to use when the popup window is shown or dismissed. * * @param animationStyle Animation style to use. */
public void setAnimationStyle(@StyleRes int animationStyle) { mPopup.setAnimationStyle(animationStyle); }
Returns the animation style that will be used when the popup window is shown or dismissed.
Returns:Animation style that will be used.
/** * Returns the animation style that will be used when the popup window is * shown or dismissed. * * @return Animation style that will be used. */
public @StyleRes int getAnimationStyle() { return mPopup.getAnimationStyle(); }
Returns the view that will be used to anchor this popup.
Returns:The popup's anchor view
/** * Returns the view that will be used to anchor this popup. * * @return The popup's anchor view */
public @Nullable View getAnchorView() { return mDropDownAnchorView; }
Sets the popup's anchor view. This popup will always be positioned relative to the anchor view when shown.
Params:
  • anchor – The view to use as an anchor.
/** * Sets the popup's anchor view. This popup will always be positioned relative to * the anchor view when shown. * * @param anchor The view to use as an anchor. */
public void setAnchorView(@Nullable View anchor) { mDropDownAnchorView = anchor; }
Returns:The horizontal offset of the popup from its anchor in pixels.
/** * @return The horizontal offset of the popup from its anchor in pixels. */
public int getHorizontalOffset() { return mDropDownHorizontalOffset; }
Set the horizontal offset of this popup from its anchor view in pixels.
Params:
  • offset – The horizontal offset of the popup from its anchor.
/** * Set the horizontal offset of this popup from its anchor view in pixels. * * @param offset The horizontal offset of the popup from its anchor. */
public void setHorizontalOffset(int offset) { mDropDownHorizontalOffset = offset; }
Returns:The vertical offset of the popup from its anchor in pixels.
/** * @return The vertical offset of the popup from its anchor in pixels. */
public int getVerticalOffset() { if (!mDropDownVerticalOffsetSet) { return 0; } return mDropDownVerticalOffset; }
Set the vertical offset of this popup from its anchor view in pixels.
Params:
  • offset – The vertical offset of the popup from its anchor.
/** * Set the vertical offset of this popup from its anchor view in pixels. * * @param offset The vertical offset of the popup from its anchor. */
public void setVerticalOffset(int offset) { mDropDownVerticalOffset = offset; mDropDownVerticalOffsetSet = true; }
Specifies the anchor-relative bounds of the popup's transition epicenter.
Params:
  • bounds – anchor-relative bounds
@hide
/** * Specifies the anchor-relative bounds of the popup's transition * epicenter. * * @param bounds anchor-relative bounds * @hide */
public void setEpicenterBounds(Rect bounds) { mEpicenterBounds = bounds; }
Set the gravity of the dropdown list. This is commonly used to set gravity to START or END for alignment with the anchor.
Params:
  • gravity – Gravity value to use
/** * Set the gravity of the dropdown list. This is commonly used to * set gravity to START or END for alignment with the anchor. * * @param gravity Gravity value to use */
public void setDropDownGravity(int gravity) { mDropDownGravity = gravity; }
Returns:The width of the popup window in pixels.
/** * @return The width of the popup window in pixels. */
public int getWidth() { return mDropDownWidth; }
Sets the width of the popup window in pixels. Can also be MATCH_PARENT or WRAP_CONTENT.
Params:
  • width – Width of the popup window.
/** * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT} * or {@link #WRAP_CONTENT}. * * @param width Width of the popup window. */
public void setWidth(int width) { mDropDownWidth = width; }
Sets the width of the popup window by the size of its content. The final width may be larger to accommodate styled window dressing.
Params:
  • width – Desired width of content in pixels.
/** * Sets the width of the popup window by the size of its content. The final width may be * larger to accommodate styled window dressing. * * @param width Desired width of content in pixels. */
public void setContentWidth(int width) { Drawable popupBackground = mPopup.getBackground(); if (popupBackground != null) { popupBackground.getPadding(mTempRect); mDropDownWidth = mTempRect.left + mTempRect.right + width; } else { setWidth(width); } }
Returns:The height of the popup window in pixels.
/** * @return The height of the popup window in pixels. */
public int getHeight() { return mDropDownHeight; }
Sets the height of the popup window in pixels. Can also be MATCH_PARENT.
Params:
Throws:
/** * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}. * * @param height Height of the popup window must be a positive value, * {@link #MATCH_PARENT}, or {@link #WRAP_CONTENT}. * * @throws IllegalArgumentException if height is set to negative value */
public void setHeight(int height) { if (height < 0 && ViewGroup.LayoutParams.WRAP_CONTENT != height && ViewGroup.LayoutParams.MATCH_PARENT != height) { if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O) { Log.e(TAG, "Negative value " + height + " passed to ListPopupWindow#setHeight" + " produces undefined results"); } else { throw new IllegalArgumentException( "Invalid height. Must be a positive value, MATCH_PARENT, or WRAP_CONTENT."); } } mDropDownHeight = height; }
Set the layout type for this popup window.

See LayoutParams.type for possible values.

Params:
  • layoutType – Layout type for this window.
See Also:
/** * Set the layout type for this popup window. * <p> * See {@link WindowManager.LayoutParams#type} for possible values. * * @param layoutType Layout type for this window. * * @see WindowManager.LayoutParams#type */
public void setWindowLayoutType(int layoutType) { mDropDownWindowLayoutType = layoutType; }
Sets a listener to receive events when a list item is clicked.
Params:
  • clickListener – Listener to register
See Also:
/** * Sets a listener to receive events when a list item is clicked. * * @param clickListener Listener to register * * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener) */
public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener clickListener) { mItemClickListener = clickListener; }
Sets a listener to receive events when a list item is selected.
Params:
  • selectedListener – Listener to register.
See Also:
/** * Sets a listener to receive events when a list item is selected. * * @param selectedListener Listener to register. * * @see ListView#setOnItemSelectedListener(OnItemSelectedListener) */
public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) { mItemSelectedListener = selectedListener; }
Set a view to act as a user prompt for this popup window. Where the prompt view will appear is controlled by setPromptPosition(int).
Params:
  • prompt – View to use as an informational prompt.
/** * Set a view to act as a user prompt for this popup window. Where the prompt view will appear * is controlled by {@link #setPromptPosition(int)}. * * @param prompt View to use as an informational prompt. */
public void setPromptView(@Nullable View prompt) { boolean showing = isShowing(); if (showing) { removePromptView(); } mPromptView = prompt; if (showing) { show(); } }
Post a show() call to the UI thread.
/** * Post a {@link #show()} call to the UI thread. */
public void postShow() { mHandler.post(mShowDropDownRunnable); }
Show the popup list. If the list is already showing, this method will recalculate the popup's size and position.
/** * Show the popup list. If the list is already showing, this method * will recalculate the popup's size and position. */
@Override public void show() { int height = buildDropDown(); final boolean noInputMethod = isInputMethodNotNeeded(); mPopup.setAllowScrollingAnchorParent(!noInputMethod); mPopup.setWindowLayoutType(mDropDownWindowLayoutType); if (mPopup.isShowing()) { if (!getAnchorView().isAttachedToWindow()) { //Don't update position if the anchor view is detached from window. return; } final int widthSpec; if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { // The call to PopupWindow's update method below can accept -1 for any // value you do not want to update. widthSpec = -1; } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { widthSpec = getAnchorView().getWidth(); } else { widthSpec = mDropDownWidth; } final int heightSpec; if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { // The call to PopupWindow's update method below can accept -1 for any // value you do not want to update. heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT; if (noInputMethod) { mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? ViewGroup.LayoutParams.MATCH_PARENT : 0); mPopup.setHeight(0); } else { mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? ViewGroup.LayoutParams.MATCH_PARENT : 0); mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT); } } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { heightSpec = height; } else { heightSpec = mDropDownHeight; } mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); mPopup.update(getAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec, (heightSpec < 0)? -1 : heightSpec); mPopup.getContentView().restoreDefaultFocus(); } else { final int widthSpec; if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { widthSpec = ViewGroup.LayoutParams.MATCH_PARENT; } else { if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { widthSpec = getAnchorView().getWidth(); } else { widthSpec = mDropDownWidth; } } final int heightSpec; if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { heightSpec = ViewGroup.LayoutParams.MATCH_PARENT; } else { if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { heightSpec = height; } else { heightSpec = mDropDownHeight; } } mPopup.setWidth(widthSpec); mPopup.setHeight(heightSpec); mPopup.setClipToScreenEnabled(true); // use outside touchable to dismiss drop down when touching outside of it, so // only set this if the dropdown is not always visible mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); mPopup.setTouchInterceptor(mTouchInterceptor); mPopup.setEpicenterBounds(mEpicenterBounds); if (mOverlapAnchorSet) { mPopup.setOverlapAnchor(mOverlapAnchor); } mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, mDropDownGravity); mDropDownList.setSelection(ListView.INVALID_POSITION); mPopup.getContentView().restoreDefaultFocus(); if (!mModal || mDropDownList.isInTouchMode()) { clearListSelection(); } if (!mModal) { mHandler.post(mHideSelector); } } }
Dismiss the popup window.
/** * Dismiss the popup window. */
@Override public void dismiss() { mPopup.dismiss(); removePromptView(); mPopup.setContentView(null); mDropDownList = null; mHandler.removeCallbacks(mResizePopupRunnable); }
Set a listener to receive a callback when the popup is dismissed.
Params:
  • listener – Listener that will be notified when the popup is dismissed.
/** * Set a listener to receive a callback when the popup is dismissed. * * @param listener Listener that will be notified when the popup is dismissed. */
public void setOnDismissListener(@Nullable PopupWindow.OnDismissListener listener) { mPopup.setOnDismissListener(listener); } private void removePromptView() { if (mPromptView != null) { final ViewParent parent = mPromptView.getParent(); if (parent instanceof ViewGroup) { final ViewGroup group = (ViewGroup) parent; group.removeView(mPromptView); } } }
Control how the popup operates with an input method: one of INPUT_METHOD_FROM_FOCUSABLE, INPUT_METHOD_NEEDED, or INPUT_METHOD_NOT_NEEDED.

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 the show() method.

See Also:
/** * 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 the {@link #show()} * method.</p> * * @see #getInputMethodMode() * @see #show() */
public void setInputMethodMode(int mode) { mPopup.setInputMethodMode(mode); }
Return the current value in setInputMethodMode(int).
See Also:
/** * Return the current value in {@link #setInputMethodMode(int)}. * * @see #setInputMethodMode(int) */
public int getInputMethodMode() { return mPopup.getInputMethodMode(); }
Set the selected position of the list. Only valid when isShowing() == true.
Params:
  • position – List position to set as selected.
/** * Set the selected position of the list. * Only valid when {@link #isShowing()} == {@code true}. * * @param position List position to set as selected. */
public void setSelection(int position) { DropDownListView list = mDropDownList; if (isShowing() && list != null) { list.setListSelectionHidden(false); list.setSelection(position); if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) { list.setItemChecked(position, true); } } }
Clear any current list selection. Only valid when isShowing() == true.
/** * Clear any current list selection. * Only valid when {@link #isShowing()} == {@code true}. */
public void clearListSelection() { final DropDownListView list = mDropDownList; if (list != null) { // WARNING: Please read the comment where mListSelectionHidden is declared list.setListSelectionHidden(true); list.hideSelector(); list.requestLayout(); } }
Returns:true if the popup is currently showing, false otherwise.
/** * @return {@code true} if the popup is currently showing, {@code false} otherwise. */
@Override public boolean isShowing() { return mPopup.isShowing(); }
Returns:true if this popup is configured to assume the user does not need to interact with the IME while it is showing, false otherwise.
/** * @return {@code true} if this popup is configured to assume the user does not need * to interact with the IME while it is showing, {@code false} otherwise. */
public boolean isInputMethodNotNeeded() { return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED; }
Perform an item click operation on the specified list adapter position.
Params:
  • position – Adapter position for performing the click
Returns:true if the click action could be performed, false if not. (e.g. if the popup was not showing, this method would return false.)
/** * Perform an item click operation on the specified list adapter position. * * @param position Adapter position for performing the click * @return true if the click action could be performed, false if not. * (e.g. if the popup was not showing, this method would return false.) */
public boolean performItemClick(int position) { if (isShowing()) { if (mItemClickListener != null) { final DropDownListView list = mDropDownList; final View child = list.getChildAt(position - list.getFirstVisiblePosition()); final ListAdapter adapter = list.getAdapter(); mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position)); } return true; } return false; }
Returns:The currently selected item or null if the popup is not showing.
/** * @return The currently selected item or null if the popup is not showing. */
public @Nullable Object getSelectedItem() { if (!isShowing()) { return null; } return mDropDownList.getSelectedItem(); }
See Also:
Returns:The position of the currently selected item or AdapterView<ListAdapter>.INVALID_POSITION if isShowing() == false.
/** * @return The position of the currently selected item or {@link ListView#INVALID_POSITION} * if {@link #isShowing()} == {@code false}. * * @see ListView#getSelectedItemPosition() */
public int getSelectedItemPosition() { if (!isShowing()) { return ListView.INVALID_POSITION; } return mDropDownList.getSelectedItemPosition(); }
See Also:
Returns:The ID of the currently selected item or AdapterView<ListAdapter>.INVALID_ROW_ID if isShowing() == false.
/** * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID} * if {@link #isShowing()} == {@code false}. * * @see ListView#getSelectedItemId() */
public long getSelectedItemId() { if (!isShowing()) { return ListView.INVALID_ROW_ID; } return mDropDownList.getSelectedItemId(); }
See Also:
Returns:The View for the currently selected item or null if isShowing() == false.
/** * @return The View for the currently selected item or null if * {@link #isShowing()} == {@code false}. * * @see ListView#getSelectedView() */
public @Nullable View getSelectedView() { if (!isShowing()) { return null; } return mDropDownList.getSelectedView(); }
Returns:The ListView displayed within the popup window. Only valid when isShowing() == true.
/** * @return The {@link ListView} displayed within the popup window. * Only valid when {@link #isShowing()} == {@code true}. */
@Override public @Nullable ListView getListView() { return mDropDownList; } @NonNull DropDownListView createDropDownListView(Context context, boolean hijackFocus) { return new DropDownListView(context, hijackFocus); }
The maximum number of list items that can be visible and still have the list expand when touched.
Params:
  • max – Max number of items that can be visible and still allow the list to expand.
/** * The maximum number of list items that can be visible and still have * the list expand when touched. * * @param max Max number of items that can be visible and still allow the list to expand. */
void setListItemExpandMax(int max) { mListItemExpandMaximum = max; }
Filter key down events. By forwarding key down events to this function, views using non-modal ListPopupWindow can have it handle key selection of items.
Params:
  • keyCode – keyCode param passed to the host view's onKeyDown
  • event – event param passed to the host view's onKeyDown
See Also:
Returns:true if the event was handled, false if it was ignored.
/** * Filter key down events. By forwarding key down events to this function, * views using non-modal ListPopupWindow can have it handle key selection of items. * * @param keyCode keyCode param passed to the host view's onKeyDown * @param event event param passed to the host view's onKeyDown * @return true if the event was handled, false if it was ignored. * * @see #setModal(boolean) * @see #onKeyUp(int, KeyEvent) */
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { // when the drop down is shown, we drive it directly if (isShowing()) { // the key events are forwarded to the list in the drop down view // note that ListView handles space but we don't want that to happen // also if selection is not currently in the drop down, then don't // let center or enter presses go there since that would cause it // to select one of its items if (keyCode != KeyEvent.KEYCODE_SPACE && (mDropDownList.getSelectedItemPosition() >= 0 || !KeyEvent.isConfirmKey(keyCode))) { int curIndex = mDropDownList.getSelectedItemPosition(); boolean consumed; final boolean below = !mPopup.isAboveAnchor(); final ListAdapter adapter = mAdapter; boolean allEnabled; int firstItem = Integer.MAX_VALUE; int lastItem = Integer.MIN_VALUE; if (adapter != null) { allEnabled = adapter.areAllItemsEnabled(); firstItem = allEnabled ? 0 : mDropDownList.lookForSelectablePosition(0, true); lastItem = allEnabled ? adapter.getCount() - 1 : mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false); } if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) || (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) { // When the selection is at the top, we block the key // event to prevent focus from moving. clearListSelection(); mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); show(); return true; } else { // WARNING: Please read the comment where mListSelectionHidden // is declared mDropDownList.setListSelectionHidden(false); } consumed = mDropDownList.onKeyDown(keyCode, event); if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed); if (consumed) { // If it handled the key event, then the user is // navigating in the list, so we should put it in front. mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); // Here's a little trick we need to do to make sure that // the list view is actually showing its focus indicator, // by ensuring it has focus and getting its window out // of touch mode. mDropDownList.requestFocusFromTouch(); show(); switch (keyCode) { // avoid passing the focus from the text view to the // next component case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_UP: return true; } } else { if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { // when the selection is at the bottom, we block the // event to avoid going to the next focusable widget if (curIndex == lastItem) { return true; } } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex == firstItem) { return true; } } } } return false; }
Filter key up events. By forwarding key up events to this function, views using non-modal ListPopupWindow can have it handle key selection of items.
Params:
  • keyCode – keyCode param passed to the host view's onKeyUp
  • event – event param passed to the host view's onKeyUp
See Also:
Returns:true if the event was handled, false if it was ignored.
/** * Filter key up events. By forwarding key up events to this function, * views using non-modal ListPopupWindow can have it handle key selection of items. * * @param keyCode keyCode param passed to the host view's onKeyUp * @param event event param passed to the host view's onKeyUp * @return true if the event was handled, false if it was ignored. * * @see #setModal(boolean) * @see #onKeyDown(int, KeyEvent) */
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) { boolean consumed = mDropDownList.onKeyUp(keyCode, event); if (consumed && KeyEvent.isConfirmKey(keyCode)) { // if the list accepts the key events and the key event was a click, the text view // gets the selected item from the drop down as its content dismiss(); } return consumed; } return false; }
Filter pre-IME key events. By forwarding View.onKeyPreIme(int, KeyEvent) events to this function, views using ListPopupWindow can have it dismiss the popup when the back key is pressed.
Params:
  • keyCode – keyCode param passed to the host view's onKeyPreIme
  • event – event param passed to the host view's onKeyPreIme
See Also:
Returns:true if the event was handled, false if it was ignored.
/** * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)} * events to this function, views using ListPopupWindow can have it dismiss the popup * when the back key is pressed. * * @param keyCode keyCode param passed to the host view's onKeyPreIme * @param event event param passed to the host view's onKeyPreIme * @return true if the event was handled, false if it was ignored. * * @see #setModal(boolean) */
public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) { // special case for the back key, we do not even try to send it // to the drop down list but instead, consume it immediately final View anchorView = mDropDownAnchorView; if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState(); if (state != null) { state.startTracking(event, this); } return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState(); if (state != null) { state.handleUpEvent(event); } if (event.isTracking() && !event.isCanceled()) { dismiss(); return true; } } } return false; }
Returns an OnTouchListener that can be added to the source view to implement drag-to-open behavior. Generally, the source view should be the same view that was passed to setAnchorView.

When the listener is set on a view, touching that view and dragging outside of its bounds will open the popup window. Lifting will select the currently touched list item.

Example usage:

ListPopupWindow myPopup = new ListPopupWindow(context);
myPopup.setAnchor(myAnchor);
OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
myAnchor.setOnTouchListener(dragListener);
Params:
  • src – the view on which the resulting listener will be set
Returns:a touch listener that controls drag-to-open behavior
/** * Returns an {@link OnTouchListener} that can be added to the source view * to implement drag-to-open behavior. Generally, the source view should be * the same view that was passed to {@link #setAnchorView}. * <p> * When the listener is set on a view, touching that view and dragging * outside of its bounds will open the popup window. Lifting will select the * currently touched list item. * <p> * Example usage: * <pre> * ListPopupWindow myPopup = new ListPopupWindow(context); * myPopup.setAnchor(myAnchor); * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor); * myAnchor.setOnTouchListener(dragListener); * </pre> * * @param src the view on which the resulting listener will be set * @return a touch listener that controls drag-to-open behavior */
public OnTouchListener createDragToOpenListener(View src) { return new ForwardingListener(src) { @Override public ShowableListMenu getPopup() { return ListPopupWindow.this; } }; }

Builds the popup window's content and returns the height the popup should have. Returns -1 when the content already exists.

Returns:the content's height or -1 if content already exists
/** * <p>Builds the popup window's content and returns the height the popup * should have. Returns -1 when the content already exists.</p> * * @return the content's height or -1 if content already exists */
private int buildDropDown() { ViewGroup dropDownView; int otherHeights = 0; if (mDropDownList == null) { Context context = mContext; /** * This Runnable exists for the sole purpose of checking if the view layout has got * completed and if so call showDropDown to display the drop down. This is used to show * the drop down as soon as possible after user opens up the search dialog, without * waiting for the normal UI pipeline to do it's job which is slower than this method. */ mShowDropDownRunnable = new Runnable() { public void run() { // View layout should be all done before displaying the drop down. View view = getAnchorView(); if (view != null && view.getWindowToken() != null) { show(); } } }; mDropDownList = createDropDownListView(context, !mModal); if (mDropDownListHighlight != null) { mDropDownList.setSelector(mDropDownListHighlight); } mDropDownList.setAdapter(mAdapter); mDropDownList.setOnItemClickListener(mItemClickListener); mDropDownList.setFocusable(true); mDropDownList.setFocusableInTouchMode(true); mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (position != -1) { DropDownListView dropDownList = mDropDownList; if (dropDownList != null) { dropDownList.setListSelectionHidden(false); } } } public void onNothingSelected(AdapterView<?> parent) { } }); mDropDownList.setOnScrollListener(mScrollListener); if (mItemSelectedListener != null) { mDropDownList.setOnItemSelectedListener(mItemSelectedListener); } dropDownView = mDropDownList; View hintView = mPromptView; if (hintView != null) { // if a hint has been specified, we accomodate more space for it and // add a text view in the drop down menu, at the bottom of the list LinearLayout hintContainer = new LinearLayout(context); hintContainer.setOrientation(LinearLayout.VERTICAL); LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f ); switch (mPromptPosition) { case POSITION_PROMPT_BELOW: hintContainer.addView(dropDownView, hintParams); hintContainer.addView(hintView); break; case POSITION_PROMPT_ABOVE: hintContainer.addView(hintView); hintContainer.addView(dropDownView, hintParams); break; default: Log.e(TAG, "Invalid hint position " + mPromptPosition); break; } // Measure the hint's height to find how much more vertical // space we need to add to the drop down's height. final int widthSize; final int widthMode; if (mDropDownWidth >= 0) { widthMode = MeasureSpec.AT_MOST; widthSize = mDropDownWidth; } else { widthMode = MeasureSpec.UNSPECIFIED; widthSize = 0; } final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode); final int heightSpec = MeasureSpec.UNSPECIFIED; hintView.measure(widthSpec, heightSpec); hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams(); otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin + hintParams.bottomMargin; dropDownView = hintContainer; } mPopup.setContentView(dropDownView); } else { final View view = mPromptView; if (view != null) { LinearLayout.LayoutParams hintParams = (LinearLayout.LayoutParams) view.getLayoutParams(); otherHeights = view.getMeasuredHeight() + hintParams.topMargin + hintParams.bottomMargin; } } // getMaxAvailableHeight() subtracts the padding, so we put it back // to get the available height for the whole window. final int padding; final Drawable background = mPopup.getBackground(); if (background != null) { background.getPadding(mTempRect); padding = mTempRect.top + mTempRect.bottom; // If we don't have an explicit vertical offset, determine one from // the window background so that content will line up. if (!mDropDownVerticalOffsetSet) { mDropDownVerticalOffset = -mTempRect.top; } } else { mTempRect.setEmpty(); padding = 0; } // Max height available on the screen for a popup. final boolean ignoreBottomDecorations = mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; final int maxHeight = mPopup.getMaxAvailableHeight( getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations); if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { return maxHeight + padding; } final int childWidthSpec; switch (mDropDownWidth) { case ViewGroup.LayoutParams.WRAP_CONTENT: childWidthSpec = MeasureSpec.makeMeasureSpec( mContext.getResources().getDisplayMetrics().widthPixels - (mTempRect.left + mTempRect.right), MeasureSpec.AT_MOST); break; case ViewGroup.LayoutParams.MATCH_PARENT: childWidthSpec = MeasureSpec.makeMeasureSpec( mContext.getResources().getDisplayMetrics().widthPixels - (mTempRect.left + mTempRect.right), MeasureSpec.EXACTLY); break; default: childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY); break; } // Add padding only if the list has items in it, that way we don't show // the popup if it is not needed. final int listContent = mDropDownList.measureHeightOfChildren(childWidthSpec, 0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1); if (listContent > 0) { final int listPadding = mDropDownList.getPaddingTop() + mDropDownList.getPaddingBottom(); otherHeights += padding + listPadding; } return listContent + otherHeights; }
@hide
/** * @hide */
public void setOverlapAnchor(boolean overlap) { mOverlapAnchorSet = true; mOverlapAnchor = overlap; } private class PopupDataSetObserver extends DataSetObserver { @Override public void onChanged() { if (isShowing()) { // Resize the popup to fit new content show(); } } @Override public void onInvalidated() { dismiss(); } } private class ListSelectorHider implements Runnable { public void run() { clearListSelection(); } } private class ResizePopupRunnable implements Runnable { public void run() { if (mDropDownList != null && mDropDownList.isAttachedToWindow() && mDropDownList.getCount() > mDropDownList.getChildCount() && mDropDownList.getChildCount() <= mListItemExpandMaximum) { mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); show(); } } } private class PopupTouchInterceptor implements OnTouchListener { public boolean onTouch(View v, MotionEvent event) { final int action = event.getAction(); final int x = (int) event.getX(); final int y = (int) event.getY(); if (action == MotionEvent.ACTION_DOWN && mPopup != null && mPopup.isShowing() && (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) { mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT); } else if (action == MotionEvent.ACTION_UP) { mHandler.removeCallbacks(mResizePopupRunnable); } return false; } } private class PopupScrollListener implements ListView.OnScrollListener { public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == SCROLL_STATE_TOUCH_SCROLL && !isInputMethodNotNeeded() && mPopup.getContentView() != null) { mHandler.removeCallbacks(mResizePopupRunnable); mResizePopupRunnable.run(); } } } }