Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
254 lines (201 sloc) 9.3 KB

AutoComleteTextView最佳实践-原理剖析篇

banner

此系列文章记录了一次使用AutoCompleteTextView(以下简称ACTV)的踩坑过程,并复盘整个的解决流程。本文着重讲解ACTV触发候选列表展示的代码总流程,深入了解Android的控件传递事件的机制。

以下是此系列所有文章

关于作者

景三,程序员,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至relish.wang@gmail.com与我交流。

首先我们想到ACTV是在输入框的内容文字改变的时候回触发候选列表展示。由于ACTV继承自EditText,我们就想到了这个功能一定是配合TextWatcher实现的。接下来我们来寻找这个TextWatcher

ACTV有多个构造方法,所有的构造方法,最终都调用了参数最多的构造方法。果不其然,我们在这里找到它为自己设置了一个TextWatcher

ACTV的构造方法:

public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr,
        int defStyleRes, Theme popupTheme) {
    super(context, attrs, defStyleAttr, defStyleRes);
    // ...省略部分代码...
    // 添加输入框内容文字变化的监听器
    addTextChangedListener(new MyWatcher());
    // ...省略部分代码...
}

MyWatcher:

// MyTextWatcher是ACTV的一个私有内部类
private class MyWatcher implements TextWatcher {
    public void afterTextChanged(Editable s) {
        doAfterTextChanged();// 调用了ACTV的doAfterTextChanged方法
    }
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        doBeforeTextChanged();
    }
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }
}

void doAfterTextChanged() {
    // ...省略部分代码...
    // 判断是否可以展示候选列表(前文提过ACTV有一个completionThreshold属性, 设置的数字的值代表,达到多少个字符就展示候选列表)
    if (enoughToFilter()) { 
        if (mFilter != null) {
            mPopupCanBeUpdated = true;
            performFiltering(getText(), mLastKeyCode); // 进入这个方法中继续查看
        }
    } else {
       // ...省略部分代码...
    }
}

根据前面贴出的部分源码可知,代码流程走入了MyWatcher里的afterTextChanged,afterTextChanged又调用了ACTV的performFiltering方法。

ACTV#performFiltering:

protected void performFiltering(CharSequence text, int keyCode) {
    // 这里的第二个参数(this)传入的其实是一个FilterListener。
    // (ACTV实现了FilterListener的onFilterComplete方法)
    mFilter.filter(text, this);
}

performFiltering里直接调用了mFilterfilter方法。我们来找一下mFilter这个对象是从哪里来的。

纵观整个ACTV的源码我们发现mFilter只有一处被赋值的地方,就在ACTV的setAdapter方法里:

ACTV#setAdapter:

public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
    // ...省略部分代码...
    if (mAdapter != null) {
        mFilter = ((Filterable) mAdapter).getFilter();// 获取Adapter中的Filter对象
        // ...省略部分代码...
    } else {
        mFilter = null;
    }
		// ...省略部分代码...
}

由上面贴出的源码节选可知,mFilter来自于我们外部为ACTV设置的适配器中。找到了mFilter的来源,那我们看一下前文提到调用了mFilterfilter方法。

Filter#filter:

public final void filter(CharSequence constraint, FilterListener listener) {
    synchronized (mLock) {
				// ...省略部分代码...
        mThreadHandler = new RequestHandler(thread.getLooper());
        Message message = mThreadHandler.obtainMessage(FILTER_TOKEN);
        RequestArguments args = new RequestArguments();
        args.constraint = constraint != null ? constraint.toString() : null;
        args.listener = listener;
        message.obj = args;
        // 向mThreadHandler发送了what为FILTER_TOKEN的Message
        mThreadHandler.sendMessageDelayed(message, delay);
      	// ...省略部分代码...
    }
}

Filter#RequestHandler#handleMessage:

