在以前用ListView的时候,有PullToRefresh这样强大的库,省了不少事。现在官方出了RecyclerView后,大有取代ListView的趋势,于是觉得也应该研究一下RecyclerView的下拉刷新和上拉加载。关于下拉刷新,个人感觉用SwipeRefreshLayout足够了,所以本文简单介绍一下使用RecyclerView扩展上拉加载的功能实现。
效果图:
1.思路
对于上拉刷新,其实就是当当前的数据集(可能是List)中的数据全部被展示完的时候,自动去加载剩余的部分。有了这个基础之后就容易多了,我们只需要在不断的滑动中判断当前显示的最后一个item是不是最后一个item(有点拗口?其实就是判断item有没有全部加载完!),如果是,则执行自动加载(其实还应该在判断是不是正在加载,这个后面说),如果不是,就不用管了。显而易见,这里面有两个比较关键的问题,第一,如何判断是不是最后一个item;第二,如何自动加载。先说第一个,对于一般的列表来说,我们在设置RecyclerView的LayoutManager时,一般传入的是LinearLayoutManager的实例,而LinearLayoutManager中有一个 int findLastVisibleItemPosition()的方法,用来返回最后一个可见item的position,我们可以利用这个position来跟item总数(getLayoutManager().getItemCount())作比较来判断;对于第二个,可以直接用个回调函数解决。好了,思路理完了之后就开始一步步实现了。
2.实现
重写RecyclerView的onScroll(int dx,int dy)方法。
@Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);
int lastVisibleItem = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
int totalItemCount = getLayoutManager().getItemCount();
if(totalItemCount - lastVisibleItem <= 1){
if(!isLoading){
isLoading = true;
loadMoreListener.onLoadMore();
}
}else {
isLoading = false;
}
// Log.i(TAG,"lastVisibleItem="+lastVisibleItem+",totalItemCount="+totalItemCount);
}
这里面有一个上面提到的问题,就是当item的位置满足自动加载的条件时,有可能此时已经在加载了,那我们就无需再去重复加载了,所以这里可以设置一个标识当前是否正在加载的flag即isLoading,只有当item的位置和isLoading这两个条件同时满足时才去执行自动加载。
这时候其实已经完成自动加载的功能了,如下图。
3.完善
在实际应用中我们其实也有自动加载失败的情况,因此我们还需要一个来提示加载结果的东西。这里可以通过为RecyclerView添加footerView来解决,当滑动到footerView的时候提示加载数据,如果加载成功,调用notifyDataSetChanged()方法来更新,如果加载失败,通过footerView来提示并且可以点击footerView重新尝试加载。而RecyclerView是可以支持不同类型的item,只需要在Adapter中的ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)中通过判断viewtType来实例化不同的ViewHolder就可以了。下面是PullToLoadMoreRecyclerView和CustomAdapter的完整代码。
PullToLoadMoreRecyclerView.java
package su.guang.pulltoloadmorerecyclerview.view;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import su.guang.pulltoloadmorerecyclerview.adapter.CustomAdapter;
/**
* Created by Jaren on 2016/8/17.
*/
public class PullToLoadMoreRecyclerView extends RecyclerView{
private final String TAG = "PullToLoadMoreRView";
private OnLoadMoreListener loadMoreListener;
private CustomAdapter mAdapter;
private boolean isLoading = false;
private String loadMoreText;
private String loadingMoreText;
private String loadMoreFailText;
public PullToLoadMoreRecyclerView(Context context) {
super(context);
}
public PullToLoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public PullToLoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);
int lastVisibleItem = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
int totalItemCount = getLayoutManager().getItemCount();
if(totalItemCount - lastVisibleItem <= 1){
if(!isLoading){
// 当条件同时满足时,才执行自动加载
isLoading = true; // 改变标志位
loadMoreListener.onLoadMore();
}
}else {
isLoading = false;
}
// Log.i(TAG,"lastVisibleItem="+lastVisibleItem+",totalItemCount="+totalItemCount);
}
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
if(adapter instanceof CustomAdapter){
mAdapter = (CustomAdapter) adapter;
}else {
throw new IllegalArgumentException("Only support the CustomAdapter");
}
}
public void setOnLoadMoreListener(OnLoadMoreListener loadMoreListener){
this.loadMoreListener = loadMoreListener;
}
public final void onLoadMoreFinish(boolean isSuccessed){
if(isSuccessed){
mAdapter.setLoadMoreText(loadMoreText);
mAdapter.notifyDataSetChanged();
mAdapter.setLoadMoreClickEnabled(false);
}else {
mAdapter.setLoadMoreText(loadMoreFailText);
mAdapter.setOnLoadMoreClickedListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadMoreListener.onLoadMore();
}
});
mAdapter.setLoadMoreClickEnabled(true);
}
}
public void onLoadingMore(){
mAdapter.setLoadMoreText(loadingMoreText);
}
public void setLoadMoreText(String loadMoreText) {
this.loadMoreText = loadMoreText;
}
public void setLoadingMoreText(String loadingMoreText) {
this.loadingMoreText = loadingMoreText;
}
public void setLoadMoreFailText(String loadMoreFailText) {
this.loadMoreFailText = loadMoreFailText;
}
public interface OnLoadMoreListener{
void onLoadMore();
}
}
CustomAdapter.java
package su.guang.pulltoloadmorerecyclerview.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
import su.guang.pulltoloadmorerecyclerview.R;
/**
* Created by Jaren on 2016/8/17.
*/
public class CustomAdapter extends RecyclerView.Adapter {
protected final int NORMAL_ITEM = 1;
protected final int FOOTER_ITEM = 2;
private List<?> data;
private Context context;
private View.OnClickListener clickListener;
private TextView tvLoadMore;
private boolean loadMoreClickEnabled = false;
public CustomAdapter(Context context, List<?> data) {
this.context = context;
this.data = data;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == FOOTER_ITEM){
View itemView = LayoutInflater.from(context).inflate(R.layout.item_footerview,null);
return new FooterViewHolder(itemView);
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(isFooterItem(position)){
FooterViewHolder footerViewHolder = (FooterViewHolder) holder;
tvLoadMore = footerViewHolder.tv;
tvLoadMore.setOnClickListener(loadMoreClickEnabled?clickListener:null);
}
}
@Override
public int getItemCount() {
return data.size()+1;
}
@Override
public int getItemViewType(int position) {
if(isFooterItem(position)){
return FOOTER_ITEM;
}else {
return NORMAL_ITEM;
}
}
public boolean isFooterItem(int position){
return position == getItemCount()-1;
}
public void setLoadMoreText(String text){
tvLoadMore.setText(text);
}
public final void setLoadMoreClickEnabled(boolean loadMoreClickEnabled) {
this.loadMoreClickEnabled = loadMoreClickEnabled;
tvLoadMore.setOnClickListener(loadMoreClickEnabled?clickListener:null);
}
public void setOnLoadMoreClickedListener(View.OnClickListener clickedListener){
this.clickListener = clickedListener;
}
class FooterViewHolder extends RecyclerView.ViewHolder{
private TextView tv;
public FooterViewHolder(View itemView) {
super(itemView);
tv= (TextView) itemView.findViewById(R.id.tv);
}
}
}
下面是关于如何使用的(套上了SwipeRefreshLayout)。
MainActivity.java
package su.guang.pulltoloadmorerecyclerview;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import su.guang.pulltoloadmorerecyclerview.adapter.MyAdapter;
import su.guang.pulltoloadmorerecyclerview.view.PullToLoadMoreRecyclerView;
public class MainActivity extends AppCompatActivity {
SwipeRefreshLayout swipeRefreshLayout;
PullToLoadMoreRecyclerView recyclerView;
MyAdapter mAdapter;
List<String> data = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swiperefreshlayout);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
new RefreshTask().execute(1);
}
});
recyclerView = (PullToLoadMoreRecyclerView) findViewById(R.id.recyclerview);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
for(int i = 0;i < 10;i++){
data.add(i+"");
}
recyclerView.setOnLoadMoreListener(new PullToLoadMoreRecyclerView.OnLoadMoreListener() {
@Override
public void onLoadMore() {
new LoadMoreTask().execute(1);
}
});
mAdapter = new MyAdapter(this,data);
recyclerView.setLoadMoreText("上拉加载更多");
recyclerView.setLoadingMoreText("正在加载...");
recyclerView.setLoadMoreFailText("加载失败,请点击重试");
recyclerView.setAdapter(mAdapter);
mAdapter.notifyDataSetChanged();
}
class LoadMoreTask extends AsyncTask{
@Override
protected void onPreExecute() {
super.onPreExecute();
recyclerView.onLoadingMore();
}
@Override
protected Object doInBackground(Object[] params) {
try {
// 模拟网络请求
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通过产生10内整数并对2取余来模拟加载是否成功
int random = (int)(Math.random()*10);
boolean b = random%2 == 0;
if(b){
int last = Integer.parseInt(data.get(data.size()-1));
for(int i =0;i<10;i++){
data.add(last+i+1+"");
}
}
return b;
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
recyclerView.onLoadMoreFinish((boolean)o);
}
}
class RefreshTask extends AsyncTask{
@Override
protected Object doInBackground(Object[] params) {
try {
// 模拟网络请求
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通过产生10内整数并对2取余来模拟加载是否成功
int random = (int)(Math.random()*10);
boolean b = random%2 == 0;
if(b){
data.clear();
for(int i =0;i<10;i++){
data.add(i+"");
}
}
return b;
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
String str = null;
if((boolean)o){
str = "刷新成功";
}else {
str = "刷新失败,请重试";
}
swipeRefreshLayout.setRefreshing(false);
mAdapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(),str,Toast.LENGTH_LONG).show();
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="su.guang.pulltoloadmorerecyclerview.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swiperefreshlayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<su.guang.pulltoloadmorerecyclerview.view.PullToLoadMoreRecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
MyAdapter.java
package su.guang.pulltoloadmorerecyclerview.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import su.guang.pulltoloadmorerecyclerview.R;
/**
* Created by Jaren on 2016/8/19.
*/
public class MyAdapter extends CustomAdapter {
private Context context;
private List<String> data;
public MyAdapter(Context context, List<String> data) {
super(context, data);
this.context = context;
this.data = data;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
if(!isFooterItem(position)){
MyViewHolder tvViewHolder = (MyViewHolder) holder;
tvViewHolder.tv.setText(data.get(position));
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == NORMAL_ITEM){
View itemView = null;
itemView = LayoutInflater.from(context).inflate(R.layout.item_recyclerview,null);
return new MyViewHolder(itemView);
}
return super.onCreateViewHolder(parent, viewType);
}
class MyViewHolder extends RecyclerView.ViewHolder{
private TextView tv;
private ImageView iv;
public MyViewHolder(View itemView) {
super(itemView);
tv= (TextView) itemView.findViewById(R.id.tv);
iv = (ImageView) itemView.findViewById(R.id.iv);
}
}
}
Demo:http://download.csdn.net/detail/jarencrazy/9608583
最后,第一次写完整的文章,确实感觉到把这些东西转换成文字很难,如果有表述不清的,或者表述错误、理解错误的,还请大家在评论中指正,谢谢!