首页 > 代码库 > AutoCompleteTextView源码解析

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 =http://www.mamicode.com/ 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的过滤规则即可。

AutoCompleteTextView源码解析