private class RequestHandler extends Handler {
    public void handleMessage(Message msg) {
        int what = msg.what;
        switch (what) {
            case FILTER_TOKEN:
                RequestArguments args = (RequestArguments) msg.obj;
            		// performFiltering具体实现在Adapter中
                args.results = performFiltering(args.constraint);
                Message message = mResultHandler.obtainMessage(what);
                message.obj = args;
                message.sendToTarget();
      					// ...省略部分代码...
                break;
           // ...省略部分代码...
        }
    }
}

看到这里的args.result数据来源于performFiltering方法,这个方法在Filter中是一个抽象方法,具体实现就是在我们为ACTV设置的Adapter中。

Filter#performFiltering:

这个方法实现的代码前文没贴,不过没关系,笔者来说明。这个方法是用来根据ACTV输入框里的关键字来过滤出需要展示候选数据的List。performFiltering中就是实现具体的过滤规则(比如匹配一下用户的手机号,姓名等)。

上面我们看到performFiltering的数据传入Massage被发送到了mResultHandler里我们来看一下mResultHandlerhandleMessage

Filter#ResultsHandler:

private class ResultsHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
         RequestArguments args = (RequestArguments) msg.obj;
				 // 将performFiltering返回的数据作为入参传入publishResults
         publishResults(args.constraint, args.results);
         if (args.listener != null) {
            int count = args.results != null ? args.results.count : -1;
            // 再将过滤出来的数据量传给Listener,通知设置监听器的一方过滤操作结束且告知过滤后的数量
            args.listener.onFilterComplete(count);
        }
    }
}

Filter#publishResults

最后performFiltering返回的过滤结果传入publishResultspublishResults就负责将数据展示出来就行了(设置数据源,调用Adapter的notifiyDataSetChanged)。

ACTV#onFilterComplete:——(ACTV实现FilterListener#publishResults)

publishResults之后就会调用后FilterListener#onFilterComplete, 这个监听器在ACTV中有实现:

public void onFilterComplete(int count) {
    updateDropDownForFilter(count);// 调用下方的方法
}

private void updateDropDownForFilter(int count) {
  	// ...省略部分代码...
  	// (展示数据数量大于0 或 候选窗口总是展示)且 输入文字足够触发过滤器
    if ((count > 0 || dropDownAlwaysVisible) && enoughToFilter) {
        if (hasFocus() && hasWindowFocus() && mPopupCanBeUpdated) {
            showDropDown(); // 展示候选窗口
        }
    } 
  	// ...省略部分代码...
}

ACTV#showDropDown

/**
 * <p>Displays the drop down on screen.</p>
 */
public void showDropDown() {
    // ...省略部分代码...
    mPopup.show();
    // ...省略部分代码...
}

mPopup#show:

/**
 * Show the popup list. If the list is already showing, this method
 * will recalculate the popup's size and position.
 */
@Override
public void show() {
    int height = buildDropDown();// 1 在show的时候会动态计算高度
    // ...省略部分代码...
    if (mPopup.isShowing()) {
    // ...省略部分代码...
    } else {
        final int heightSpec;
        if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
            heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
        } else {
            if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
                heightSpec = height;
            } else {
                heightSpec = mDropDownHeight;
            }
        }
        mPopup.setWidth(widthSpec);
        mPopup.setHeight(heightSpec); // 2 并设置为mPopup的高度
    }
}

通过上述的代码流程梳理,我们大概知道了ACTV的工作原理。我们再简单整理一下过程:

  • 1 ACTV设置TextWatcher

  • 2 TextWatcher的afterTextChanged中执行了mFilter.filter方法(mFilter来自我们为ACTV设置的Adapter)

  • 3 mFilter.filter方法先执行了performFiltering, performFiltering传入的值就是ACTV输入框中的输入的文字

  • 4 performFiltering返回根据关键字匹配到的数据,再将返回数据传入publishResults。

  • 5 最后通知ACTV数据过滤工作结束(onFilterComplete), ACTV在onFilterComplete中展示候选列表窗口。

You can’t perform that action at this time.