大家好,欢迎来到IT知识分享网。
AutoComleteTextView最佳实践-原理剖析篇
此系列文章记录了一次使用AutoCompleteTextView(以下简称ACTV)的踩坑过程,并复盘整个的解决流程。本文着重讲解ACTV触发候选列表展示的代码总流程,深入了解Android的控件传递事件的机制。
以下是此系列所有文章
- 《AutoCompleteTextView最佳实践-总集篇》
- 《AutoCompleteTextView最佳实践-最简例子篇》
- 《AutoCompleteTextView最佳实践-原理剖析篇》
- 《AutoCompleteTextView最佳实践-键盘事件篇》
- 《AutoCompleteTextView最佳实践-其他功能篇》(未完成)
关于作者
景三,程序员,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至与我交流。
首先我们想到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
里直接调用了mFilter
的filter
方法。我们来找一下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
的来源,那我们看一下前文提到调用了mFilter
的filter
方法。
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
里我们来看一下mResultHandler
的handleMessage
。
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
返回的过滤结果传入publishResults
。publishResults
就负责将数据展示出来就行了(设置数据源,调用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中展示候选列表窗口。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/133559.html