SearchView配合RecyclerView实现分页搜索过滤关键字

在日常开发中,我们会遇到一种需求,就是通过输入关键字快速的查询当前列表中的数据并进行过滤显示。(感觉好难用文字描述这个功能啊·····),在网上找了一些资料也没有类似的文章,只好自己变尝试变查资料,用了几个小时的时间终于搞出来了。
OK,看图:

1.gif

组件准备

1.SearchView:SearchView是Android原生的搜索框控件,它提供了一个用户界面,用于用户搜索查询。
SearchView默认是展示一个search的icon,点击icon展开搜索框,如果你想让搜索框默认就展开,可以通过setIconifiedByDefault(false);实现。

2.XRecyclerView: github地址
这个组件主要是对RecyclerView的封装,主要完成了下拉刷新、上拉加载更多、RecyclerView头部。
关于这个组件的详解,请参考这篇文章:文章地址

开始布局

现在先进行布局:


<android.support.design.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/window_background"
    android:fitsSystemWindows="true"
    android:focusable="true"
    android:focusableInTouchMode="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/app_main_color"
            android:minHeight="?attr/actionBarSize"
            app:layout_scrollFlags="scroll"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:theme="@style/ThemeOverlay.AppCompat.ActionBar"
            app:title="@string/location_detail"
            app:titleTextColor="@color/white" />

        <SearchView
            android:id="@+id/searchView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/spacing_small"
            android:iconifiedByDefault="false"
            android:queryHint="@string/search_hint"
            android:textColor="@color/white"
           />

    </android.support.design.widget.AppBarLayout>


    <com.jcodecraeer.xrecyclerview.XRecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:animateLayoutChanges="false"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <TextView
        android:id="@+id/tv_emptyView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center"
        android:background="#dcdcdc"
        android:text="无数据" />

</android.support.design.widget.CoordinatorLayout>

这个布局是我项目中的布局方式,所以你不必完全按照这个写法来,主要关注SearchView和XRecyclerView的写法就可以了,OK,我们先看SearchView:

android:iconifiedByDefault="false"

由于SearchView默认是一个搜索放大镜的图标,在点击后才会展开输入框输入搜索关键字,但是在我的项目中是需要搜索框默认就展开的,所以需要设置这个属性,当属性设置为true时,搜索框不展开,当属性设置为false时,搜索框展开。

 android:queryHint="@string/search_hint"

这个是搜索框的提示内容

android:textColor="@color/white"

这个是搜索框的输入字体颜色

SearchVieW基本上就是这些设置了,当然如果你打算只让搜索框内输入数字,可以利用SearcheView的android:inputType属性来设置。

再来看看XRecyclerView:
这个组件基本上不需要什么特殊的设置,只有一个属性

app:layout_behavior="@string/appbar_scrolling_view_behavior"

这个是配合AppBarLayout和Toolbar使用的滚动监听,当向上滚动时可以动态的隐藏Toolbar,不过这里和本文没有什么关系,只是顺带一提。

逻辑分析

在开发之前,我习惯先进行一些思考,把要实现的功能在脑子里实现一遍,主要考虑的不是代码细节而是逻辑判断多一些,对于这个功能,主要考虑几点:
1.SearchView是否有一个监听,可以监听我的输入内容。
2.下拉刷新的时候,应当把SearchView中的输入内容全部清空,并且让SearchView不再获取焦点,从而关闭输入法,增加用户体验。
3.应当实时判断SearchView中的数据,如果SearchView中含有数据的话,则关闭XRecyclerView的加载更多方法, 不允许用户在这个界面下加载更多,直到SearchView中输入的数据为空,则再次开启下拉加载更多的功能。
4.要在Acitivity中写一个过滤方法,目的是为了得到符合条件的数据源(ArrayList)
5.Adapter中要一个设置过滤的方法,目的是为了将过滤后的数据传入Adapter并刷新数据。
6.Adapter中还要设置一个关闭过滤的方法, 目的是在上拉加载更多的时候将当前数据源传递给Adapter并刷新数据。(这一步开始并没有考虑到,直到发现每次搜索完毕后,再上拉加载时明明能获取数据可是再界面中却无法显示数据时才想到的。)

