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的过滤规则即可。

  • 相关阅读:
    316 Remove Duplicate Letters 去除重复字母
    315 Count of Smaller Numbers After Self 计算右侧小于当前元素的个数
    313 Super Ugly Number 超级丑数
    312 Burst Balloons 戳气球
    309 Best Time to Buy and Sell Stock with Cooldown 买股票的最佳时间含冷冻期
    Java 类成员的初始化顺序
    JavaScript 全局
    HTML字符实体
    Java中的toString()方法
    JavaScript 弹窗
  • 原文地址:https://www.cnblogs.com/alexthecoder/p/4271734.html
Copyright © 2011-2022 走看看