AutoCompleteTextView源码分析
项目中需要使用AutoCompleteTextView实现邮箱后缀名提示,因此把AutoCompleteTextView源码也顺便看了一下。
AutoCompleteTextView继承了EditText,同时实现了Filter.FilterListener接口。
public class AutoCompleteTextView extends EditText implements Filter.FilterListener
public static interface FilterListener { /** * <p>Notifies the end of a filtering operation.</p> * * @param count the number of values computed by the filter */ public void onFilterComplete(int count); }
查看AutoCompleteTextView构造函数:
public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mPopup = new ListPopupWindow(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle); ...... mThreshold = a.getInt( R.styleable.AutoCompleteTextView_completionThreshold, 2); // 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. // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return // this TextView, as a default anchoring point. mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor, View.NO_ID); ..... addTextChangedListener(new MyWatcher()); }
从以上代码可以看出,AutoCompleteTextView其实是EditText+ListPopupWindow而已,而ListPopupWindow则是添加了ListView对PopupWindow进行了封装。当我们通过AutoCompleteTextView.setAdapter其实是传递到了ListPopupWindow.setAdapter,最终则是传递到了DropDownListView(继承于ListView)。AutoCompleteTextView则当作PopupWindow的anchor,因此PopupWindow才会显示在EditText正下方。
private class MyWatcher implements TextWatcher { public void afterTextChanged(Editable s) { doAfterTextChanged(); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { doBeforeTextChanged(); } public void onTextChanged(CharSequence s, int start, int before, int count) { } }
MyWatcher是用于监听EditText输入内容变化的,当输入内容变化之后会触发doAfterTextChanged(),代码如下:
void doAfterTextChanged() { if (mBlockCompletion) return; // if the list was open before the keystroke, but closed afterwards, // then something in the keystroke processing (an input filter perhaps) // called performCompletion() and we shouldn't do any more processing. if (DEBUG) Log.v(TAG, "after text changed: openBefore=" + mOpenBefore + " open=" + isPopupShowing()); if (mOpenBefore && !isPopupShowing()) { return; } // the drop down is shown only when a minimum number of characters // was typed in the text view if (enoughToFilter()) { if (mFilter != null) { mPopupCanBeUpdated = true; performFiltering(getText(), mLastKeyCode); } } else { // drop down is automatically dismissed when enough characters // are deleted from the text view if (!mPopup.isDropDownAlwaysVisible()) { dismissDropDown(); } if (mFilter != null) { mFilter.filter(null); } } }
其中enoughToFilter()则是判断输入内容长度是否满足过滤条件,可以通过setThreshold(int threshold)更改。performFiltering(getText(), mLastKeyCode)代码如下:
protected void performFiltering(CharSequence text, int keyCode) { mFilter.filter(text, this); }
那么mFilter是从哪里来的呢?通过查找搜索,可以看到如下:
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; } mPopup.setAdapter(mAdapter); }
原来是通过Adapter来的,那么我们传递进来的Adapter必须实现Filterable接口
public interface Filterable { /** * <p>Returns a filter that can be used to constrain data with a filtering * pattern.</p> * * <p>This method is usually implemented by {@link android.widget.Adapter} * classes.</p> * * @return a filter used to constrain data */ Filter getFilter(); }
我们再看Filter.filter()代码:
public final void filter(CharSequence constraint, FilterListener listener) { synchronized (mLock) { if (mThreadHandler == null) { HandlerThread thread = new HandlerThread( THREAD_NAME, android.os.Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mThreadHandler = new RequestHandler(thread.getLooper()); } final long delay = (mDelayer == null) ? 0 : mDelayer.getPostingDelay(constraint); Message message = mThreadHandler.obtainMessage(FILTER_TOKEN); RequestArguments args = new RequestArguments(); // make sure we use an immutable copy of the constraint, so that // it doesn't change while the filter operation is in progress args.constraint = constraint != null ? constraint.toString() : null; args.listener = listener; message.obj = args; mThreadHandler.removeMessages(FILTER_TOKEN); mThreadHandler.removeMessages(FINISH_TOKEN); mThreadHandler.sendMessageDelayed(message, delay); } }
由此进入到RequestHandler的handleMessage()
public void handleMessage(Message msg) { int what = msg.what; Message message; switch (what) { case FILTER_TOKEN: RequestArguments args = (RequestArguments) msg.obj; try { args.results = performFiltering(args.constraint); } catch (Exception e) { args.results = new FilterResults(); Log.w(LOG_TAG, "An exception occured during performFiltering()!", e); } finally { message = mResultHandler.obtainMessage(what); message.obj = args; message.sendToTarget(); } synchronized (mLock) { if (mThreadHandler != null) { Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN); mThreadHandler.sendMessageDelayed(finishMessage, 3000); } } break; case FINISH_TOKEN: synchronized (mLock) { if (mThreadHandler != null) { mThreadHandler.getLooper().quit(); mThreadHandler = null; } } break; } }
可以看到performFiltering(CharSequence constraint)为abstract,那么在Adapter里面实现Filterable接口时,必须继承Filter并且实现performFiltering(CharSequence constraint),Filter还有另外一个抽象方法
publishResults(CharSequence constraint,FilterResults results)。其中performFiltering()则是过滤规则(我们需要自己实现才能满足要求),publishResults()则是处理完成之后返回结果。publishResults()是在ResultsHandler里面调用的
private class ResultsHandler extends Handler { /** * <p>Messages received from the request handler are processed in the * UI thread. The processing involves calling * {@link Filter#publishResults(CharSequence, * android.widget.Filter.FilterResults)} * to post the results back in the UI and then notifying the listener, * if any.</p> * * @param msg the filtering results */ @Override public void handleMessage(Message msg) { RequestArguments args = (RequestArguments) msg.obj; publishResults(args.constraint, args.results); if (args.listener != null) { int count = args.results != null ? args.results.count : -1; args.listener.onFilterComplete(count); } } }
其中args.listener则是在AutoCompleteTextView.performFiltering(CharSequence text, int keyCode)传入的,那么此时会执行AutoCompleteTextView中的onFilterComplete(),然后执行updateDropDownForFilter()把ListPopupWindow(PopupWindow)显示出来。到此则完成一次过滤。下面我们看下ArrayAdapter中的过滤规则。
public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
public Filter getFilter() { if (mFilter == null) { mFilter = new ArrayFilter(); } return mFilter; }
private class ArrayFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence prefix) { FilterResults results = new FilterResults(); if (mOriginalValues == null) { synchronized (mLock) { mOriginalValues = new ArrayList<T>(mObjects); } } if (prefix == null || prefix.length() == 0) { ArrayList<T> list; synchronized (mLock) { list = new ArrayList<T>(mOriginalValues); } results.values = list; results.count = list.size(); } else { String prefixString = prefix.toString().toLowerCase(); ArrayList<T> values; synchronized (mLock) { values = new ArrayList<T>(mOriginalValues); } final int count = values.size(); final ArrayList<T> newValues = new ArrayList<T>(); for (int i = 0; i < count; i++) { final T value = values.get(i); final String valueText = value.toString().toLowerCase(); // First match against the whole, non-splitted value if (valueText.startsWith(prefixString)) { newValues.add(value); } else { final String[] words = valueText.split(" "); final int wordCount = words.length; // Start at index 0, in case valueText starts with space(s) for (int k = 0; k < wordCount; k++) { if (words[k].startsWith(prefixString)) { newValues.add(value); break; } } } } results.values = newValues; results.count = newValues.size(); } return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { //noinspection unchecked mObjects = (List<T>) results.values; if (results.count > 0) { notifyDataSetChanged(); } else { notifyDataSetInvalidated(); } } } }
可以看到ArrayAdapter中的过滤则是利用string.startwith()来做处理的,在publishResults()更新数据。
因此要实现自定义的AutoCompleteTextView则只需要重写BaseAdapter并且自定义Filter的过滤规则即可。