代码实现

在代码中已经将之前的逻辑分析都注释出来了。

Activity:


/**
 * 创建人:贾真
 * 创建时间:2016/9/9 10:01
 * 修改备注:
 */
public class CarLocationListActivity extends OBaseActivity {
    private static final String TAG = CarLocationListActivity.class.getSimpleName();
    @BindView(R.id.toolbar)
    Toolbar toolbar;
    @BindView(R.id.recyclerview)
    XRecyclerView mRecyclerView;
    @BindView(R.id.searchView)
    SearchView searchView;
    @BindView(R.id.tv_emptyView)
    TextView tvEmptyView;
    private CarLocationListAdapter mAdapter;
    //数据存储列表
    private ArrayList<CarLocationListModel> listData;
    //当前页数
    int pageNo = 1;
    //每页显示数
    int pageSize = 15;

    @Override
    protected void init() {
        setContentView(R.layout.activity_locationcar_list);
        ButterKnife.bind(this);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.setRefreshProgressStyle(ProgressStyle.BallSpinFadeLoader);
        mRecyclerView.setLoadingMoreProgressStyle(ProgressStyle.BallRotate);
        mRecyclerView.setArrowImageView(R.drawable.iconfont_downgrey);
        mRecyclerView.setLoadingMoreEnabled(true);
        mRecyclerView.setEmptyView(tvEmptyView);


    }

    private void GetPageListData(int pageNo, int pageSize) {

        appAction.GetCarLocationList(PublicDefault.USERID, pageNo, pageSize, new onNetWorkListener<ArrayList<CarLocationListModel>>() {

            @Override
            public void onSuccess(ArrayList<CarLocationListModel> model) {

                if (mAdapter == null) {
                    listData = model;
                    mAdapter = new CarLocationListAdapter(listData);
                    mRecyclerView.setAdapter(mAdapter);
                    mRecyclerView.refreshComplete();
                } else {
                    //将获取的元素全部加入到列表的尾部
                    listData.addAll(model);
                    mAdapter.closeFilter(listData);
                    mRecyclerView.loadMoreComplete();
                }
            }

            @Override
            public void onFailure(int errorEvent, String message) {
                ToastUtil.ErrorImageToast(CarLocationListActivity.this, getResources().getString(R.string.get_location_list_fail));
                if (mAdapter == null) {

                    mRecyclerView.refreshComplete();
                } else {

                    mRecyclerView.loadMoreComplete();
                }


            }
        });
    }

