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

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

组件准备

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

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

开始布局

现在先进行布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

<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:

1
android:iconifiedByDefault="false"

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

1
android:queryHint="@string/search_hint"

这个是搜索框的提示内容

1
android:textColor="@color/white"

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

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

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

1
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195

/**
* 创建人:贾真
* 创建时间: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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

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

热评文章