Recylerview 加载更多功能实现(分页加载)

2017.3.20更新

更新说明:之前看评论里的童鞋们给我反映问题,很感谢大家指出,针对出现的问题我进行了一些改进。同时将加载更多的这个功能从主体的adapter中分离出来,使用了装饰者模式,降低了代码的耦合,这样便于维护和修改。

装饰者模式是常用的Java设计模式之一,不熟悉的童鞋可以自行查阅资料,先了解了装饰者模式看以下内容会容易懂一点~这个设计模式在Android里也是广泛应用,最典型的就是Context的应用了。

……………………….我是分割线…………………………………………

Recyclerview是 Listview 的升级版本,在项目中使用较为广泛,官方也推荐我们使用 Recyclerview 来代替 Listview,在此就不多说 Recyclerview 的优势特点 balala了。。。

在实际项目中,列表通常是分页的,请求服务器也只会一次请求若干条,按需加载,这样比较节省流量,这样就有了我们很常见的上拉加载更多的功能,具体的实现效果如下图:

该activity的布局文件:


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="io.geek.myapplication.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>


LinearLayout>

底部加载更多的view布局:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:gravity="center"
              android:layout_height="wrap_content">

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:text="正在加载..."
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

LinearLayout>

底部提示到底的view布局:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:padding="8dp"
              android:gravity="center"
              android:layout_height="wrap_content">

    <TextView
        android:text="已经到底了"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

LinearLayout>

数据item的view:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:orientation="horizontal"
              android:gravity="center"
              android:padding="8dp"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">

    <View
        android:background="@color/colorAccent"
        android:layout_width="50dp"
        android:layout_height="50dp"/>

    <TextView
        android:gravity="center"
        android:id="@+id/tv_content"
        tools:text="我是第一项"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

LinearLayout>

接下来就是代码的编写了,开头就已经讲到过我们在这要用到装饰者模式,其实装饰者模式并没有多么复杂,可以理解成给一个类装饰点新功能,并且不会影响到这个类本来的功能。装饰者模式写法也很简单,分为以下几步套路:

1. 装饰者模式套路第一步:

创建基类(装饰类和被装饰类共同父类),首先写一个抽象类BaseAdapter,继承自RecyclerView.Adapter,这个Adapter中只封装了与数据源相关的字段和方法:

/**
 * 与数据源相关的字段和方法封装在父类中
 */

public abstract class BaseAdapter<T> extends RecyclerView.Adapter {
    protected List dataSet = new ArrayList<>();


    public void updateData(List dataSet) {
        this.dataSet.clear();
        appendData(dataSet);
    }

    public void appendData(List dataSet) {
        if (dataSet != null && !dataSet.isEmpty()) {
            this.dataSet.addAll(dataSet);
            notifyDataSetChanged();
        }
    }

    public List getDataSet() {
        return dataSet;
    }

    @Override
    public int getItemCount() {
        return dataSet.size();
    }
}

2. 装饰者模式套路第二步

创建装饰类,然后开始编写加载更多的adapter的包装类,这个类继承自BaseAdpater,这个装饰类中只负责处理加载更多的逻辑,代码如下:

/**
 * 在这个装饰器中,只做与加载更多相关操作。

public class LoadMoreAdapterWrapper extends BaseAdapter {
    private BaseAdapter mAdapter;
    private static final int mPageSize = 10;
    private int mPagePosition = 0;
    private boolean hasMoreData = true;
    private OnLoad mOnLoad;

    public LoadMoreAdapterWrapper(BaseAdapter adapter, OnLoad onLoad) {
        mAdapter = adapter;
        mOnLoad = onLoad;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == R.layout.list_item_no_more) {
            View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
            return new NoMoreItemVH(view);
        } else if (viewType == R.layout.list_item_loading) {
            View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
            return new LoadingItemVH(view);
        } else {
            return mAdapter.onCreateViewHolder(parent, viewType);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof LoadingItemVH) {
            requestData(mPagePosition, mPageSize);
        } else if (holder instanceof NoMoreItemVH) {

        } else {
            mAdapter.onBindViewHolder(holder, position);
        }
    }

    private void requestData(int pagePosition, int pageSize) {

        //网络请求,如果是异步请求,则在成功之后的回调中添加数据,并且调用notifyDataSetChanged方法,hasMoreData为true
        //如果没有数据了,则hasMoreData为false,然后通知变化,更新recylerview

        if (mOnLoad != null) {
            mOnLoad.load(pagePosition, pageSize, new ILoadCallback() {
                @Override
                public void onSuccess() {
                    notifyDataSetChanged();
                    mPagePosition = (mPagePosition + 1) * mPageSize;
                    hasMoreData = true;
                }

                @Override
                public void onFailure() {
                    hasMoreData = false;
                }
            });
        }
    }

    @Override
    public int getItemViewType(int position) {
        if (position == getItemCount() - 1) {
            if (hasMoreData) {
                return R.layout.list_item_loading;
            } else {
                return R.layout.list_item_no_more;
            }
        } else {
            return mAdapter.getItemViewType(position);
        }
    }

    @Override
    public int getItemCount() {
        return mAdapter.getItemCount() + 1;
    }

    static class LoadingItemVH extends RecyclerView.ViewHolder {

        public LoadingItemVH(View itemView) {
            super(itemView);
        }

    }

    static class NoMoreItemVH extends RecyclerView.ViewHolder {

        public NoMoreItemVH(View itemView) {
            super(itemView);
        }
    }

}

public interface OnLoad {
    void load(int pagePosition, int pageSize, ILoadCallback callback);
}


public interface ILoadCallback {
    void onSuccess();

    void onFailure();
}

解析如下:
1. 该类中的传入的mAdapter就是需要被装饰的adapter,mOnLoad是自定义的一个接口,传入之后可以回调处理加载更多数据。
2. 首先重写getItemCount方法,因为在列表中最后始终会有一个加载更多或者是到底提示的view,所以item的数目始终是数据源的数目多一个。
3. 重写getItemType方法:首先判断position是否是最后一个,如果不是的话,则直接返回被装饰的mAdapter的getItemType;如果是的话判断hasMoreData变量,是true则返回加载更多的布局文件ID,反之返回到底的布局文件ID。这里的hasMoreData是我们自己定义的一个boolean字段,通过改变这个值就可以最后一个view应该是加载更多还是到底。
4. 重写onCreateViewHolder方法,在这个方法中通过判断viewType的类型返回不同的ViewHolder。
5. 重写onBindViewHolder方法,在这个方法中,判断holder的类型的不同做出不同的操作,如果holder是加载更多的holder,那么表示加载更多的view正在展示,这时候就应该做出加载更多的操作,这个操作放在了requestData方法中。
6. 在requestData方法中,触发了OnLoad接口中的load回调,传入的参数有当前的页码加载数据一页的大小,加载完成之后的回调接口,可以看到加载完成之后会进行不同的作,如果成功,则设置hasMoreData为true,并且通知数据发生改变,更新列表,改变前页码;如果失败的话,则把hasMoreData设置为false。

3. 装饰者模式套路第三步

创建被装饰类,同样这个类也需要继承自BaseAdapter:


/**
 * 被装饰类要和装饰类继承自同一父类
 */

public class MyAdapter extends BaseAdapter<String> {
    private Context mContext;

    public MyAdapter(Context context) {
        mContext = context;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(mContext).inflate(R.layout.list_item_mine, parent, false);
        return new MyViewHolder(v);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ((MyViewHolder) holder).bind(getDataSet().get(position));
    }


    static class MyViewHolder extends RecyclerView.ViewHolder {
        TextView mTextView;

        public MyViewHolder(View itemView) {
            super(itemView);

            mTextView = (TextView) itemView.findViewById(R.id.tv_content);
        }

        public void bind(CharSequence content) {
            mTextView.setText(content);
        }
    }

}

4. 装饰者模式套路第四步

最后一步就是如何使用,这个adapter就是一个普通的没有上拉加载功能的adapter,如果要给它加上这个功能,只需要这么使用:


public class MainActivity extends AppCompatActivity {

    RecyclerView mRecyclerView;
    BaseAdapter mAdapter;
    int loadCount;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler);

        //创建被装饰者类实例
        final MyAdapter adapter = new MyAdapter(this);
        //创建装饰者实例,并传入被装饰者和回调接口
        mAdapter = new LoadMoreAdapterWrapper(adapter, new OnLoad() {
            @Override
            public void load(int pagePosition, int pageSize, final ILoadCallback callback) {
                //此处模拟做网络操作,2s延迟,将拉取的数据更新到adpter中
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        List dataSet = new ArrayList();
                        for (int i = 0; i < 10; i++) {
                            dataSet.add("我是一条数据");
                        }
                        //数据的处理最终还是交给被装饰的adapter来处理
                        adapter.appendData(dataSet);
                        callback.onSuccess();
                        //模拟加载到没有更多数据的情况,触发onFailure
                        if (loadCount++ == 3) {
                            callback.onFailure();
                        }
                    }
                }, 2000);
            }
        });
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
    }

}

这样就完整实现了 Recylerview 的加载更多功能,使用装饰者模式使得整个代码结构更加清晰,在被装饰的adapter中就不用处理太多逻辑,专心处理数据展示即可,易于维护。

其中比较绕的地方可能就是数据加载的回调接口,不过仔细理一下还是没有多大问题,总体来说还是很简单的,如果还是有不明白的,可能你对 Recylerview 的使用方法还不够了解,可以自行学习。
有不当之处请大家批评指正,谢谢:)

你可能感兴趣的:(Android开发)