    @Override
    protected void setListeners() {
        /**
         * 列表下拉刷新和上拉加载的监听方法
         * 下拉刷新时要将页数重新设置为1 并且将数据清空 还要将适配器清理掉 并且要将搜索文字清理掉
         *
         */
        mRecyclerView.setLoadingListener(new XRecyclerView.LoadingListener() {
            @Override
            public void onRefresh() {
                //逻辑2:下拉刷新时,将SearchView中的数据清空,并让SearchView失去焦点。
                searchView.setQuery("", false);
                searchView.clearFocus();

                pageNo = 1;
                if (listData != null)
                    listData.clear();
                mAdapter = null;
                GetPageListData(pageNo, pageSize);
            }

            @Override
            public void onLoadMore() {
                pageNo++;
                GetPageListData(pageNo, pageSize);

            }
        });
        mRecyclerView.setRefreshing(true);

        //逻辑1:SearchView的监听输入内容事件:监听查询内容,onQueryTextSubmit是提交监听,不符合我的需求 onQueryTextChange是实时监听,符合我们的需求
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            // 当点击搜索按钮时触发该方法
            @Override
            public boolean onQueryTextSubmit(String s) {
                return false;
            }

            // 当搜索内容改变时触发该方法
            @Override
            public boolean onQueryTextChange(String searchText) {
                //逻辑3:当搜索框中存在搜索数据则关闭加载更多
                if (!"".equals(searchView.getQuery().toString().trim())) {
                    L.d(TAG, "关闭加载更多");
                    mRecyclerView.setLoadingMoreEnabled(false);
                } else {
                    L.d(TAG, "开启加载更多");
                    mRecyclerView.setLoadingMoreEnabled(true);
                }
                final ArrayList<CarLocationListModel> filteredModelList = filter(listData, searchText);
                mAdapter.setFilter(filteredModelList);
                return true;
            }
        });

        searchView.setOnCloseListener(new SearchView.OnCloseListener() {
            @Override
            public boolean onClose() {
                L.d(TAG, "onClose");
                searchView.setQuery("", false);
                searchView.clearFocus();
                return true;
            }
        });
    }

    @Override
    protected void stop() {
        mRecyclerView = null;
        toolbar = null;
        mAdapter = null;
        if (listData != null) {
            listData.clear();
            listData = null;
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                onBackPressed();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     *
     * 逻辑4:过滤方法,目的是过滤符合当前数据中符合条件的数据源
     * @param models
     * @param query
     * @return
     */
    private ArrayList<CarLocationListModel> filter(ArrayList<CarLocationListModel> models, String query) {

        query = query.toLowerCase();

        final ArrayList<CarLocationListModel> filteredModelList = new ArrayList<>();

        for (CarLocationListModel model : models) {

            final String text = model.getChePaiHao().toLowerCase();

            if (text.contains(query)) {

                filteredModelList.add(model);

            }

        }

        return filteredModelList;

    }

}


Adapter:


public class CarLocationListAdapter extends RecyclerView.Adapter<LocationListViewHolder> {
    public ArrayList<CarLocationListModel> datas = null;

    public CarLocationListAdapter(ArrayList<CarLocationListModel> datas) {
        this.datas = datas;
    }

    //创建新View,被LayoutManager所调用
    @Override
    public LocationListViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.adapter_locationcar_list, viewGroup, false);
        return new LocationListViewHolder(view);
    }

    @Override
    public void onBindViewHolder(LocationListViewHolder viewHolder, int position) {
        viewHolder.tvCarNo.setText(String.valueOf(datas.get(position).getChePaiHao()));
        viewHolder.tvSpeed.setText(datas.get(position).getCheSu()+"km/h");
        viewHolder.tvCarAddress.setText("位置:"+ TextUtil.checkText(datas.get(position).getDangQianWeiZhi()));
        viewHolder.tvJrlc.setText("今日里程:"+datas.get(position).getJinRiLiCheng()+"Km");
        viewHolder.tvJryh.setText("今日油耗:"+datas.get(position).getJinRiYouHao()+"L");
    }

    //获取数据的数量
    @Override
    public int getItemCount() {
        return datas.size();
    }

    /**
     * 逻辑5:在Adapter中设置一个过滤方法,目的是为了将过滤后的数据传入Adapter中并刷新数据
     * @param locationListModels
     */
    public void setFilter(ArrayList<CarLocationListModel> locationListModels ) {

        datas = new ArrayList<>();

        datas .addAll( locationListModels );

        notifyDataSetChanged();

    }

    /**
     *逻辑6:
     * 设置一个关闭过滤的方法, 目的是在上拉加载更多的时候将真实数据源传递给Adapter并刷新数据
     * @param allList
     */
    public void closeFilter(ArrayList<CarLocationListModel> allList){

        datas=allList;
        notifyDataSetChanged();
    }

}

这里就只贴出来Activity和Adapter和主Activity的布局文件,其他的ViewHolder和Adapter的布局文件,大家在实现的时候可以写简单一点的,我这里就不贴出来了。
只要是跟着这个教程走的话,基本上是不会有什么太大的问题的,剩下的就是按部就班的进行开发就可以了。

总结

其实这个效果实现起来并不难,关键是要分析清楚各种逻辑关系,如果记性好可以用脑子理清这些逻辑关系,如果记性没有那么好,就自己写出这些逻辑关系吧,毕竟好记性不如烂笔头嘛,好了 抛砖至此,多谢您看完。

引用

1.Android 搜索框:SearchView 的属性和用法详解
2.Android Filter RecyclerView Using SearchView In ToolBar

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容