Added ListPopupWindow. Refactored AutoCompleteTextView and Spinner
(optionally) to use it. Added associated styles.
ListPopupWindow allows apps to present a popup window of options to
the user that will correctly dodge the IME if needed.
Change-Id: I509c6c45036856daab686a6edeb7a9de1e72eb0a
diff --git a/api/current.xml b/api/current.xml
index d79fab6..3c2fccb 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -16883,6 +16883,17 @@
visibility="public"
>
</field>
+<field name="Widget_Spinner_DropDown"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973970"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="Widget_TabWidget"
type="int"
transient="false"
@@ -211630,6 +211641,665 @@
</parameter>
</method>
</interface>
+<class name="ListPopupWindow"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ListPopupWindow"
+ type="android.widget.ListPopupWindow"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<constructor name="ListPopupWindow"
+ type="android.widget.ListPopupWindow"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+</constructor>
+<constructor name="ListPopupWindow"
+ type="android.widget.ListPopupWindow"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+<parameter name="defStyleAttr" type="int">
+</parameter>
+</constructor>
+<constructor name="ListPopupWindow"
+ type="android.widget.ListPopupWindow"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+<parameter name="defStyleAttr" type="int">
+</parameter>
+<parameter name="defStyleRes" type="int">
+</parameter>
+</constructor>
+<method name="clearListSelection"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="dismiss"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getAnchorView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getAnimationStyle"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getBackground"
+ return="android.graphics.drawable.Drawable"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getHeight"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getHorizontalOffset"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getInputMethodMode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getListView"
+ return="android.widget.ListView"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPromptPosition"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedItem"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedItemId"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedItemPosition"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSoftInputMode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVerticalOffset"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getWidth"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isInputMethodNotNeeded"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isModal"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isShowing"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onKeyDown"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
+<method name="onKeyPreIme"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
+<method name="onKeyUp"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
+<method name="performItemClick"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="postShow"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="adapter" type="android.widget.ListAdapter">
+</parameter>
+</method>
+<method name="setAnchorView"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="anchor" type="android.view.View">
+</parameter>
+</method>
+<method name="setAnimationStyle"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animationStyle" type="int">
+</parameter>
+</method>
+<method name="setBackgroundDrawable"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="d" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
+<method name="setHeight"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="height" type="int">
+</parameter>
+</method>
+<method name="setHorizontalOffset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<method name="setInputMethodMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mode" type="int">
+</parameter>
+</method>
+<method name="setListSelector"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="selector" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
+<method name="setModal"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="modal" type="boolean">
+</parameter>
+</method>
+<method name="setOnItemClickListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="clickListener" type="android.widget.AdapterView.OnItemClickListener">
+</parameter>
+</method>
+<method name="setOnItemSelectedListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="selectedListener" type="android.widget.AdapterView.OnItemSelectedListener">
+</parameter>
+</method>
+<method name="setPromptPosition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="setPromptView"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="prompt" type="android.view.View">
+</parameter>
+</method>
+<method name="setSelection"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="setSoftInputMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mode" type="int">
+</parameter>
+</method>
+<method name="setVerticalOffset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<method name="setWidth"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="width" type="int">
+</parameter>
+</method>
+<method name="show"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<field name="INPUT_METHOD_FROM_FOCUSABLE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INPUT_METHOD_NEEDED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INPUT_METHOD_NOT_NEEDED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MATCH_PARENT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="POSITION_PROMPT_ABOVE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="POSITION_PROMPT_BELOW"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="WRAP_CONTENT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="ListView"
extends="android.widget.AbsListView"
abstract="false"
@@ -212670,6 +213340,22 @@
deprecated="not deprecated"
visibility="public"
>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+<parameter name="defStyleAttr" type="int">
+</parameter>
+<parameter name="defStyleRes" type="int">
+</parameter>
+</constructor>
+<constructor name="PopupWindow"
+ type="android.widget.PopupWindow"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
</constructor>
<constructor name="PopupWindow"
type="android.widget.PopupWindow"
@@ -217256,6 +217942,28 @@
<parameter name="promptId" type="int">
</parameter>
</method>
+<field name="MODE_DIALOG"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MODE_DROPDOWN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
</class>
<interface name="SpinnerAdapter"
abstract="true"
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 8611901..34aef99 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -29,13 +29,12 @@
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
@@ -90,45 +89,21 @@
static final boolean DEBUG = false;
static final String TAG = "AutoCompleteTextView";
- private static final int HINT_VIEW_ID = 0x17;
-
- /**
- * 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 CharSequence mHintText;
+ private TextView mHintView;
private int mHintResource;
private ListAdapter mAdapter;
private Filter mFilter;
private int mThreshold;
- private PopupWindow mPopup;
- private DropDownListView mDropDownList;
- private int mDropDownVerticalOffset;
- private int mDropDownHorizontalOffset;
+ private ListPopupWindow mPopup;
private int mDropDownAnchorId;
- private View mDropDownAnchorView; // view is retrieved lazily from id once needed
- private int mDropDownWidth;
- private int mDropDownHeight;
- private final Rect mTempRect = new Rect();
-
- private Drawable mDropDownListHighlight;
private AdapterView.OnItemClickListener mItemClickListener;
private AdapterView.OnItemSelectedListener mItemSelectedListener;
- private final DropDownItemClickListener mDropDownItemClickListener =
- new DropDownItemClickListener();
-
- private boolean mDropDownAlwaysVisible = false;
-
private boolean mDropDownDismissedOnCompletion = true;
-
- private boolean mForceIgnoreOutsideTouch = false;
private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
private boolean mOpenBefore;
@@ -137,10 +112,6 @@
private boolean mBlockCompletion;
- private ListSelectorHider mHideSelector;
- private Runnable mShowDropDownRunnable;
- private Runnable mResizePopupRunnable = new ResizePopupRunnable();
-
private PassThroughClickListener mPassThroughClickListener;
private PopupDataSetObserver mObserver;
@@ -155,9 +126,10 @@
public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mPopup = new PopupWindow(context, attrs,
+ mPopup = new ListPopupWindow(context, attrs,
com.android.internal.R.attr.autoCompleteTextViewStyle);
mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
+ mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
TypedArray a =
context.obtainStyledAttributes(
@@ -166,14 +138,11 @@
mThreshold = a.getInt(
R.styleable.AutoCompleteTextView_completionThreshold, 2);
- mHintText = a.getText(R.styleable.AutoCompleteTextView_completionHint);
-
- mDropDownListHighlight = a.getDrawable(
- R.styleable.AutoCompleteTextView_dropDownSelector);
- mDropDownVerticalOffset = (int)
- a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f);
- mDropDownHorizontalOffset = (int)
- a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f);
+ mPopup.setListSelector(a.getDrawable(R.styleable.AutoCompleteTextView_dropDownSelector));
+ mPopup.setVerticalOffset((int)
+ a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f));
+ mPopup.setHorizontalOffset((int)
+ a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f));
// Get the anchor's id now, but the view won't be ready, so wait to actually get the
// view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
@@ -184,13 +153,18 @@
// For dropdown width, the developer can specify a specific width, or MATCH_PARENT
// (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
- mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- mDropDownHeight = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownHeight,
- ViewGroup.LayoutParams.WRAP_CONTENT);
+ mPopup.setWidth(a.getLayoutDimension(
+ R.styleable.AutoCompleteTextView_dropDownWidth,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mPopup.setHeight(a.getLayoutDimension(
+ R.styleable.AutoCompleteTextView_dropDownHeight,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
R.layout.simple_dropdown_hint);
+
+ mPopup.setOnItemClickListener(new DropDownItemClickListener());
+ setCompletionHint(a.getText(R.styleable.AutoCompleteTextView_completionHint));
// Always turn on the auto complete input type flag, since it
// makes no sense to use this widget without it.
@@ -238,6 +212,20 @@
*/
public void setCompletionHint(CharSequence hint) {
mHintText = hint;
+ if (hint != null) {
+ if (mHintView == null) {
+ final TextView hintView = (TextView) LayoutInflater.from(getContext()).inflate(
+ mHintResource, null).findViewById(com.android.internal.R.id.text1);
+ hintView.setText(mHintText);
+ mHintView = hintView;
+ mPopup.setPromptView(hintView);
+ } else {
+ mHintView.setText(hint);
+ }
+ } else {
+ mPopup.setPromptView(null);
+ mHintView = null;
+ }
}
/**
@@ -250,7 +238,7 @@
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
*/
public int getDropDownWidth() {
- return mDropDownWidth;
+ return mPopup.getWidth();
}
/**
@@ -263,7 +251,7 @@
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
*/
public void setDropDownWidth(int width) {
- mDropDownWidth = width;
+ mPopup.setWidth(width);
}
/**
@@ -277,7 +265,7 @@
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
*/
public int getDropDownHeight() {
- return mDropDownHeight;
+ return mPopup.getHeight();
}
/**
@@ -291,7 +279,7 @@
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
*/
public void setDropDownHeight(int height) {
- mDropDownHeight = height;
+ mPopup.setHeight(height);
}
/**
@@ -316,7 +304,7 @@
*/
public void setDropDownAnchor(int id) {
mDropDownAnchorId = id;
- mDropDownAnchorView = null;
+ mPopup.setAnchorView(null);
}
/**
@@ -358,7 +346,7 @@
* @param offset the vertical offset
*/
public void setDropDownVerticalOffset(int offset) {
- mDropDownVerticalOffset = offset;
+ mPopup.setVerticalOffset(offset);
}
/**
@@ -367,7 +355,7 @@
* @return the vertical offset
*/
public int getDropDownVerticalOffset() {
- return mDropDownVerticalOffset;
+ return mPopup.getVerticalOffset();
}
/**
@@ -376,7 +364,7 @@
* @param offset the horizontal offset
*/
public void setDropDownHorizontalOffset(int offset) {
- mDropDownHorizontalOffset = offset;
+ mPopup.setHorizontalOffset(offset);
}
/**
@@ -385,7 +373,7 @@
* @return the horizontal offset
*/
public int getDropDownHorizontalOffset() {
- return mDropDownHorizontalOffset;
+ return mPopup.getHorizontalOffset();
}
/**
@@ -422,7 +410,7 @@
* @hide Pending API council approval
*/
public boolean isDropDownAlwaysVisible() {
- return mDropDownAlwaysVisible;
+ return mPopup.isDropDownAlwaysVisible();
}
/**
@@ -439,7 +427,7 @@
* @hide Pending API council approval
*/
public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
- mDropDownAlwaysVisible = dropDownAlwaysVisible;
+ mPopup.setDropDownAlwaysVisible(dropDownAlwaysVisible);
}
/**
@@ -606,15 +594,13 @@
mFilter = null;
}
- if (mDropDownList != null) {
- mDropDownList.setAdapter(mAdapter);
- }
+ mPopup.setAdapter(mAdapter);
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing()
- && !mDropDownAlwaysVisible) {
+ && !mPopup.isDropDownAlwaysVisible()) {
// special case for the back key, we do not even try to send it
// to the drop down list but instead, consume it immediately
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
@@ -633,18 +619,16 @@
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (isPopupShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
- boolean consumed = mDropDownList.onKeyUp(keyCode, event);
- if (consumed) {
- switch (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
- case KeyEvent.KEYCODE_ENTER:
- case KeyEvent.KEYCODE_DPAD_CENTER:
- performCompletion();
- return true;
- }
+ boolean consumed = mPopup.onKeyUp(keyCode, event);
+ if (consumed) {
+ switch (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
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ performCompletion();
+ return true;
}
}
return super.onKeyUp(keyCode, event);
@@ -652,87 +636,11 @@
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- // when the drop down is shown, we drive it directly
- if (isPopupShowing()) {
- // 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
- || (keyCode != KeyEvent.KEYCODE_ENTER
- && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
- 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);
- showDropDown();
- return true;
- } else {
- // WARNING: Please read the comment where mListSelectionHidden
- // is declared
- mDropDownList.mListSelectionHidden = 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();
- showDropDown();
-
- 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;
- }
- }
- }
- } else {
+ if (mPopup.onKeyDown(keyCode, event)) {
+ return true;
+ }
+
+ if (!isPopupShowing()) {
switch(keyCode) {
case KeyEvent.KEYCODE_DPAD_DOWN:
performValidation();
@@ -743,7 +651,7 @@
boolean handled = super.onKeyDown(keyCode, event);
mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
- if (handled && isPopupShowing() && mDropDownList != null) {
+ if (handled && isPopupShowing()) {
clearListSelection();
}
@@ -804,11 +712,12 @@
if (enoughToFilter()) {
if (mFilter != null) {
performFiltering(getText(), mLastKeyCode);
+ buildImeCompletions();
}
} else {
// drop down is automatically dismissed when enough characters
// are deleted from the text view
- if (!mDropDownAlwaysVisible) dismissDropDown();
+ if (!mPopup.isDropDownAlwaysVisible()) dismissDropDown();
if (mFilter != null) {
mFilter.filter(null);
}
@@ -841,13 +750,7 @@
* it back.
*/
public void clearListSelection() {
- final DropDownListView list = mDropDownList;
- if (list != null) {
- // WARNING: Please read the comment where mListSelectionHidden is declared
- list.mListSelectionHidden = true;
- list.hideSelector();
- list.requestLayout();
- }
+ mPopup.clearListSelection();
}
/**
@@ -856,11 +759,7 @@
* @param position The position to move the selector to.
*/
public void setListSelection(int position) {
- if (mPopup.isShowing() && (mDropDownList != null)) {
- mDropDownList.mListSelectionHidden = false;
- mDropDownList.setSelection(position);
- // ListView.setSelection() will call requestLayout()
- }
+ mPopup.setSelection(position);
}
/**
@@ -874,10 +773,7 @@
* @see ListView#getSelectedItemPosition()
*/
public int getListSelection() {
- if (mPopup.isShowing() && (mDropDownList != null)) {
- return mDropDownList.getSelectedItemPosition();
- }
- return ListView.INVALID_POSITION;
+ return mPopup.getSelectedItemPosition();
}
/**
@@ -911,13 +807,7 @@
replaceText(completion.getText());
mBlockCompletion = false;
- if (mItemClickListener != null) {
- final DropDownListView list = mDropDownList;
- final int position = completion.getPosition();
- mItemClickListener.onItemClick(list,
- list.getChildAt(position - list.getFirstVisiblePosition()),
- position, completion.getId());
- }
+ mPopup.performItemClick(completion.getPosition());
}
}
@@ -925,7 +815,7 @@
if (isPopupShowing()) {
Object selectedItem;
if (position < 0) {
- selectedItem = mDropDownList.getSelectedItem();
+ selectedItem = mPopup.getSelectedItem();
} else {
selectedItem = mAdapter.getItem(position);
}
@@ -939,18 +829,18 @@
mBlockCompletion = false;
if (mItemClickListener != null) {
- final DropDownListView list = mDropDownList;
+ final ListPopupWindow list = mPopup;
if (selectedView == null || position < 0) {
selectedView = list.getSelectedView();
position = list.getSelectedItemPosition();
id = list.getSelectedItemId();
}
- mItemClickListener.onItemClick(list, selectedView, position, id);
+ mItemClickListener.onItemClick(list.getListView(), selectedView, position, id);
}
}
- if (mDropDownDismissedOnCompletion && !mDropDownAlwaysVisible) {
+ if (mDropDownDismissedOnCompletion && !mPopup.isDropDownAlwaysVisible()) {
dismissDropDown();
}
}
@@ -1000,7 +890,6 @@
/** {@inheritDoc} */
public void onFilterComplete(int count) {
updateDropDownForFilter(count);
-
}
private void updateDropDownForFilter(int count) {
@@ -1014,11 +903,12 @@
* to filter.
*/
- if ((count > 0 || mDropDownAlwaysVisible) && enoughToFilter()) {
+ final boolean dropDownAlwaysVisible = mPopup.isDropDownAlwaysVisible();
+ if ((count > 0 || dropDownAlwaysVisible) && enoughToFilter()) {
if (hasFocus() && hasWindowFocus()) {
showDropDown();
}
- } else if (!mDropDownAlwaysVisible) {
+ } else if (!dropDownAlwaysVisible) {
dismissDropDown();
}
}
@@ -1026,7 +916,7 @@
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
- if (!hasWindowFocus && !mDropDownAlwaysVisible) {
+ if (!hasWindowFocus && !mPopup.isDropDownAlwaysVisible()) {
dismissDropDown();
}
}
@@ -1036,7 +926,7 @@
super.onDisplayHint(hint);
switch (hint) {
case INVISIBLE:
- if (!mDropDownAlwaysVisible) {
+ if (!mPopup.isDropDownAlwaysVisible()) {
dismissDropDown();
}
break;
@@ -1050,7 +940,7 @@
if (!focused) {
performValidation();
}
- if (!focused && !mDropDownAlwaysVisible) {
+ if (!focused && !mPopup.isDropDownAlwaysVisible()) {
dismissDropDown();
}
}
@@ -1075,8 +965,6 @@
imm.displayCompletions(this, null);
}
mPopup.dismiss();
- mPopup.setContentView(null);
- mDropDownList = null;
}
@Override
@@ -1089,18 +977,6 @@
return result;
}
-
- /**
- * <p>Used for lazy instantiation of the anchor view from the id we have. If the value of
- * the id is NO_ID or we can't find a view for the given id, we return this TextView as
- * the default anchoring point.</p>
- */
- private View getDropDownAnchorView() {
- if (mDropDownAnchorView == null && mDropDownAnchorId != View.NO_ID) {
- mDropDownAnchorView = getRootView().findViewById(mDropDownAnchorId);
- }
- return mDropDownAnchorView == null ? this : mDropDownAnchorView;
- }
/**
* Issues a runnable to show the dropdown as soon as possible.
@@ -1108,7 +984,7 @@
* @hide internal used only by SearchDialog
*/
public void showDropDownAfterLayout() {
- post(mShowDropDownRunnable);
+ mPopup.postShow();
}
/**
@@ -1119,7 +995,7 @@
*/
public void ensureImeVisible(boolean visible) {
mPopup.setInputMethodMode(visible
- ? PopupWindow.INPUT_METHOD_NEEDED : PopupWindow.INPUT_METHOD_NOT_NEEDED);
+ ? ListPopupWindow.INPUT_METHOD_NEEDED : ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
showDropDown();
}
@@ -1127,89 +1003,21 @@
* @hide internal used only here and SearchDialog
*/
public boolean isInputMethodNotNeeded() {
- return mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
+ return mPopup.getInputMethodMode() == ListPopupWindow.INPUT_METHOD_NOT_NEEDED;
}
/**
* <p>Displays the drop down on screen.</p>
*/
public void showDropDown() {
- int height = buildDropDown();
-
- int widthSpec = 0;
- int heightSpec = 0;
-
- boolean noInputMethod = isInputMethodNotNeeded();
-
- if (mPopup.isShowing()) {
- 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 = getDropDownAnchorView().getWidth();
+ if (mPopup.getAnchorView() == null) {
+ if (mDropDownAnchorId != View.NO_ID) {
+ mPopup.setAnchorView(getRootView().findViewById(mDropDownAnchorId));
} else {
- widthSpec = mDropDownWidth;
+ mPopup.setAnchorView(this);
}
-
- 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.setWindowLayoutMode(
- mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
- ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
- } else {
- mPopup.setWindowLayoutMode(
- mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
- ViewGroup.LayoutParams.MATCH_PARENT : 0,
- ViewGroup.LayoutParams.MATCH_PARENT);
- }
- } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
- heightSpec = height;
- } else {
- heightSpec = mDropDownHeight;
- }
-
- mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
-
- mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
- mDropDownVerticalOffset, widthSpec, heightSpec);
- } else {
- if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
- widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
- } else {
- if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
- mPopup.setWidth(getDropDownAnchorView().getWidth());
- } else {
- mPopup.setWidth(mDropDownWidth);
- }
- }
-
- if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
- heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
- } else {
- if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
- mPopup.setHeight(height);
- } else {
- mPopup.setHeight(mDropDownHeight);
- }
- }
-
- mPopup.setWindowLayoutMode(widthSpec, heightSpec);
- mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
-
- // 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(new PopupTouchInterceptor());
- mPopup.showAsDropDown(getDropDownAnchorView(),
- mDropDownHorizontalOffset, mDropDownVerticalOffset);
- mDropDownList.setSelection(ListView.INVALID_POSITION);
- clearListSelection();
- post(mHideSelector);
}
+ mPopup.show();
}
/**
@@ -1220,19 +1028,10 @@
* @hide used only by SearchDialog
*/
public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
- mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
+ mPopup.setForceIgnoreOutsideTouch(forceIgnoreOutsideTouch);
}
-
- /**
- * <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;
-
+
+ private void buildImeCompletions() {
final ListAdapter adapter = mAdapter;
if (adapter != null) {
InputMethodManager imm = InputMethodManager.peekInstance();
@@ -1260,135 +1059,6 @@
imm.displayCompletions(this, completions);
}
}
-
- if (mDropDownList == null) {
- Context context = getContext();
-
- mHideSelector = new ListSelectorHider();
-
- /**
- * 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 = getDropDownAnchorView();
- if (view != null && view.getWindowToken() != null) {
- showDropDown();
- }
- }
- };
-
- mDropDownList = new DropDownListView(context);
- mDropDownList.setSelector(mDropDownListHighlight);
- mDropDownList.setAdapter(adapter);
- mDropDownList.setVerticalFadingEdgeEnabled(true);
- mDropDownList.setOnItemClickListener(mDropDownItemClickListener);
- 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.mListSelectionHidden = false;
- }
- }
- }
-
- public void onNothingSelected(AdapterView<?> parent) {
- }
- });
- mDropDownList.setOnScrollListener(new PopupScrollListener());
-
- if (mItemSelectedListener != null) {
- mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
- }
-
- dropDownView = mDropDownList;
-
- View hintView = getHintView(context);
- if (hintView != null) {
- // if an 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
- );
- hintContainer.addView(dropDownView, hintParams);
- hintContainer.addView(hintView);
-
- // measure the hint's height to find how much more vertical space
- // we need to add to the drop down's height
- int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST);
- 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 {
- dropDownView = (ViewGroup) mPopup.getContentView();
- final View view = dropDownView.findViewById(HINT_VIEW_ID);
- if (view != null) {
- LinearLayout.LayoutParams hintParams =
- (LinearLayout.LayoutParams) view.getLayoutParams();
- otherHeights = view.getMeasuredHeight() + hintParams.topMargin
- + hintParams.bottomMargin;
- }
- }
-
- // Max height available on the screen for a popup.
- boolean ignoreBottomDecorations =
- mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
- final int maxHeight = mPopup.getMaxAvailableHeight(
- getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
-
- // getMaxAvailableHeight() subtracts the padding, so we put it back,
- // to get the available height for the whole window
- int padding = 0;
- Drawable background = mPopup.getBackground();
- if (background != null) {
- background.getPadding(mTempRect);
- padding = mTempRect.top + mTempRect.bottom;
- }
-
- if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
- return maxHeight + padding;
- }
-
- final int listContent = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
- 0, ListView.NO_POSITION, maxHeight - otherHeights, 2);
- // add padding only if the list has items in it, that way we don't show
- // the popup if it is not needed
- if (listContent > 0) otherHeights += padding;
-
- return listContent + otherHeights;
- }
-
- private View getHintView(Context context) {
- if (mHintText != null && mHintText.length() > 0) {
- final TextView hintView = (TextView) LayoutInflater.from(context).inflate(
- mHintResource, null).findViewById(com.android.internal.R.id.text1);
- hintView.setText(mHintText);
- hintView.setId(HINT_VIEW_ID);
- return hintView;
- } else {
- return null;
- }
}
/**
@@ -1440,47 +1110,6 @@
return mFilter;
}
- private class ListSelectorHider implements Runnable {
- public void run() {
- clearListSelection();
- }
- }
-
- private class ResizePopupRunnable implements Runnable {
- public void run() {
- mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
- showDropDown();
- }
- }
-
- private class PopupTouchInterceptor implements OnTouchListener {
- public boolean onTouch(View v, MotionEvent event) {
- final int action = event.getAction();
- if (action == MotionEvent.ACTION_DOWN &&
- mPopup != null && mPopup.isShowing()) {
- postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
- } else if (action == MotionEvent.ACTION_UP) {
- 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) {
- removeCallbacks(mResizePopupRunnable);
- mResizePopupRunnable.run();
- }
- }
- }
-
private class DropDownItemClickListener implements AdapterView.OnItemClickListener {
public void onItemClick(AdapterView parent, View v, int position, long id) {
performCompletion(v, position, id);
@@ -1488,123 +1117,6 @@
}
/**
- * <p>Wrapper class for a ListView. This wrapper hijacks the focus to
- * make sure the list uses the appropriate drawables and states when
- * displayed on screen within a drop down. The focus is never actually
- * passed to the drop down; the list only looks focused.</p>
- */
- private static class DropDownListView extends ListView {
- /*
- * WARNING: This is a workaround for a touch mode issue.
- *
- * Touch mode is propagated lazily to windows. This causes problems in
- * the following scenario:
- * - Type something in the AutoCompleteTextView and get some results
- * - Move down with the d-pad to select an item in the list
- * - Move up with the d-pad until the selection disappears
- * - Type more text in the AutoCompleteTextView *using the soft keyboard*
- * and get new results; you are now in touch mode
- * - The selection comes back on the first item in the list, even though
- * the list is supposed to be in touch mode
- *
- * Using the soft keyboard triggers the touch mode change but that change
- * is propagated to our window only after the first list layout, therefore
- * after the list attempts to resurrect the selection.
- *
- * The trick to work around this issue is to pretend the list is in touch
- * mode when we know that the selection should not appear, that is when
- * we know the user moved the selection away from the list.
- *
- * This boolean is set to true whenever we explicitely hide the list's
- * selection and reset to false whenver we know the user moved the
- * selection back to the list.
- *
- * When this boolean is true, isInTouchMode() returns true, otherwise it
- * returns super.isInTouchMode().
- */
- private boolean mListSelectionHidden;
-
- /**
- * <p>Creates a new list view wrapper.</p>
- *
- * @param context this view's context
- */
- public DropDownListView(Context context) {
- super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
- }
-
- /**
- * <p>Avoids jarring scrolling effect by ensuring that list elements
- * made of a text view fit on a single line.</p>
- *
- * @param position the item index in the list to get a view for
- * @return the view for the specified item
- */
- @Override
- View obtainView(int position, boolean[] isScrap) {
- View view = super.obtainView(position, isScrap);
-
- if (view instanceof TextView) {
- ((TextView) view).setHorizontallyScrolling(true);
- }
-
- return view;
- }
-
- @Override
- public boolean isInTouchMode() {
- // WARNING: Please read the comment where mListSelectionHidden is declared
- return mListSelectionHidden || super.isInTouchMode();
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always
- */
- @Override
- public boolean hasWindowFocus() {
- return true;
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always
- */
- @Override
- public boolean isFocused() {
- return true;
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always
- */
- @Override
- public boolean hasFocus() {
- return true;
- }
-
- protected int[] onCreateDrawableState(int extraSpace) {
- int[] res = super.onCreateDrawableState(extraSpace);
- //noinspection ConstantIfStatement
- if (false) {
- StringBuilder sb = new StringBuilder("Created drawable state: [");
- for (int i=0; i<res.length; i++) {
- if (i > 0) sb.append(", ");
- sb.append("0x");
- sb.append(Integer.toHexString(res[i]));
- }
- sb.append("]");
- Log.i(TAG, sb.toString());
- }
- return res;
- }
- }
-
- /**
* This interface is used to make sure that the text entered in this TextView complies to
* a certain format. Since there is no foolproof way to prevent the user from leaving
* this View with an incorrect value in it, all we can do is try to fix it ourselves
@@ -1652,10 +1164,7 @@
private class PopupDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
- if (isPopupShowing()) {
- // This will resize the popup to fit the new adapter's content
- showDropDown();
- } else if (mAdapter != null) {
+ if (mAdapter != null) {
// If the popup is not showing already, showing it will cause
// the list of data set observers attached to the adapter to
// change. We can't do it from here, because we are in the middle
@@ -1670,14 +1179,5 @@
});
}
}
-
- @Override
- public void onInvalidated() {
- if (!mDropDownAlwaysVisible) {
- // There's no data to display so make sure we're not showing
- // the drop down and its list
- dismissDropDown();
- }
- }
}
}
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
new file mode 100644
index 0000000..eda3606
--- /dev/null
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -0,0 +1,1215 @@
+/*
+ * 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.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.View.MeasureSpec;
+import android.view.View.OnTouchListener;
+
+/**
+ * A ListPopupWindow anchors itself to a host view and displays a
+ * list of choices. When one is selected, the popup is dismissed.
+ *
+ * <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 {
+ 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 PopupWindow mPopup;
+ 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 boolean mDropDownAlwaysVisible = false;
+ private boolean mForceIgnoreOutsideTouch = false;
+
+ 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 Handler mHandler = new Handler();
+
+ private Rect mTempRect = new Rect();
+
+ private boolean mModal;
+
+ /**
+ * 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(Context context) {
+ this(context, null, 0, 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(Context context, AttributeSet attrs) {
+ this(context, attrs, 0, 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(Context context, AttributeSet attrs, 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(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ mContext = context;
+ mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * 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(ListAdapter adapter) {
+ if (mObserver == null) {
+ mObserver = new PopupDataSetObserver();
+ } else if (mAdapter != null) {
+ adapter.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 = true;
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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 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(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(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 int getAnimationStyle() {
+ return mPopup.getAnimationStyle();
+ }
+
+ /**
+ * Returns the view that will be used to anchor this popup.
+ *
+ * @return The popup's anchor view
+ */
+ public 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(View anchor) {
+ mDropDownAnchorView = anchor;
+ }
+
+ /**
+ * @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.
+ *
+ * @param The horizontal offset of the popup from its anchor.
+ */
+ public void setHorizontalOffset(int offset) {
+ mDropDownHorizontalOffset = offset;
+ }
+
+ /**
+ * @return The vertical offset of the popup from its anchor in pixels.
+ */
+ public int getVerticalOffset() {
+ return mDropDownVerticalOffset;
+ }
+
+ /**
+ * Set the vertical offset of this popup from its anchor view in pixels.
+ *
+ * @param The vertical offset of the popup from its anchor.
+ */
+ public void setVerticalOffset(int offset) {
+ mDropDownVerticalOffset = offset;
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * @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.
+ */
+ public void setHeight(int height) {
+ mDropDownHeight = height;
+ }
+
+ /**
+ * 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(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(android.widget.AdapterView.OnItemSelectedListener)
+ */
+ public void setOnItemSelectedListener(AdapterView.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(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.
+ */
+ public void show() {
+ int height = buildDropDown();
+
+ int widthSpec = 0;
+ int heightSpec = 0;
+
+ boolean noInputMethod = isInputMethodNotNeeded();
+
+ if (mPopup.isShowing()) {
+ 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;
+ }
+
+ 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.setWindowLayoutMode(
+ mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+ ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
+ } else {
+ mPopup.setWindowLayoutMode(
+ mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+ ViewGroup.LayoutParams.MATCH_PARENT : 0,
+ 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, heightSpec);
+ } else {
+ if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
+ widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
+ } else {
+ if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ mPopup.setWidth(getAnchorView().getWidth());
+ } else {
+ mPopup.setWidth(mDropDownWidth);
+ }
+ }
+
+ if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+ heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
+ } else {
+ if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ mPopup.setHeight(height);
+ } else {
+ mPopup.setHeight(mDropDownHeight);
+ }
+ }
+
+ mPopup.setWindowLayoutMode(widthSpec, heightSpec);
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+
+ // 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.showAsDropDown(getAnchorView(),
+ mDropDownHorizontalOffset, mDropDownVerticalOffset);
+ mDropDownList.setSelection(ListView.INVALID_POSITION);
+
+ if (!mModal || mDropDownList.isInTouchMode()) {
+ clearListSelection();
+ }
+ if (!mModal) {
+ mHandler.post(mHideSelector);
+ }
+ }
+ }
+
+ /**
+ * Dismiss the popup window.
+ */
+ public void dismiss() {
+ mPopup.dismiss();
+ removePromptView();
+ mPopup.setContentView(null);
+ mDropDownList = null;
+ }
+
+ 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.mListSelectionHidden = 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.mListSelectionHidden = true;
+ list.hideSelector();
+ list.requestLayout();
+ }
+ }
+
+ /**
+ * @return {@code true} if the popup is currently showing, {@code false} otherwise.
+ */
+ 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());
+ mItemClickListener.onItemClick(list, child, position, child.getId());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return The currently selected item or null if the popup is not showing.
+ */
+ public 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 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}.
+ */
+ public ListView getListView() {
+ return mDropDownList;
+ }
+
+ /**
+ * Filter key down 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 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)
+ */
+ public boolean onKeyDown(int keyCode, 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
+ || (keyCode != KeyEvent.KEYCODE_ENTER
+ && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
+ 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.mListSelectionHidden = 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 down 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)
+ */
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
+ boolean consumed = mDropDownList.onKeyUp(keyCode, event);
+ if (consumed) {
+ switch (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
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ dismiss();
+ break;
+ }
+ }
+ 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, 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) {
+ anchorView.getKeyDispatcherState().startTracking(event, this);
+ return true;
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ anchorView.getKeyDispatcherState().handleUpEvent(event);
+ if (event.isTracking() && !event.isCanceled()) {
+ dismiss();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * <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 = new DropDownListView(context, !mModal);
+ if (mDropDownListHighlight != null) {
+ mDropDownList.setSelector(mDropDownListHighlight);
+ }
+ mDropDownList.setAdapter(mAdapter);
+ mDropDownList.setVerticalFadingEdgeEnabled(true);
+ 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.mListSelectionHidden = false;
+ }
+ }
+ }
+
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+ mDropDownList.setOnScrollListener(mScrollListener);
+
+ if (mItemSelectedListener != null) {
+ mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
+ }
+
+ dropDownView = mDropDownList;
+
+ View hintView = mPromptView;
+ if (hintView != null) {
+ // if an 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
+ int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST);
+ 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 {
+ dropDownView = (ViewGroup) mPopup.getContentView();
+ final View view = mPromptView;
+ if (view != null) {
+ LinearLayout.LayoutParams hintParams =
+ (LinearLayout.LayoutParams) view.getLayoutParams();
+ otherHeights = view.getMeasuredHeight() + hintParams.topMargin
+ + hintParams.bottomMargin;
+ }
+ }
+
+ // Max height available on the screen for a popup.
+ boolean ignoreBottomDecorations =
+ mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
+ final int maxHeight = mPopup.getMaxAvailableHeight(
+ getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
+
+ // getMaxAvailableHeight() subtracts the padding, so we put it back,
+ // to get the available height for the whole window
+ int padding = 0;
+ Drawable background = mPopup.getBackground();
+ if (background != null) {
+ background.getPadding(mTempRect);
+ padding = mTempRect.top + mTempRect.bottom;
+ }
+
+ if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+ return maxHeight + padding;
+ }
+
+ final int listContent = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
+ 0, ListView.NO_POSITION, maxHeight - otherHeights, 2);
+ // add padding only if the list has items in it, that way we don't show
+ // the popup if it is not needed
+ if (listContent > 0) otherHeights += padding;
+
+ return listContent + otherHeights;
+ }
+
+ /**
+ * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
+ * make sure the list uses the appropriate drawables and states when
+ * displayed on screen within a drop down. The focus is never actually
+ * passed to the drop down in this mode; the list only looks focused.</p>
+ */
+ private static class DropDownListView extends ListView {
+ private static final String TAG = ListPopupWindow.TAG + ".DropDownListView";
+ /*
+ * WARNING: This is a workaround for a touch mode issue.
+ *
+ * Touch mode is propagated lazily to windows. This causes problems in
+ * the following scenario:
+ * - Type something in the AutoCompleteTextView and get some results
+ * - Move down with the d-pad to select an item in the list
+ * - Move up with the d-pad until the selection disappears
+ * - Type more text in the AutoCompleteTextView *using the soft keyboard*
+ * and get new results; you are now in touch mode
+ * - The selection comes back on the first item in the list, even though
+ * the list is supposed to be in touch mode
+ *
+ * Using the soft keyboard triggers the touch mode change but that change
+ * is propagated to our window only after the first list layout, therefore
+ * after the list attempts to resurrect the selection.
+ *
+ * The trick to work around this issue is to pretend the list is in touch
+ * mode when we know that the selection should not appear, that is when
+ * we know the user moved the selection away from the list.
+ *
+ * This boolean is set to true whenever we explicitly hide the list's
+ * selection and reset to false whenever we know the user moved the
+ * selection back to the list.
+ *
+ * When this boolean is true, isInTouchMode() returns true, otherwise it
+ * returns super.isInTouchMode().
+ */
+ private boolean mListSelectionHidden;
+
+ /**
+ * True if this wrapper should fake focus.
+ */
+ private boolean mHijackFocus;
+
+ /**
+ * <p>Creates a new list view wrapper.</p>
+ *
+ * @param context this view's context
+ */
+ public DropDownListView(Context context, boolean hijackFocus) {
+ super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
+ mHijackFocus = hijackFocus;
+ }
+
+ /**
+ * <p>Avoids jarring scrolling effect by ensuring that list elements
+ * made of a text view fit on a single line.</p>
+ *
+ * @param position the item index in the list to get a view for
+ * @return the view for the specified item
+ */
+ @Override
+ View obtainView(int position, boolean[] isScrap) {
+ View view = super.obtainView(position, isScrap);
+
+ if (view instanceof TextView) {
+ ((TextView) view).setHorizontallyScrolling(true);
+ }
+
+ return view;
+ }
+
+ @Override
+ public boolean isInTouchMode() {
+ // WARNING: Please read the comment where mListSelectionHidden is declared
+ return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
+ }
+
+ /**
+ * <p>Returns the focus state in the drop down.</p>
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean hasWindowFocus() {
+ return mHijackFocus || super.hasWindowFocus();
+ }
+
+ /**
+ * <p>Returns the focus state in the drop down.</p>
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean isFocused() {
+ return mHijackFocus || super.isFocused();
+ }
+
+ /**
+ * <p>Returns the focus state in the drop down.</p>
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean hasFocus() {
+ return mHijackFocus || super.hasFocus();
+ }
+ }
+
+ 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() {
+ 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 < getWidth() && y >= 0 && y < 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();
+ }
+ }
+ }
+}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 0378328..d404ce7 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -16,27 +16,28 @@
package android.widget;
-import com.android.internal.R;
+import java.lang.ref.WeakReference;
import android.content.Context;
import android.content.res.TypedArray;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.Gravity;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnScrollChangedListener;
-import android.view.View.OnTouchListener;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.IBinder;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.View.OnTouchListener;
+import android.view.ViewTreeObserver.OnScrollChangedListener;
-import java.lang.ref.WeakReference;
+import com.android.internal.R;
/**
* <p>A popup window that can be used to display an arbitrary view. The popup
@@ -157,12 +158,21 @@
* <p>The popup does provide a background.</p>
*/
public PopupWindow(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, 0);
+ }
+
+ /**
+ * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p>
+ *
+ * <p>The popup does not provide a background.</p>
+ */
+ public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
TypedArray a =
context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.PopupWindow, defStyle, 0);
+ attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);
mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
@@ -1315,6 +1325,7 @@
}
private class PopupViewContainer extends FrameLayout {
+ private static final String TAG = "PopupWindow.PopupViewContainer";
public PopupViewContainer(Context context) {
super(context);
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 2f6dd1e..feb4211 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -24,6 +24,7 @@
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.util.AttributeSet;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -37,10 +38,20 @@
*/
@Widget
public class Spinner extends AbsSpinner implements OnClickListener {
+ private static final String TAG = "Spinner";
- private CharSequence mPrompt;
- private AlertDialog mPopup;
-
+ /**
+ * Use a dialog window for selecting spinner options.
+ */
+ public static final int MODE_DIALOG = 0;
+
+ /**
+ * Use a dropdown anchored to the Spinner for selecting spinner options.
+ */
+ public static final int MODE_DROPDOWN = 1;
+
+ private SpinnerPopup mPopup;
+
public Spinner(Context context) {
this(context, null);
}
@@ -55,10 +66,43 @@
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.Spinner, defStyle, 0);
- mPrompt = a.getString(com.android.internal.R.styleable.Spinner_prompt);
+ final int mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode,
+ MODE_DIALOG);
+
+ switch (mode) {
+ case MODE_DIALOG: {
+ mPopup = new DialogPopup();
+ break;
+ }
+
+ case MODE_DROPDOWN: {
+ final int hintResource = a.getResourceId(
+ com.android.internal.R.styleable.Spinner_popupPromptView, 0);
+
+ DropdownPopup popup = new DropdownPopup(context, attrs, defStyle, hintResource);
+
+ popup.setBackgroundDrawable(a.getDrawable(
+ com.android.internal.R.styleable.Spinner_popupBackground));
+ popup.setVerticalOffset(a.getDimensionPixelOffset(
+ com.android.internal.R.styleable.Spinner_dropDownVerticalOffset, 0));
+ popup.setHorizontalOffset(a.getDimensionPixelOffset(
+ com.android.internal.R.styleable.Spinner_dropDownHorizontalOffset, 0));
+
+ mPopup = popup;
+ break;
+ }
+ }
+
+ mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
a.recycle();
}
+
+ @Override
+ public void setAdapter(SpinnerAdapter adapter) {
+ super.setAdapter(adapter);
+ mPopup.setAdapter(new DropDownAdapter(adapter));
+ }
@Override
public int getBaseline() {
@@ -246,15 +290,10 @@
if (!handled) {
handled = true;
- Context context = getContext();
-
- final DropDownAdapter adapter = new DropDownAdapter(getAdapter());
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
- if (mPrompt != null) {
- builder.setTitle(mPrompt);
+ if (!mPopup.isShowing()) {
+ mPopup.show();
}
- mPopup = builder.setSingleChoiceItems(adapter, getSelectedItemPosition(), this).show();
}
return handled;
@@ -271,7 +310,7 @@
* @param prompt the prompt to set
*/
public void setPrompt(CharSequence prompt) {
- mPrompt = prompt;
+ mPopup.setPromptText(prompt);
}
/**
@@ -279,16 +318,42 @@
* @param promptId the resource ID of the prompt to display when the dialog is shown
*/
public void setPromptId(int promptId) {
- mPrompt = getContext().getText(promptId);
+ setPrompt(getContext().getText(promptId));
}
/**
* @return The prompt to display when the dialog is shown
*/
public CharSequence getPrompt() {
- return mPrompt;
+ return mPopup.getHintText();
}
+ /*
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mPopup.onKeyDown(keyCode, event)) {
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mPopup.onKeyUp(keyCode, event)) {
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (mPopup.onKeyPreIme(keyCode, event)) {
+ return true;
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+ */
+
/**
* <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
* into a ListAdapter.</p>
@@ -384,4 +449,123 @@
return getCount() == 0;
}
}
+
+ /**
+ * Implements some sort of popup selection interface for selecting a spinner option.
+ * Allows for different spinner modes.
+ */
+ private interface SpinnerPopup {
+ public void setAdapter(ListAdapter adapter);
+
+ /**
+ * Show the popup
+ */
+ public void show();
+
+ /**
+ * Dismiss the popup
+ */
+ public void dismiss();
+
+ /**
+ * @return true if the popup is showing, false otherwise.
+ */
+ public boolean isShowing();
+
+ /**
+ * Set hint text to be displayed to the user. This should provide
+ * a description of the choice being made.
+ * @param hintText Hint text to set.
+ */
+ public void setPromptText(CharSequence hintText);
+ public CharSequence getHintText();
+ }
+
+ private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
+ private AlertDialog mPopup;
+ private ListAdapter mListAdapter;
+ private CharSequence mPrompt;
+
+ public void dismiss() {
+ mPopup.dismiss();
+ mPopup = null;
+ }
+
+ public boolean isShowing() {
+ return mPopup != null ? mPopup.isShowing() : false;
+ }
+
+ public void setAdapter(ListAdapter adapter) {
+ mListAdapter = adapter;
+ }
+
+ public void setPromptText(CharSequence hintText) {
+ mPrompt = hintText;
+ }
+
+ public CharSequence getHintText() {
+ return mPrompt;
+ }
+
+ public void show() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ if (mPrompt != null) {
+ builder.setTitle(mPrompt);
+ }
+ mPopup = builder.setSingleChoiceItems(mListAdapter,
+ getSelectedItemPosition(), this).show();
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ setSelection(which);
+ dismiss();
+ }
+ }
+
+ private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
+ private CharSequence mHintText;
+ private TextView mHintView;
+ private int mHintResource;
+
+ public DropdownPopup(Context context, AttributeSet attrs,
+ int defStyleRes, int hintResource) {
+ super(context, attrs, 0, defStyleRes);
+
+ mHintResource = hintResource;
+
+ setAnchorView(Spinner.this);
+ setModal(true);
+ setPromptPosition(POSITION_PROMPT_BELOW);
+ setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView parent, View v, int position, long id) {
+ Spinner.this.setSelection(position);
+ dismiss();
+ }
+ });
+ }
+
+ public CharSequence getHintText() {
+ return mHintText;
+ }
+
+ public void setPromptText(CharSequence hintText) {
+ mHintText = hintText;
+ if (mHintView != null) {
+ mHintView.setText(hintText);
+ }
+ }
+
+ public void show() {
+ if (mHintView == null) {
+ final TextView textView = (TextView) LayoutInflater.from(getContext()).inflate(
+ mHintResource, null).findViewById(com.android.internal.R.id.text1);
+ textView.setText(mHintText);
+ setPromptView(textView);
+ mHintView = textView;
+ }
+ super.show();
+ getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ setSelection(Spinner.this.getSelectedItemPosition());
+ }
+ }
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e7b83c7..af8d91c 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2207,6 +2207,30 @@
<declare-styleable name="Spinner">
<!-- The prompt to display when the spinner's dialog is shown. -->
<attr name="prompt" format="reference" />
+ <!-- Display mode for spinner options. -->
+ <attr name="spinnerMode" format="enum">
+ <!-- Spinner options will be presented to the user as a dialog window. -->
+ <enum name="dialog" value="0" />
+ <!-- Spinner options will be presented to the user as an inline dropdown
+ anchored to the spinner widget itself. -->
+ <enum name="dropdown" value="1" />
+ </attr>
+ <!-- List selector to use for spinnerMode="dropdown" display. -->
+ <attr name="dropDownSelector" />
+ <!-- Background drawable to use for the dropdown in spinnerMode="dropdown". -->
+ <attr name="popupBackground" />
+ <!-- Vertical offset from the spinner widget for positioning the dropdown in
+ spinnerMode="dropdown". -->
+ <attr name="dropDownVerticalOffset" />
+ <!-- Horizontal offset from the spinner widget for positioning the dropdown
+ in spinnerMode="dropdown". -->
+ <attr name="dropDownHorizontalOffset" />
+ <!-- Width of the dropdown in spinnerMode="dropdown". -->
+ <attr name="dropDownWidth" />
+ <!-- Reference to a layout to use for displaying a prompt in the dropdown for
+ spinnerMode="dropdown". This layout must contain a TextView with the id
+ @android:id/text1 to be populated with the prompt text. -->
+ <attr name="popupPromptView" format="reference" />
</declare-styleable>
<declare-styleable name="DatePicker">
<!-- The first year (inclusive), for example "1940". -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 1aa55e2..7ef842f5 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1286,5 +1286,6 @@
<public type="id" name="home" />
<public type="style" name="Theme.WithActionBar" />
+ <public type="style" name="Widget.Spinner.DropDown" />
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index c90770c5..4f74413 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -451,6 +451,18 @@
<style name="Widget.Spinner">
<item name="android:background">@android:drawable/btn_dropdown</item>
<item name="android:clickable">true</item>
+ <item name="android:spinnerMode">dialog</item>
+
+ <item name="android:dropDownSelector">@android:drawable/list_selector_background</item>
+ <item name="android:popupBackground">@android:drawable/spinner_dropdown_background</item>
+ <item name="android:dropDownVerticalOffset">-10dip</item>
+ <item name="android:dropDownHorizontalOffset">0dip</item>
+ <item name="android:dropDownWidth">wrap_content</item>
+ <item name="android:popupPromptView">@android:layout/simple_dropdown_hint</item>
+ </style>
+
+ <style name="Widget.Spinner.DropDown">
+ <item name="android:spinnerMode">dropdown</item>
</style>
<style name="Widget.TextView.PopupMenu">
diff --git a/preloaded-classes b/preloaded-classes
index 4098cbf..da5502d 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -563,8 +563,6 @@
android.widget.AdapterView
android.widget.ArrayAdapter
android.widget.AutoCompleteTextView
-android.widget.AutoCompleteTextView$DropDownItemClickListener
-android.widget.AutoCompleteTextView$DropDownListView
android.widget.BaseAdapter
android.widget.BaseExpandableListAdapter
android.widget.CheckBox
@@ -582,6 +580,7 @@
android.widget.ImageView
android.widget.ImageView$ScaleType
android.widget.LinearLayout
+android.widget.ListPopupWindow
android.widget.ListView
android.widget.ListView$SavedState
android.widget.MediaController