zoukankan      html  css  js  c++  java
  • 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的过滤规则即可。

  • 相关阅读:
    一个主机下创建两个MySQL
    Chrome: Failed to read the 'localStorage' property from 'Window' 的解决办法
    Effective C++
    归并排序
    Daily Note
    关于Beta分布、二项分布与Dirichlet分布、多项分布的关系
    测试公式
    VLAN原理解释
    子网划分
    windows下制作debian U盘启动
  • 原文地址:https://www.cnblogs.com/alexthecoder/p/4271734.html
Copyright © 2011-2022 走看看