ListView过滤搜索功能源码分析

来源:互联网 发布:黄伯云学术造假 知乎 编辑:程序博客网 时间:2024/06/06 12:49

近期维护联系人(Contacts)代码时,发现其中有个功能是根据你输入的字串来搜索出ListView相匹配数据的一项功能。鉴于网上大部分内容都是写其一些基本用法,本文分析ListView其搜索过滤功能的实现,让大家在使用过程中能够做到心里有底,正确的使用好这项功能。

本文章分为两个主题

  1. ListView中怎么使用搜索过滤功能
  2. 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,大家可以下载下来查看。

0 0