在ListView中实现下拉刷新上拉加载更多是很方便的,在RecyclerView中要实现这个功能,需要一些新的思路,当然习惯后也是很简便的。下面开始这个过程的总结
思路
在RecyclerView中,默认没有HeaderView和FooterView,所以只能开发者自己来实现这两个View,通常的做法是:使用官方的刷新控件SwipeRefreshLayout来实现下拉刷新,然后自定义一个FooterView来实现滚动到底部的加载视图
步骤
导包
SwipeRefreshLayout和RecyclerView都是官方支持包中的控件,所以需要进行导包,在AndroidStudio中,如下:
compile 'com.android.support:appcompat-v7:23.1.0'
compile 'com.android.support:recyclerview-v7:23.1.0'
界面
界面很简单,一个SwipeRefreshLayout中包含一个RecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/refresh_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
控件初始化
控件初始化,需要完成控件的下拉监听和上拉监听,其中,下拉监听通过SwipRefreshLayout的setOnRefreshListener()方法监听,而上拉刷新,需要通过监听列表的滚动,当列表滚动到底部时触发事件,具体代码如下:
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
private SwipeRefreshLayout refreshLayout;
private RecyclerView recyclerView;
private LinearLayoutManager layoutManager;
private RecyclerAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_layout);
recyclerView = (RecyclerView) findViewById(R.id.recycler_list);
layoutManager = new LinearLayoutManager(this);
refreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary);//设置刷新时进度条颜色,最多四种
refreshLayout.setOnRefreshListener(this);
mAdapter = new RecyclerAdapter();//自定义的适配器
recyclerView.setAdapter(mAdapter);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addOnScrollListener(new OnRecyclerScrollListener());
}
/**
* 用于下拉刷新
*/
@Override
public void onRefresh() {
}
/**
* 用于上拉加载更多
*/
public class OnRecyclerScrollListener extends RecyclerView.OnScrollListener {
int lastVisibleItem = 0;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (mAdapter != null && newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == mAdapter.getItemCount()) {
//滚动到底部了,可以进行数据加载等操作
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = layoutManager.findLastVisibleItemPosition();
}
}
}
自定义适配器
使用RecyclerView和ListView一样,需要一个适配器来适配数据和视图,继承自RecyclerView.Adapter,需要重写几个方法:
- onCreateViewHolder(ViewGroup parent,int viewType):创建ViewHolder时调用,其中第二个参数int viewType可以用来作为判断当前需要创建的View的类型,从而可以判断是加载内容的视图还是加载footer的视图。
- onBindViewHolder(ViewHolder holder,int position):绑定数据到视图中,可以通过position的到数据集中的数据,然后将其填充到视图中
- getItemCount():返回数据集的大小,通常是返回list.size()+1,因为多了一个footer
- getItemViewType():返回当前要显示的视图的类型,会在onCreateViewHolder方法之前调用,返回的viewType会作为onCreateViewHolder的参数,以此来判断要创建的视图类型
通过重写上述四个方法,还需要 两个继承自ViewHolder的静态类来实现content和footer的ViewHolder,就能大致完成一个可以自定义的Adaptr了。
footer的布局
内容界面布局就应该更具实际情况编写,就不贴出代码了,主要看一下footer的布局,因为要实现上拉加载更多,就应该在下拉到底部的时候有一个提示“上拉加载更多”,在加载的过程中应该有一个进度条,所以布局如下:
item_list_footer.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_item_footer_load_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="center"
android:text="上拉加载更多"
android:visibility="gone"/>
<ProgressBar
android:id="@+id/pb_item_footer_loading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"/>
</RelativeLayout>
完整代码
Adapter的代码如下:
package com.example.tancen.pullrefreshrecyclerviewdemo;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.ArrayList;
/**
* Created by classTC on 11/30/2015.Recycler的适配器
*/
public class RecyclerAdapter extends RecyclerView.Adapter<ViewHolder> {
private static final int TYPE_CONTENT = 0;
private static final int TYPE_FOOTER = 1;
private ArrayList<DataBean> dataList;
private ProgressBar pbLoading;
private TextView tvLoadMore;
public RecyclerAdapter() {
dataList = new ArrayList<>();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_CONTENT) {
return new ContentViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list_content, parent, false));
} else if (viewType == TYPE_FOOTER) {
return new FooterViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list_footer, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
int type = getItemViewType(position);
if (type == TYPE_CONTENT) {
DataBean bean = dataList.get(position);
((ContentViewHolder) holder).tvId.setText("" + bean.getId());
((ContentViewHolder) holder).tvName.setText(bean.getName());
} else if (type == TYPE_FOOTER) {
pbLoading = ((FooterViewHolder) holder).pbLoading;
tvLoadMore = ((FooterViewHolder) holder).tvLoadMore;
}
}
/**
* 获取数据集加上一个footer的数量
*/
@Override
public int getItemCount() {
return dataList.size() + 1;
}
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return TYPE_CONTENT;
}
}
/**
* 获取数据集的大小
*/
public int getListSize() {
return dataList.size();
}
/**
* 将新增数据集添加到尾部,一般用作加载更多F
*/
public void addAll(ArrayList<DataBean> list) {
dataList.addAll(list);
notifyDataSetChanged();
}
/**
* 将新增数据集添加到前面,一般用作刷新
*/
public void addFirstAll(ArrayList<DataBean> list) {
dataList.addAll(0, list);
notifyDataSetChanged();
}
/**
* 清除所有数据
*/
public void clear() {
dataList.clear();
notifyDataSetChanged();
}
/**
* 内容的ViewHolder
*/
public static class ContentViewHolder extends ViewHolder {
private TextView tvId, tvName;
public ContentViewHolder(View itemView) {
super(itemView);
tvId = (TextView) itemView.findViewById(R.id.tv_item_id);
tvName = (TextView) itemView.findViewById(R.id.tv_item_name);
}
}
/**
* footer的ViewHolder
*/
public static class FooterViewHolder extends ViewHolder {
private TextView tvLoadMore;
private ProgressBar pbLoading;
public FooterViewHolder(View itemView) {
super(itemView);
tvLoadMore = (TextView) itemView.findViewById(R.id.tv_item_footer_load_more);
pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_item_footer_loading);
}
}
/**
* 显示正在加载的进度条
*/
public void showLoading() {
if (pbLoading != null && tvLoadMore != null) {
pbLoading.setVisibility(View.VISIBLE);
tvLoadMore.setVisibility(View.GONE);
}
}
/**
* 显示上拉加载的文字
*/
public void showLoadMore() {
if (pbLoading != null && tvLoadMore != null) {
pbLoading.setVisibility(View.GONE);
tvLoadMore.setVisibility(View.VISIBLE);
}
}
}
有了自定义的Adapter,只需要调用Adapter中的addAll(),addFirstAll()方法就能实现在列表顶部加载最新的数据和在列表尾部加载之前的数据了,demo中使用了一个数据bean:DataBean,用于封装数据,模拟数据获取的代码如下:
/**
* 模拟刷新请求
*/
private void requestRefreshData() {
refreshLayout.setRefreshing(true);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
ArrayList<DataBean> list = new ArrayList<>();
for (int i = (int) (5 + Math.random() * 10); i >=0; i--) {
DataBean bean = new DataBean();
bean.setId(i);
bean.setName("data_refresh_" + i);
list.add(bean);
}
mAdapter.clear();//有新的数据,需要先将之前的数据清除掉
mAdapter.addFirstAll(list);
refreshLayout.setRefreshing(false);
}
}, 3000);
}
/**
* 模拟加载更多请求
*/
private void requestMoreData() {
mAdapter.showLoading();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
ArrayList<DataBean> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
DataBean bean = new DataBean();
bean.setId(i);
bean.setName("data_load_more_" + i);
list.add(bean);
}
mAdapter.addAll(list);
mAdapter.showLoadMore();
}
}, 3000);
}
到此,一个简单的使用RecyclerView实现的下拉刷新上拉加载更多的demo就完成了,整体思路很简单,就是通过判断当前要显示的item的类型,然后更具类型加载不同的视图,如果列表滚动到底部了,则需要显示footer。Demo源码:PullRefreshRecyclerViewDemo
运行效果: