ListView过滤搜索功能源码分析
来源:互联网 发布:黄伯云学术造假 知乎 编辑:程序博客网 时间:2024/06/06 12:49
近期维护联系人(Contacts)代码时,发现其中有个功能是根据你输入的字串来搜索出ListView相匹配数据的一项功能。鉴于网上大部分内容都是写其一些基本用法,本文分析ListView其搜索过滤功能的实现,让大家在使用过程中能够做到心里有底,正确的使用好这项功能。
本文章分为两个主题
- ListView中怎么使用搜索过滤功能
- ListView搜索过滤功能的实现分析
ListView中怎么使用搜索过滤功能
实现ListView搜索过滤功能的一般做法如下:
*1. 开启ListView过滤搜索功能
mContactListView.setTextFilterEnabled(true);
*2. 自定义Adapter实现Filterbale接口,实现getFilter()
方法,返回自定义Filter对象。
@Override public Filter getFilter() { return new MyFilter();
*3. 继承Filter实现performFiltering(CharSequence constraint)
和publishResults(CharSequence constraint, FilterResults results)
方法,实现自己的过滤规则。
/** * a custom filter rules */ private class MyFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults filterResults = new FilterResults(); Uri uri = ContactsContract.Contacts.CONTENT_URI; if (!TextUtils.isEmpty(constraint)) { uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI , Uri.encode(constraint.toString())); } //TODO:this should not be run in the UI thread Cursor cursor = mContext.getContentResolver().query(uri, PROJECTION, null, null, null); if (cursor != null) { filterResults.values = cursor; filterResults.count = cursor.getCount(); } else { filterResults.values = null; filterResults.count = 0; } return filterResults; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { if (results.count > 0) { swapCursor((Cursor) results.values); } else { notifyDataSetInvalidated(); } } } }
其中performFiltering
这里你可以根据过滤的字串constraint(由ListView的setFilterText(String filterText)
传入)来写入你自己的过滤规则;publishResults则接受performFiltering回传的结果,你需要根据其结果来决定是否更新ListView的数据。
*4. 根据外部输入数据的变化来设置ListView的关键字过滤或者清除其过滤(ListView的setFilterText(String filterText)
和clearTextFilter()
)。
ListView搜索过滤功能的实现分析
上述讲解了ListView过滤搜索功能的基本用法,文章后半部分会带领大家走进ListView搜索过滤功能的实现过程。
*1. ListView使用搜索过滤功能时,首先必须setTextFilterEnabled(true)打开过滤功能。
public void setTextFilterEnabled(boolean textFilterEnabled) { mTextFilterEnabled = textFilterEnabled; }
我们在ListView的父类AbsListView可以看到上述代码,mTextFilterEnabled是个标记位,用来标识过滤功能的标识,默认为false;接下来我们来分析ListView在使用过程中设置关键字搜索过滤的setFilterText(String filterText)方法。
*2. 为啥Adapter要实现Filterable接口的原因
/** * Sets the initial value for the text filter. * @param filterText The text to use for the filter. * * @see #setTextFilterEnabled */ public void setFilterText(String filterText) { // TODO: Should we check for acceptFilter()? if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) { createTextFilter(false); // This is going to call our listener onTextChanged, but we might not // be ready to bring up a window yet mTextFilter.setText(filterText); mTextFilter.setSelection(filterText.length()); if (mAdapter instanceof Filterable) { // if mPopup is non-null, then onTextChanged will do the filtering if (mPopup == null) { Filter f = ((Filterable) mAdapter).getFilter(); f.filter(filterText); } // Set filtered to true so we will display the filter window when our main // window is ready mFiltered = true; mDataSetObserver.clearSavedState(); } } }
首先检查过滤标识mTextFilterEnabled和过滤关键字字是否为空,设置其过滤关键字;接下来重点的一句if (mAdapter instanceof Filterable)
来判断Adapter是否属于Filterable接口类型,因此到此我们就知道了为啥我们的Adapter为什么要实现Filterable接口了,因此不实现,接下来的那些搜索过滤方法压根不会执行。接下来,我们以CursorAdapter适配器来分析过滤功能在Adapter中的实现过程,至于为啥用这个适配器分析,因为它本身自带实现了Filterable接口,分析起来方便。
*3. Filterable接口涉及的设计模式
查看源码可知Filterable是一个接口,提供了一个getFilter的方法
/** *
Defines a filterable behavior. A filterable class can have its data * constrained by a filter. Filterable classes are usually * {@link android.widget.Adapter} implementations.
* * @see android.widget.Filter */public interface Filterable { /** *Returns a filter that can be used to constrain data with a filtering * pattern.
* *This method is usually implemented by {@link android.widget.Adapter} * classes.
* * @return a filter used to constrain data */ Filter getFilter();}
这里采用了一种很经典的装饰者模式,为啥会这么设计, 我的猜想是ListView设计之初,压根就没有考虑到搜索过滤这个功能,为了赋予这种功能,只能动态的让子类去实现,当然这样设计也有一个很大的好处把易于变化的部分分离了出去,让各个客户自己去实现,仔细想想过滤搜索功能中易于变化的部分不就是搜索规则的不同吗?
*4. Adapter的Filter相关
在CursorAdapter的中,可以看见其实现了Filterable接口,重写了getFile()方法
public Filter getFilter() { if (mCursorFilter == null) { mCursorFilter = new CursorFilter(this); } return mCursorFilter; }
上述代码返回的是CursorAdapter自定义的Filter,我们先不去理会它;我们来先查看下Filter是什么东西。
Fileter是一个抽象类,其中有着几个重要的方法和一些内部类和接口,其中重要的方法如下
public final void filter(CharSequence constraint) public final void filter(CharSequence constraint, FilterListener listener)protected abstract FilterResults performFiltering(CharSequence constraint);protected abstract void publishResults(CharSequence constraint, FilterResults results);
看到filter(CharSequence constraint)方法是否有一种熟悉的感觉,没错,因为我们找到入口了,当我们在ListView设置过滤关键字的setFilterText方法中
Filter f = ((Filterable) mAdapter).getFilter(); f.filter(filterText);
取得Filter对象,调用其filter方法
*5 Filter的实现过程
我们从filter的入口来开始分析其实现,filter(filterText)的内部调用了另一个重载方法filter(CharSequence constraint, FilterListener listener),我们来查看其源码
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); } }
由源码可知,这个方法是异步运行的,首先开启一个后台线程运行的mThreadHandler(HandlerThread的形式)对象,删除原来的消息,构造一个新的Message对象往Handler消息队列发送(消息对象用RequestArguments类封装了,RequestArguments封装了过滤字串和监听器对象)。我们查看RequestHandler类里的handleMessage(Message msg)回调函数(因为最终都会回调这个函数,对Handler不熟悉的童鞋可以参考博主的剖析Android-Handler机制流程 相关的文章)
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; }
我们查看FILTER_TOKEN的Message消息,这个代码中主要做了三件事:
* 取RequestArguments对象的过滤字串调用performFiltering(args.constraint)
函数,这个函数会回调Filter子类的performFiltering方法,performFiltering返回FilterResults结果;
* 构造一个新的Message对象,其中有个地方要注意的是RequestArguments的results存储着performFiltering返回的FilterResults(FilterResults是Filter的内部类,保存着过滤搜索后的结果和其数量)对象(异常则为一个新的FilterResults空对象),通过mResultHandler对象发送至消息队列中。
* 3s后发送一个FINISH_TOKEN消息对象,用来安全退出Looper
上述分析可以知道,当我们重写performFiltering过滤规则方法时,需要构建一个FilterResults对象用来存放搜索后的结果和其数量返回;最终的结果会通过mResultHandler对象发送到消息队列中;接下来分析mResultHandler对象
/** *
Creates a new asynchronous filter.
*/ public Filter() { mResultHandler = new ResultsHandler(); }
这个对象构建是在其构造函数中进行初始化化,也就是说当我们在Adapter实现Filterable接口的getFilter()方法中,构建自己的Filter时会进行构建,也就是说这个Handler运行在UI线程中;我们接着查看ResultsHandler对象的实现
private class ResultsHandler extends Handler { @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); } } }
这个Handler对象的handleMessage回调函数做的工作很简单,首先获取消息中的obj也就是RequestArguments,然后将其中的过滤字串和过滤搜索后的结果通过publishResults(args.constraint, args.results);函数回调出去;如果有监听器对象则回调监听函数。因此我们知道了为什么我们需要在publishResults中对搜索的results结果进行判断来更新ListView数据了(因为有可能在执行performFiltering时异常返回一个FilterResults空对象)。
至此ListView的过滤搜索功能的实现过程已经分析完毕了,希望大家看完之后,在之后的使用过程中能够做到心中有数,当然这个ListView搜索过滤的项目源码,我已经上传到Github上了AutomaticQueryContacts,大家可以下载下来查看。
- ListView过滤搜索功能源码分析
- Android ListView/GridView 搜索过滤功能
- Android ListView的字母排序和过滤搜索功能
- Android自定义搜索框(EditText)的搜索功能实现,过滤ListView
- android 搜索框过滤 listview
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Mysql字符串截取函数SUBSTRING的用法说明
- [Azure] Possible reasons for publish failed
- mysql中limit慎用
- React-Native_01:开山篇
- JS作用域函数闭包
- ListView过滤搜索功能源码分析
- getPath(),getAbsolutePath(),getCanonicalPath()区别
- iOS开发UI篇—Quartz2D简单使用(二)
- java基础学习——java程序的运行机制详细分析
- LIGHT OJ-1305 - Area of a Parallelogram 【向量叉积】
- java转android阶段的牢骚
- 移动App开发人员应该关注的7件事
- jquery在文档加载之后运行函数
- 解密SparkStreaming运行机制和架构进阶之Job和容错