扩展RecyclerView——为RecyclerView添加“上拉加载更多”

        在以前用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


        最后,第一次写完整的文章,确实感觉到把这些东西转换成文字很难,如果有表述不清的,或者表述错误、理解错误的,还请大家在评论中指正,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值