1381 lines
50 KiB
Java
1381 lines
50 KiB
Java
/*
|
|
* 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.compat.annotation.UnsupportedAppUsage;
|
|
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.
|
|
*
|
|
* <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.
|
|
*
|
|
* @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
|
|
* @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
|
|
*
|
|
* @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.
|
|
*/
|
|
private static final int EXPAND_LIST_TIMEOUT = 250;
|
|
|
|
private Context mContext;
|
|
private ListAdapter mAdapter;
|
|
@UnsupportedAppUsage
|
|
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 {@code null}, the anchor bounds are used as the epicenter.
|
|
*/
|
|
private Rect mEpicenterBounds;
|
|
|
|
private boolean mModal;
|
|
|
|
@UnsupportedAppUsage
|
|
PopupWindow mPopup;
|
|
|
|
/**
|
|
* 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 #setPromptPosition(int)
|
|
* @see #getPromptPosition()
|
|
* @see #setPromptView(View)
|
|
*/
|
|
public static final int POSITION_PROMPT_BELOW = 1;
|
|
|
|
/**
|
|
* 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 {@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 {@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 {@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 {@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 {@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 {@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 {@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 {@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.
|
|
*
|
|
* @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
|
|
* {@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;
|
|
}
|
|
|
|
/**
|
|
* @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.
|
|
*
|
|
* <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.
|
|
*
|
|
* @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 {@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.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
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 {@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.
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
|
|
mDropDownAlwaysVisible = dropDownAlwaysVisible;
|
|
}
|
|
|
|
/**
|
|
* @return Whether the drop-down is visible under special conditions.
|
|
*
|
|
* @hide Only used by AutoCompleteTextView under special conditions.
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public boolean isDropDownAlwaysVisible() {
|
|
return mDropDownAlwaysVisible;
|
|
}
|
|
|
|
/**
|
|
* 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 {@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.
|
|
*
|
|
* @param selector List selector drawable to use in the popup.
|
|
*/
|
|
public void setListSelector(Drawable selector) {
|
|
mDropDownListHighlight = selector;
|
|
}
|
|
|
|
/**
|
|
* @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.
|
|
*
|
|
* @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.
|
|
*
|
|
* @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.
|
|
*
|
|
* @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.
|
|
*
|
|
* @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.
|
|
*
|
|
* @param anchor The view to use as an anchor.
|
|
*/
|
|
public void setAnchorView(@Nullable View anchor) {
|
|
mDropDownAnchorView = anchor;
|
|
}
|
|
|
|
/**
|
|
* @return The horizontal offset of the popup from its anchor in pixels.
|
|
* @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
|
|
*/
|
|
public int getHorizontalOffset() {
|
|
return mDropDownHorizontalOffset;
|
|
}
|
|
|
|
/**
|
|
* Set the horizontal offset of this popup from its anchor view in pixels.
|
|
*
|
|
* @param offset The horizontal offset of the popup from its anchor.
|
|
* @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
|
|
*/
|
|
public void setHorizontalOffset(int offset) {
|
|
mDropDownHorizontalOffset = offset;
|
|
}
|
|
|
|
/**
|
|
* @return The vertical offset of the popup from its anchor in pixels.
|
|
* @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
|
|
*/
|
|
public int getVerticalOffset() {
|
|
if (!mDropDownVerticalOffsetSet) {
|
|
return 0;
|
|
}
|
|
return mDropDownVerticalOffset;
|
|
}
|
|
|
|
/**
|
|
* Set the vertical offset of this popup from its anchor view in pixels.
|
|
*
|
|
* @param offset The vertical offset of the popup from its anchor.
|
|
* @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
|
|
*/
|
|
public void setVerticalOffset(int offset) {
|
|
mDropDownVerticalOffset = offset;
|
|
mDropDownVerticalOffsetSet = true;
|
|
}
|
|
|
|
/**
|
|
* Specifies the anchor-relative bounds of the popup's transition
|
|
* epicenter.
|
|
*
|
|
* @param bounds anchor-relative bounds, or {@code null} to use default epicenter
|
|
*
|
|
* @see #getEpicenterBounds()
|
|
*/
|
|
public void setEpicenterBounds(@Nullable Rect bounds) {
|
|
mEpicenterBounds = bounds != null ? new Rect(bounds) : null;
|
|
}
|
|
|
|
/**
|
|
* Returns bounds which are used as a popup's epicenter
|
|
* of the enter and exit transitions.
|
|
*
|
|
* @return bounds relative to anchor view, or {@code null} if not set
|
|
* @see #setEpicenterBounds(Rect)
|
|
*/
|
|
@Nullable
|
|
public Rect getEpicenterBounds() {
|
|
return mEpicenterBounds != null ? new Rect(mEpicenterBounds) : null;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* @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 {@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.
|
|
*
|
|
* @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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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 {@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.
|
|
* <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.
|
|
*
|
|
* @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.
|
|
*
|
|
* @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 {@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 {@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.
|
|
*/
|
|
@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.setIsClippedToScreen(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.
|
|
*/
|
|
@Override
|
|
public void dismiss() {
|
|
mPopup.dismiss();
|
|
removePromptView();
|
|
mPopup.setContentView(null);
|
|
mDropDownList = null;
|
|
mHandler.removeCallbacks(mResizePopupRunnable);
|
|
}
|
|
|
|
/**
|
|
* Remove existing exit transition from PopupWindow and force immediate dismissal.
|
|
* @hide
|
|
*/
|
|
public void dismissImmediate() {
|
|
mPopup.setExitTransition(null);
|
|
dismiss();
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* {@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 {@link #setInputMethodMode(int)}.
|
|
*
|
|
* @see #setInputMethodMode(int)
|
|
*/
|
|
public int getInputMethodMode() {
|
|
return mPopup.getInputMethodMode();
|
|
}
|
|
|
|
/**
|
|
* 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 {@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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {@code true} if the popup is currently showing, {@code false} otherwise.
|
|
*/
|
|
@Override
|
|
public boolean isShowing() {
|
|
return mPopup.isShowing();
|
|
}
|
|
|
|
/**
|
|
* @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.
|
|
*
|
|
* @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;
|
|
}
|
|
|
|
/**
|
|
* @return The currently selected item or null if the popup is not showing.
|
|
*/
|
|
public @Nullable Object getSelectedItem() {
|
|
if (!isShowing()) {
|
|
return null;
|
|
}
|
|
return mDropDownList.getSelectedItem();
|
|
}
|
|
|
|
/**
|
|
* @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();
|
|
}
|
|
|
|
/**
|
|
* @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();
|
|
}
|
|
|
|
/**
|
|
* @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();
|
|
}
|
|
|
|
/**
|
|
* @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.
|
|
*
|
|
* @param max Max number of items that can be visible and still allow the list to expand.
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
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.
|
|
*
|
|
* @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:
|
|
case KeyEvent.KEYCODE_NUMPAD_ENTER:
|
|
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.
|
|
*
|
|
* @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 {@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
|
|
|| keyCode == KeyEvent.KEYCODE_ESCAPE) && 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 {@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;
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* <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
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
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
|
|
*/
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|