在日常開發(fā)中栋荸,我們會(huì)遇到一種需求豪娜,就是通過(guò)輸入關(guān)鍵字快速的查詢當(dāng)前列表中的數(shù)據(jù)并進(jìn)行過(guò)濾顯示吁津。(感覺好難用文字描述這個(gè)功能啊·····)棚蓄,在網(wǎng)上找了一些資料也沒(méi)有類似的文章,只好自己變嘗試變查資料碍脏,用了幾個(gè)小時(shí)的時(shí)間終于搞出來(lái)了梭依。
OK,看圖:
組件準(zhǔn)備
1.SearchView:SearchView是Android原生的搜索框控件,它提供了一個(gè)用戶界面典尾,用于用戶搜索查詢役拴。
SearchView默認(rèn)是展示一個(gè)search的icon,點(diǎn)擊icon展開搜索框钾埂,如果你想讓搜索框默認(rèn)就展開河闰,可以通過(guò)setIconifiedByDefault(false);實(shí)現(xiàn)。
2.XRecyclerView: github地址
這個(gè)組件主要是對(duì)RecyclerView的封裝褥紫,主要完成了下拉刷新姜性、上拉加載更多、RecyclerView頭部故源。
關(guān)于這個(gè)組件的詳解污抬,請(qǐng)參考這篇文章:文章地址
開始布局
現(xiàn)在先進(jìn)行布局:
<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="無(wú)數(shù)據(jù)" />
</android.support.design.widget.CoordinatorLayout>
這個(gè)布局是我項(xiàng)目中的布局方式,所以你不必完全按照這個(gè)寫法來(lái)绳军,主要關(guān)注SearchView和XRecyclerView的寫法就可以了印机,OK,我們先看SearchView:
android:iconifiedByDefault="false"
由于SearchView默認(rèn)是一個(gè)搜索放大鏡的圖標(biāo)门驾,在點(diǎn)擊后才會(huì)展開輸入框輸入搜索關(guān)鍵字射赛,但是在我的項(xiàng)目中是需要搜索框默認(rèn)就展開的,所以需要設(shè)置這個(gè)屬性奶是,當(dāng)屬性設(shè)置為true時(shí)楣责,搜索框不展開,當(dāng)屬性設(shè)置為false時(shí)聂沙,搜索框展開秆麸。
android:queryHint="@string/search_hint"
這個(gè)是搜索框的提示內(nèi)容
android:textColor="@color/white"
這個(gè)是搜索框的輸入字體顏色
SearchVieW基本上就是這些設(shè)置了,當(dāng)然如果你打算只讓搜索框內(nèi)輸入數(shù)字及汉,可以利用SearcheView的android:inputType屬性來(lái)設(shè)置沮趣。
再來(lái)看看XRecyclerView:
這個(gè)組件基本上不需要什么特殊的設(shè)置,只有一個(gè)屬性
app:layout_behavior="@string/appbar_scrolling_view_behavior"
這個(gè)是配合AppBarLayout和Toolbar使用的滾動(dòng)監(jiān)聽坷随,當(dāng)向上滾動(dòng)時(shí)可以動(dòng)態(tài)的隱藏Toolbar,不過(guò)這里和本文沒(méi)有什么關(guān)系房铭,只是順帶一提驻龟。
邏輯分析
在開發(fā)之前,我習(xí)慣先進(jìn)行一些思考缸匪,把要實(shí)現(xiàn)的功能在腦子里實(shí)現(xiàn)一遍翁狐,主要考慮的不是代碼細(xì)節(jié)而是邏輯判斷多一些,對(duì)于這個(gè)功能凌蔬,主要考慮幾點(diǎn):
1.SearchView是否有一個(gè)監(jiān)聽露懒,可以監(jiān)聽我的輸入內(nèi)容。
2.下拉刷新的時(shí)候砂心,應(yīng)當(dāng)把SearchView中的輸入內(nèi)容全部清空隐锭,并且讓SearchView不再獲取焦點(diǎn),從而關(guān)閉輸入法计贰,增加用戶體驗(yàn)。
3.應(yīng)當(dāng)實(shí)時(shí)判斷SearchView中的數(shù)據(jù)蒂窒,如果SearchView中含有數(shù)據(jù)的話躁倒,則關(guān)閉XRecyclerView的加載更多方法, 不允許用戶在這個(gè)界面下加載更多洒琢,直到SearchView中輸入的數(shù)據(jù)為空秧秉,則再次開啟下拉加載更多的功能。
4.要在Acitivity中寫一個(gè)過(guò)濾方法衰抑,目的是為了得到符合條件的數(shù)據(jù)源(ArrayList)
5.Adapter中要一個(gè)設(shè)置過(guò)濾的方法象迎,目的是為了將過(guò)濾后的數(shù)據(jù)傳入Adapter并刷新數(shù)據(jù)。
6.Adapter中還要設(shè)置一個(gè)關(guān)閉過(guò)濾的方法呛踊, 目的是在上拉加載更多的時(shí)候?qū)?dāng)前數(shù)據(jù)源傳遞給Adapter并刷新數(shù)據(jù)砾淌。(這一步開始并沒(méi)有考慮到,直到發(fā)現(xiàn)每次搜索完畢后谭网,再上拉加載時(shí)明明能獲取數(shù)據(jù)可是再界面中卻無(wú)法顯示數(shù)據(jù)時(shí)才想到的汪厨。)
代碼實(shí)現(xiàn)
在代碼中已經(jīng)將之前的邏輯分析都注釋出來(lái)了。
Activity:
/**
* 創(chuàng)建人:賈真
* 創(chuàng)建時(shí)間: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;
//數(shù)據(jù)存儲(chǔ)列表
private ArrayList<CarLocationListModel> listData;
//當(dāng)前頁(yè)數(shù)
int pageNo = 1;
//每頁(yè)顯示數(shù)
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() {
/**
* 列表下拉刷新和上拉加載的監(jiān)聽方法
* 下拉刷新時(shí)要將頁(yè)數(shù)重新設(shè)置為1 并且將數(shù)據(jù)清空 還要將適配器清理掉 并且要將搜索文字清理掉
*
*/
mRecyclerView.setLoadingListener(new XRecyclerView.LoadingListener() {
@Override
public void onRefresh() {
//邏輯2:下拉刷新時(shí)愉择,將SearchView中的數(shù)據(jù)清空劫乱,并讓SearchView失去焦點(diǎn)。
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的監(jiān)聽輸入內(nèi)容事件:監(jiān)聽查詢內(nèi)容锥涕,onQueryTextSubmit是提交監(jiān)聽衷戈,不符合我的需求 onQueryTextChange是實(shí)時(shí)監(jiān)聽,符合我們的需求
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
// 當(dāng)點(diǎn)擊搜索按鈕時(shí)觸發(fā)該方法
@Override
public boolean onQueryTextSubmit(String s) {
return false;
}
// 當(dāng)搜索內(nèi)容改變時(shí)觸發(fā)該方法
@Override
public boolean onQueryTextChange(String searchText) {
//邏輯3:當(dāng)搜索框中存在搜索數(shù)據(jù)則關(guān)閉加載更多
if (!"".equals(searchView.getQuery().toString().trim())) {
L.d(TAG, "關(guān)閉加載更多");
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:過(guò)濾方法层坠,目的是過(guò)濾符合當(dāng)前數(shù)據(jù)中符合條件的數(shù)據(jù)源
* @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;
}
//創(chuàng)建新View殖妇,被LayoutManager所調(diào)用
@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");
}
//獲取數(shù)據(jù)的數(shù)量
@Override
public int getItemCount() {
return datas.size();
}
/**
* 邏輯5:在Adapter中設(shè)置一個(gè)過(guò)濾方法,目的是為了將過(guò)濾后的數(shù)據(jù)傳入Adapter中并刷新數(shù)據(jù)
* @param locationListModels
*/
public void setFilter(ArrayList<CarLocationListModel> locationListModels ) {
datas = new ArrayList<>();
datas .addAll( locationListModels );
notifyDataSetChanged();
}
/**
*邏輯6:
* 設(shè)置一個(gè)關(guān)閉過(guò)濾的方法窿春, 目的是在上拉加載更多的時(shí)候?qū)⒄鎸?shí)數(shù)據(jù)源傳遞給Adapter并刷新數(shù)據(jù)
* @param allList
*/
public void closeFilter(ArrayList<CarLocationListModel> allList){
datas=allList;
notifyDataSetChanged();
}
}
這里就只貼出來(lái)Activity和Adapter和主Activity的布局文件拉一,其他的ViewHolder和Adapter的布局文件采盒,大家在實(shí)現(xiàn)的時(shí)候可以寫簡(jiǎn)單一點(diǎn)的,我這里就不貼出來(lái)了蔚润。
只要是跟著這個(gè)教程走的話磅氨,基本上是不會(huì)有什么太大的問(wèn)題的,剩下的就是按部就班的進(jìn)行開發(fā)就可以了嫡纠。
總結(jié)
其實(shí)這個(gè)效果實(shí)現(xiàn)起來(lái)并不難烦租,關(guān)鍵是要分析清楚各種邏輯關(guān)系,如果記性好可以用腦子理清這些邏輯關(guān)系除盏,如果記性沒(méi)有那么好叉橱,就自己寫出這些邏輯關(guān)系吧,畢竟好記性不如爛筆頭嘛者蠕,好了 拋磚至此窃祝,多謝您看完。
引用
1.Android 搜索框:SearchView 的屬性和用法詳解
2.Android Filter RecyclerView Using SearchView In ToolBar