AutoCompleteTextView的使用和源码分析以及实现响应式输入提示功能
来源:互联网 发布:淘宝改后台软件安全吗 编辑:程序博客网 时间:2024/05/16 19:25
在我的项目中在使用AutoCompleteTextView用来为用户输入提示。提示内容是来自网络返回,效果如下
这篇博客记录我的分析和编码过程
1.简单AutoCompleteTextView使用
简单代码示例1:
public class CountriesActivity extends Activity { protected void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.countries); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, COUNTRIES); AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.countries_list); textView.setAdapter(adapter); } private static final String[] COUNTRIES = new String[] { "Belgium", "France", "Italy", "Germany", "Spain" }; }
这是官网最简答的实例。
说明一下直接运行的话要输入两个字符以上才能弹出提示,这是默认的配置在源码中看到 mThreshold = a.getInt(R.styleable.AutoCompleteTextView_completionThreshold, 2);
就说明没有设置默认值是2。一般的使用时需要修改一些默认属性设置
AutoCompleteTextView关键属性说明:
有了上面的说明一般的使用控件就没有问题,但是我的项目需求是填充内容来自网络随时更新,不能在代码中写死。通过分析上面的实例我当时就想到定义全局的Adapter和mListHttpHint数据源在联网部分随时刷新弹出提示。
2.实现上面的构想
代码1:
mACTVSearch.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { //输入前应该有清空 mListHttpHint的数据操作 mListHttpHint.clear(); } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { //用户输入完成后调用 在这里取得输入结果 联网 mListHttpHint.addAll(gethttpHint(s));//将联网返回结果添加进数据集合 mAdapter.notifyDataSetChanged(); } });
上面代码只说明大概思路,不考虑联网回调同步问题。但是运行上面结果一直得不到想要的效果。联网成功返回但是mAdapter.notifyDataSetChanged()
没有效果,没有弹出输入提示,后来改用ArrayAdapter<String>
的addAll()方法
源码如下:
public void addAll(T ... items) { synchronized (mLock) { if (mOriginalValues != null) { Collections.addAll(mOriginalValues, items); } else { Collections.addAll(mObjects, items); } } if (mNotifyOnChange) notifyDataSetChanged(); }
明显看到在ArrayAdapter<String>
中添加数据也会调用通知更新方法。修改代码1后,任然没有实现效果,产生的效果是每次输入的下次才能看到添加的提示内容。
思考解决办法1 从notifyDataSetChanged()切入
当时我想到的是 notifyDataSetChanged()没有用,应该是方法没有成功通知界面刷新,重写过BaseAdapter的同学应该都知道这个方法的使用。BaseAdapter采用观察者模式,当和BaseAdapter绑定的数据集修改之后,直接调用notifyDataSetChanged()方法会产生UI界面重绘效果。
BaseAdapter源码如下:
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { private final DataSetObservable mDataSetObservable = new DataSetObservable();//目标对象 作为被观察者 public boolean hasStableIds() { return false; } public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer);//注册观察者对象 } public void unregisterDataSetObserver(DataSetObserver observer) { mDataSetObservable.unregisterObserver(observer);//反注册对象 } /** * Notifies the attached observers that the underlying data has been changed * and any View reflecting the data set should refresh itself. */ public void notifyDataSetChanged() { mDataSetObservable.notifyChanged();//通知注册的观察者对象 } /** * Notifies the attached observers that the underlying data is no longer valid * or available. Once invoked this adapter is no longer valid and should * not report further data set changes. */ public void notifyDataSetInvalidated() { mDataSetObservable.notifyInvalidated();//通知数据无效 } public boolean areAllItemsEnabled() { return true; } public boolean isEnabled(int position) { return true; } public View getDropDownView(int position, View convertView, ViewGroup parent) { return getView(position, convertView, parent); } public int getItemViewType(int position) { return 0;//返回item视图的类型 一般多种item视图的adapter会重写该方法,多种视图常见的使用 是聊天界面一样的左右文字显示 } public int getViewTypeCount() { return 1;//返回视图类型的数量 重写getItemViewType也要跟着重写 } public boolean isEmpty() { return getCount() == 0; }}
目前使用ArrayAdapter<T> extends BaseAdapter
就是子类ArrayAdapter也具有一样的通知更新方法。并且给代码1添加数据后调用mAdapter.getCount()
log输出后也看到的adapter中数据集数量发生的变化。说明问题不在notifyDataSetChanged()上。
思考解决办法2 从AutoCompleteTextView的过滤行为切入
使用AutoCompleteTextView时我直接使用了ArrayAdapter,传入系统布局文件和数据源
mAdapter = new SearHintAdapter(mContext, android.R.layout.simple_spinner_dropdown_item,mListHttpHint); mACTVSearch.setAdapter(mAdapter);
class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSpinnerAdapter
从类的继承和实现上看到 ArrayAdapter还实现了Filterable接口
Filterable说明:
Defines a filterable behavior. A filterable class can have its data constrained by a filter. Filterable classes are usually Adapter implementations.
大概意思是:这是个过滤行为,实现的类应该能够过滤数据,adapter类都实现了该接口
在ArrayAdapter源码的470行中也确实重写了getFilter()方法返回一个自定义的过滤器
public Filter getFilter() { if (mFilter == null) { mFilter = new ArrayFilter(); } return mFilter; }
自定义的ArrayFilter重写了两个方法:
1.过滤方法实现
@Override protected FilterResults performFiltering(CharSequence prefix) { //....省略代码 数据初始化和检查输入代码 //关键代码:根据约束条件调用一个工作线程过滤数据。子类必须实现该方法来执行过滤操作。过滤结果以Filter.FilterResults的形式返回 //参数说明:CharSequence prefix 输入项也就是用户输入在TextView的内容。ArrayList values 就是Adapter中对原始数据mObjects的复制对象 // 最最关键!valueText.startsWith(prefixString) 匹配输入项的第一个字符 匹配成功的对象放入返回对象中 final int count = values.size(); final ArrayList<T> newValues = new ArrayList<T>();//新建一个用来存储返回结果ArrayList对象 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;}
2.发布数据方法
@Override protected void publishResults(CharSequence constraint, FilterResults results) { // results 方法1返回结果对象 //检查返回对象 当大于0个数据时候 调用notifyDataSetChanged()更新UI //noinspection unchecked mObjects = (List<T>) results.values; if (results.count > 0) { notifyDataSetChanged(); } else { notifyDataSetInvalidated(); } }
看到上面的源码终于找到了解决问题的关键。当我们使用简答代码示例1直接使用AutoCompleteTextView的时候,每次输入,系统就会从源数据中找匹配项,然后通知更新UI。这是最简单的使用,系统已经为我们定义好了过滤行为和更新方法。我在代码1中添加了数据项,但是输入没有匹配成功,所以结果没有显示也就没有更新UI。
解决问题
通过上面分析就知道问题出在了过滤行为上,我的需求是每次用户输入根据输入内容从网络调取提示内容,更新到UI。添加到adapter每项数据是最新联网结果,不需要过滤数据。
1,自定义继承ArrayAdapter的SearchHintAdapter,自定义不会过滤掉任何数据的mFilter
import android.content.Context;import android.widget.ArrayAdapter;import android.widget.Filter;import java.util.ArrayList;import java.util.List;/** * Created by LiCola on 2016/03/21 0:41 */public class SearHintAdapter extends ArrayAdapter<String> { private static final String TAG = "SearHintAdapter"; private Filter mFilter; private List<String> mObjects; public SearHintAdapter(Context context, int resource, List<String> objects) { super(context, resource, objects); mObjects = objects; } @Override public Filter getFilter() { if (mFilter == null) { mFilter = new HintFilter(); } return mFilter; } /** * <p>An array filter constrains the content of the array adapter with * a prefix. Each item that does not start with the supplied prefix * is removed from the list.</p> * 重写过滤类 自定义一个不会过滤任何数的Filter */ private class HintFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence prefix) { ArrayList<Object> suggestions = new ArrayList<Object>(); for (String s : mObjects) { suggestions.add(s);// Logger.d(s); } FilterResults filterResults = new FilterResults(); filterResults.values = suggestions; filterResults.count = suggestions.size();// Logger.d("filterResults.count=" + filterResults.count); return filterResults; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { //noinspection unchecked mObjects = (List<String>) results.values;// Logger.d("results.count=" + results.count); if (results.count > 0) { notifyDataSetChanged(); } else { notifyDataSetInvalidated(); } } }}
2.监听AutoCompleteTextView的输入操作,将联网数据添加到mAdapter中
RxTextView.textChanges(mACTVSearch)//观察mACTVSearch的输入变化 .observeOn(Schedulers.io()) .filter(new Func1<CharSequence, Boolean>() { @Override public Boolean call(CharSequence charSequence) { return charSequence.length()>0;//过滤空输入 } }) //debounce 函数 过滤掉由Observable发射的速率过快的数据 .debounce(300, TimeUnit.MILLISECONDS) //switchMap函数 每当源Observable发射一个新的数据项(Observable)时, //它将取消订阅并停止监视之前那个数据项产生的Observable,并开始监视当前发射的这一个。 .switchMap(new Func1<CharSequence, Observable<SearchHintBean>>() { @Override public Observable<SearchHintBean> call(CharSequence charSequence) { return getSearHit(charSequence.toString());//Retrofit开始联网 直接返回Observable<SearchHintBean> } }) .map(new Func1<SearchHintBean, List<String>>() { @Override public List<String> call(SearchHintBean searchHintBean) { return searchHintBean.getResult();//转换联网返回结果 } }) .filter(new Func1<List<String>, Boolean>() { @Override public Boolean call(List<String> strings) { return strings.size()>0;//过滤联网空返回 } }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<String>>() { @Override public void onCompleted() { Logger.d(); } @Override public void onError(Throwable e) { Logger.d(e.toString()); } @Override public void onNext(List<String> strings) { Logger.d(strings.size()+""); mAdapter.addAll(strings); } });
上面代码用到Retrofit联网框架,RxAndroid和RxBinding响应式编码,很好的优化了代码,不了解的同学可以点击链接学习。
3.附上布局文件
<AutoCompleteTextView android:id="@+id/actv_search" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/hint_search" android:singleLine="true" android:maxLines="1" android:inputType="textAutoComplete" android:completionThreshold="1"//中文输入的话最好修改为1 android:popupBackground="@color/white" />
总结
这是对AutoCompleteTextView的深度使用,从源码的角度找到问题,适度自定义,满足开发需求。比较网络看到很多篇博客写得更深入,当有同学遇到和我一样的需求时候,希望能够帮到大家。
- AutoCompleteTextView的使用和源码分析以及实现响应式输入提示功能
- 使用AutoCompleteTextView和SharePreference实现搜索历史自动提示功能
- 使用AutoCompleteTextView和MultiAutoCompleteTextView提示输入内容
- android AutoCompleteTextView 实现输入提示
- AutoCompleteTextView输入汉字拼音首字母实现过滤提示(支持多音字,Filterable的使用)
- AutoCompleteTextView输入框自动提示功能
- AutoCompleteTextView(输入提示)和自定义键盘
- 使用AutoCompleteTextView和MultiAutoCompleteTextView实现输入自动匹配
- 强大的提示控件TextInputLayout使用以及源码分析
- 强大的提示控件TextInputLayout使用以及源码分析
- AutoCompleteTextView (自动完成输入框自动提示功能的菜单)
- 使用AutoCompleteTextView实现自动匹配输入的内容
- 使用AutoCompleteTextview实现动态匹配输入的内容
- AutoCompleteTextView 自动提示输入
- 具有自动提示功能的菜单:AutoCompleteTextView
- AutoCompleteTextView不输入字符获取提示以及相关焦点问
- Android实现搜索功能 AutoCompleteTextView和MultiAutoCompleteTextView的用法
- 实现下拉文本框的输入提示功能
- 3D电视已死 4K和曲面屏成厂商下一个目标
- 关于MPMoviePlayerController类播放视频时,外放没有声音的问题
- (CROC 2016 - Elimination Round (Rated Unofficial Edition))C. Enduring Exodus(二分)
- Android-在动作栏中添加“设置图标”
- 什么是C++虚函数、虚函数的作用和使用方法
- AutoCompleteTextView的使用和源码分析以及实现响应式输入提示功能
- Fixing your iOS build scripts PackageApplication ERROR
- 选择支持向量机(SVM)核函数
- Iframe父页面与子页面之间的相互调用
- 10.1Android性能优化之布局优化
- 第七届蓝桥杯省赛C/C++B组省赛题解
- LeetCode 70 Climbing Stairs
- java第十一节-包装类,基本类型,String之间的转换
- 解决各种IE兼容问题,IE6,IE7,IE8,IE9,IE10