diff options
Diffstat (limited to 'core/java/android/widget/AutoCompleteTextView.java')
-rw-r--r-- | core/java/android/widget/AutoCompleteTextView.java | 315 |
1 files changed, 193 insertions, 122 deletions
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index 75d0f31..e15a520 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -18,6 +18,7 @@ package android.widget; import android.content.Context; import android.content.res.TypedArray; +import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.text.Editable; @@ -91,6 +92,13 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe 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 int mHintResource; @@ -129,10 +137,12 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe private boolean mBlockCompletion; - private AutoCompleteTextView.ListSelectorHider mHideSelector; + private ListSelectorHider mHideSelector; private Runnable mShowDropDownRunnable; + private Runnable mResizePopupRunnable = new ResizePopupRunnable(); - private AutoCompleteTextView.PassThroughClickListener mPassThroughClickListener; + private PassThroughClickListener mPassThroughClickListener; + private PopupDataSetObserver mObserver; public AutoCompleteTextView(Context context) { this(context, null); @@ -172,7 +182,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor, View.NO_ID); - // For dropdown width, the developer can specify a specific width, or FILL_PARENT + // 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); @@ -210,22 +220,14 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * Private hook into the on click event, dispatched from {@link PassThroughClickListener} */ private void onClickImpl() { - // If the dropdown is showing, bring it back in front of the soft - // keyboard when the user touches the text field. - if (mPopup.isShowing() && isInputMethodNotNeeded()) { - ensureImeVisible(); + // If the dropdown is showing, bring the keyboard to the front + // when the user touches the text field. + if (mPopup.isShowing()) { + ensureImeVisible(true); } } /** - * Sets this to be single line; a separate method so - * MultiAutoCompleteTextView can skip this. - */ - /* package */ void finishInit() { - setSingleLine(); - } - - /** * <p>Sets the optional hint text that is displayed at the bottom of the * the matching list. This can be used as a cue to the user on how to * best use the list, or to provide extra information.</p> @@ -240,7 +242,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe /** * <p>Returns the current width for the auto-complete drop down list. This can - * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or + * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p> * * @return the width for the drop down list @@ -253,7 +255,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe /** * <p>Sets the current width for the auto-complete drop down list. This can - * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or + * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p> * * @param width the width to use @@ -266,7 +268,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe /** * <p>Returns the current height for the auto-complete drop down list. This can - * be a fixed height, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill + * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height * of the drop down's content.</p> * @@ -280,7 +282,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe /** * <p>Sets the current height for the auto-complete drop down list. This can - * be a fixed height, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill + * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height * of the drop down's content.</p> * @@ -448,7 +450,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe public boolean isDropDownDismissedOnCompletion() { return mDropDownDismissedOnCompletion; } - + /** * Sets whether the drop-down is dismissed when a suggestion is clicked. This is * true by default. @@ -590,10 +592,16 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * @see android.widget.ListAdapter */ public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { + if (mObserver == null) { + mObserver = new PopupDataSetObserver(); + } else if (mAdapter != null) { + mAdapter.unregisterDataSetObserver(mObserver); + } mAdapter = adapter; if (mAdapter != null) { //noinspection unchecked mFilter = ((Filterable) mAdapter).getFilter(); + adapter.registerDataSetObserver(mObserver); } else { mFilter = null; } @@ -609,8 +617,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe && !mDropDownAlwaysVisible) { // 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) { + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { getKeyDispatcherState().startTracking(event, this); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { @@ -658,10 +665,25 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) { int curIndex = mDropDownList.getSelectedItemPosition(); boolean consumed; + final boolean below = !mPopup.isAboveAnchor(); - if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= 0) || - (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= - mDropDownList.getAdapter().getCount() - 1)) { + + 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(); @@ -701,11 +723,11 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe 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 - Adapter adapter = mDropDownList.getAdapter(); - if (adapter != null && curIndex == adapter.getCount() - 1) { + if (curIndex == lastItem) { return true; } - } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex == 0) { + } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && + curIndex == firstItem) { return true; } } @@ -858,16 +880,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe return ListView.INVALID_POSITION; } - - /** - * @hide - * @return {@link android.widget.ListView#getChildCount()} of the drop down if it is showing, - * otherwise 0. - */ - protected int getDropDownChildCount() { - return mDropDownList == null ? 0 : mDropDownList.getChildCount(); - } - /** * <p>Starts filtering the content of the drop down list. The filtering * pattern is the content of the edit box. Subclasses should override this @@ -968,25 +980,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe mBlockCompletion = false; } } - - /** - * Like {@link #setTextKeepState(CharSequence)}, except that it can disable filtering. - * - * @param filter If <code>false</code>, no filtering will be performed - * as a result of this call. - * - * @hide Pending API council approval. - */ - public void setTextKeepState(CharSequence text, boolean filter) { - if (filter) { - setTextKeepState(text); - } else { - mBlockCompletion = true; - setTextKeepState(text); - mBlockCompletion = false; - } - } - + /** * <p>Performs the text completion by replacing the current text by the * selected item. Subclasses should override this method to avoid replacing @@ -1005,6 +999,11 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe /** {@inheritDoc} */ public void onFilterComplete(int count) { + updateDropDownForFilter(count); + + } + + private void updateDropDownForFilter(int count) { // Not attached to window, don't update drop-down if (getWindowVisibility() == View.GONE) return; @@ -1027,16 +1026,30 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); - performValidation(); if (!hasWindowFocus && !mDropDownAlwaysVisible) { dismissDropDown(); } } @Override + protected void onDisplayHint(int hint) { + super.onDisplayHint(hint); + switch (hint) { + case INVISIBLE: + if (!mDropDownAlwaysVisible) { + dismissDropDown(); + } + break; + } + } + + @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); - performValidation(); + // Perform validation if the view is losing focus. + if (!focused) { + performValidation(); + } if (!focused && !mDropDownAlwaysVisible) { dismissDropDown(); } @@ -1067,11 +1080,11 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } @Override - protected boolean setFrame(int l, int t, int r, int b) { + protected boolean setFrame(final int l, int t, final int r, int b) { boolean result = super.setFrame(l, t, r, b); if (mPopup.isShowing()) { - mPopup.update(this, r - l, -1); + showDropDown(); } return result; @@ -1100,11 +1113,13 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe /** * Ensures that the drop down is not obscuring the IME. - * + * @param visible whether the ime should be in front. If false, the ime is pushed to + * the background. * @hide internal used only here and SearchDialog */ - public void ensureImeVisible() { - mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + public void ensureImeVisible(boolean visible) { + mPopup.setInputMethodMode(visible + ? PopupWindow.INPUT_METHOD_NEEDED : PopupWindow.INPUT_METHOD_NOT_NEEDED); showDropDown(); } @@ -1127,7 +1142,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe boolean noInputMethod = isInputMethodNotNeeded(); if (mPopup.isShowing()) { - if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) { + 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; @@ -1137,19 +1152,19 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe widthSpec = mDropDownWidth; } - if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) { + 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.FILL_PARENT; + heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT; if (noInputMethod) { mPopup.setWindowLayoutMode( - mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ? - ViewGroup.LayoutParams.FILL_PARENT : 0, 0); + mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? + ViewGroup.LayoutParams.MATCH_PARENT : 0, 0); } else { mPopup.setWindowLayoutMode( - mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ? - ViewGroup.LayoutParams.FILL_PARENT : 0, - ViewGroup.LayoutParams.FILL_PARENT); + mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? + ViewGroup.LayoutParams.MATCH_PARENT : 0, + ViewGroup.LayoutParams.MATCH_PARENT); } } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { heightSpec = height; @@ -1162,8 +1177,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, widthSpec, heightSpec); } else { - if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) { - widthSpec = ViewGroup.LayoutParams.FILL_PARENT; + if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { + widthSpec = ViewGroup.LayoutParams.MATCH_PARENT; } else { if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { mPopup.setWidth(getDropDownAnchorView().getWidth()); @@ -1172,8 +1187,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } } - if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) { - heightSpec = ViewGroup.LayoutParams.FILL_PARENT; + if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { + heightSpec = ViewGroup.LayoutParams.MATCH_PARENT; } else { if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { mPopup.setHeight(height); @@ -1218,18 +1233,30 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe ViewGroup dropDownView; int otherHeights = 0; - if (mAdapter != null) { + final ListAdapter adapter = mAdapter; + if (adapter != null) { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { - int N = mAdapter.getCount(); - if (N > 20) N = 20; - CompletionInfo[] completions = new CompletionInfo[N]; - for (int i = 0; i < N; i++) { - Object item = mAdapter.getItem(i); - long id = mAdapter.getItemId(i); - completions[i] = new CompletionInfo(id, i, - convertSelectionToString(item)); + final int count = Math.min(adapter.getCount(), 20); + CompletionInfo[] completions = new CompletionInfo[count]; + int realCount = 0; + + for (int i = 0; i < count; i++) { + if (adapter.isEnabled(i)) { + realCount++; + Object item = adapter.getItem(i); + long id = adapter.getItemId(i); + completions[i] = new CompletionInfo(id, i, + convertSelectionToString(item)); + } } + + if (realCount != count) { + CompletionInfo[] tmp = new CompletionInfo[realCount]; + System.arraycopy(completions, 0, tmp, 0, realCount); + completions = tmp; + } + imm.displayCompletions(this, completions); } } @@ -1257,7 +1284,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe mDropDownList = new DropDownListView(context); mDropDownList.setSelector(mDropDownListHighlight); - mDropDownList.setAdapter(mAdapter); + mDropDownList.setAdapter(adapter); mDropDownList.setVerticalFadingEdgeEnabled(true); mDropDownList.setOnItemClickListener(mDropDownItemClickListener); mDropDownList.setFocusable(true); @@ -1278,6 +1305,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe public void onNothingSelected(AdapterView<?> parent) { } }); + mDropDownList.setOnScrollListener(new PopupScrollListener()); if (mItemSelectedListener != null) { mDropDownList.setOnItemSelectedListener(mItemSelectedListener); @@ -1293,7 +1321,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe hintContainer.setOrientation(LinearLayout.VERTICAL); LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, 0, 1.0f + ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f ); hintContainer.addView(dropDownView, hintParams); hintContainer.addView(hintView); @@ -1329,20 +1357,26 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe final int maxHeight = mPopup.getMaxAvailableHeight( getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations); - if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) { - // 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; - } + // 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; } - return mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED, - 0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights; + 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) { @@ -1412,17 +1446,41 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } } + 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) { - if (event.getAction() == MotionEvent.ACTION_DOWN && + final int action = event.getAction(); + if (action == MotionEvent.ACTION_DOWN && mPopup != null && mPopup.isShowing()) { - mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); - showDropDown(); + 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); @@ -1483,8 +1541,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * @return the view for the specified item */ @Override - protected View obtainView(int position) { - View view = super.obtainView(position); + View obtainView(int position, boolean[] isScrap) { + View view = super.obtainView(position, isScrap); if (view instanceof TextView) { ((TextView) view).setHorizontallyScrolling(true); @@ -1493,24 +1551,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe return view; } - /** - * <p>Returns the top padding of the currently selected view.</p> - * - * @return the height of the top padding for the selection - */ - public int getSelectionPaddingTop() { - return mSelectionTopPadding; - } - - /** - * <p>Returns the bottom padding of the currently selected view.</p> - * - * @return the height of the bottom padding for the selection - */ - public int getSelectionPaddingBottom() { - return mSelectionBottomPadding; - } - @Override public boolean isInTouchMode() { // WARNING: Please read the comment where mListSelectionHidden is declared @@ -1608,5 +1648,36 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe if (mWrapped != null) mWrapped.onClick(v); } } - + + 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 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 + // of iterating throught he list of observers. + post(new Runnable() { + public void run() { + final ListAdapter adapter = mAdapter; + if (adapter != null) { + updateDropDownForFilter(adapter.getCount()); + } + } + }); + } + } + + @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(); + } + } + } } |