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

来源:互联网 发布:js获取手机屏幕宽度 编辑:程序博客网 时间:2024/05/17 22:12

2017.3.20更新

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

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

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

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

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

这里写图片描述

该activity的布局文件:

<?xml version="1.0" encoding="utf-8"?><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布局:

<?xml version="1.0" encoding="utf-8"?><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布局:

<?xml version="1.0" encoding="utf-8"?><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:

<?xml version="1.0" encoding="utf-8"?><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<T> 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<T> getDataSet() {        return dataSet;    }    @Override    public int getItemCount() {        return dataSet.size();    }}

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

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

/** * 在这个装饰器中,只做与加载更多相关操作。public class LoadMoreAdapterWrapper extends BaseAdapter<String> {    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<String> 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 的使用方法还不够了解,可以自行学习。
有不当之处请大家批评指正,谢谢:)

3 0