android 打造真正的下拉刷新上拉加载recyclerview(四):自动加载和其他封装

来源:互联网 发布:最新淘宝店铺教程视频 编辑:程序博客网 时间:2024/05/21 17:20

转载请注明出处:http://blog.csdn.net/anyfive/article/details/53098820

前言

之前,我们介绍了下拉刷新上拉加载RecyclerView的使用,然后依次写了两篇文章来分别介绍添加删除头尾部,和上拉刷新下拉加载。这篇文章算是PTLRecyclerView最后的一篇文章了,那么在这里,我们主要介绍以下几点:

  • 滑动到底部自动加载
  • EmptyView的实现
  • 对RecyclerView的Adapter进行封装,使其更好用
  • 分割线相关

自动加载

还记得在使用ListView的时候,我们怎么实现自动加载的功能吗?给ListView设置滑动监听,当滑动到最后一项时,加载更多数据。但是在RecyclerView中,你会发现监听滑动的时候,拿不到firstVisibleItem和visibleItemCount了,那怎么办呢?既然他不给我们,那我们自己去拿这两个参数就好啦。

步骤如下:

  1. 继承RecyclerView;
  2. 重写onScrollStateChanged(int state)方法;
  3. 当当前滚动状态为静默(RecyclerView.SCROLL_STATE_IDLE)时,通过LayoutManager的findLastVisibleItemPosition/findLastVisibleItemPositions方法,获得lastVisibleItemPosition;
  4. 若lastVisibleItemPosition是最后一项,加载数据。

当然,我们要把”自动加载”功能封装起来的话,就需要有一个接口用于回调”开始加载”,也就是OnLoadListener:

public interface OnLoadListener {    void onStartLoading(int skip);//开始加载,传入skip}
  • 1
  • 2
  • 3

然后,我们就可以封装AutoLoadRecyclerView了,由于这个比较简单,我们这里只贴出onScrollStateChanged方法:

 @Overridepublic void onScrollStateChanged(int state) {    super.onScrollStateChanged(state);    if (state == RecyclerView.SCROLL_STATE_IDLE              && !mIsLoading             && mLoadMoreEnable             && mLoadView != null) {        LayoutManager layoutManager = getLayoutManager();        int lastVisibleItemPosition;        if (layoutManager instanceof GridLayoutManager) {            lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();        } else if (layoutManager instanceof StaggeredGridLayoutManager) {            int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];            ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);            lastVisibleItemPosition = findMax(into);        } else {            lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();        }        if (layoutManager.getChildCount() > 0                && lastVisibleItemPosition >= layoutManager.getItemCount() - 1                && layoutManager.getItemCount() > layoutManager.getChildCount()                 && !mNoMore ) {            mIsLoading = true;            if (mOnLoadListener != null)                mOnLoadListener.onStartLoading(mRealAdapter.getItemCount());        }    }}private int findMax(int[] lastPositions) {    int max = lastPositions[0];    for (int value : lastPositions) {        if (value > max) {            max = value;        }    }    return max;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

代码其实很简单,这里用伪代码讲解下逻辑:

if(不再滑动 && 不是加载中 && 自动加载功能可用 && 加载底部!=null) {    if (表格布局) {        获得lastVisibleItemPosition;    } else if (瀑布流布局) {        获得lastVisibleItemPosition;    } else if (线性布局) {        获得lastVisibleItemPosition;    }    if(item数大于0 && 是最后一项 && 超过一屏 && 还有更多) {        正在加载:mIsLoading = true;        if(OnLoadListener不为空) {            回调"开始加载"方法;        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

另外无非也就是加入常规的几个方法:

  • setNoMore(boolean noMore);//设置没有更多数据
  • completeLoad();//完成加载

至于自定义尾部的做法,和下拉刷新上拉加载的自定义头尾部是一样的,可以去看看我们上一篇关于下拉刷新上拉加载的介绍。

EmptyView的实现

直接说思路: 
1. 注册一个“适配器数据观察者”: 
Adapter.registerAdapterDataObserver(DataObserver); 
2. 重写onChanged方法,判断内容是否为空,是的话显示EmptyView,否则隐藏;

思路就是这么简单,也没什么好说的,直接来看看代码,首先,在setAdpater方法中注册观察者:

@Overridepublic void setAdapter(Adapter adapter) {    super.setAdapter(adapter);    adapter.registerAdapterDataObserver(mDataObserver);    mDataObserver.onChanged();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

接着看下这个DataObserver:

private class DataObserver extends AdapterDataObserver{    @Override    public void onChanged() {        if (mEmptyView == null) {            return;        }        int itemCount = 0;        itemCount += getAdapter().getItemCount();        if (itemCount == 0) {            mEmptyView.setVisibility(VISIBLE);            if (getVisibility() != INVISIBLE)                     setVisibility(INVISIBLE);        } else {            mEmptyView.setVisibility(GONE);            if (getVisibility() != VISIBLE)                setVisibility(VISIBLE);        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

注意,PTLRecyclerView中的EmptyView实现是写在HeaderAndFooterRecyclerView中的,其中还针对这个项目进行了其他一些处理,有兴趣的同学可以去看下源码。

对RecyclerView的Adapter进行封装

关于这点鸿洋大神已经讲过了,建议各位客官去看看鸿洋大神讲解的《为RecyclerView打造通用Adapter 让RecyclerView更加好用》,毕竟老司机,讲得比较好。

我们知道,在使用RecyclerView.Adapter的时候,需要重写以下几个方法:

  • ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
  • void onBindViewHolder(ViewHolder holder, int position);
  • int getItemCount();
  • long getItemId(int position);
  • int getItemViewType(int position);//需要多种类型item的时候使用

其中,getItemCount和getItemId在日常使用中,基本都是一样的,因此可以抽出来;而 
onCreateViewHolder,只要我们有一个万能的ViewHolder,也可以抽出来;也就是说,在日常使用中,我们真正需要做的只有以下几点:

  • 设置布局文件
  • 绑定数据
  • int getItemViewType(int position);//需要多种类型item的时候使用

既然明确了我们需要实现的方法,便可以开始封装了,我们先看看多种类型item的adapter:

public abstract class MultiTypeAdapter extends RecyclerView.Adapter<ViewHolder> {    protected String TAG;    protected Context mContext;    protected ArrayList mDatas;    public MultiTypeAdapter(Context mContext, ArrayList mDatas) {        this.mContext = mContext;        this.mDatas = mDatas;        this.TAG = getClass().getSimpleName();    }    @Override    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        int layoutId = getLayoutIdByType(viewType);        return ViewHolder.get(mContext,parent,layoutId);    }    @Override    public void onBindViewHolder(ViewHolder holder, int position) {        onBindViewHolder(holder,getItemViewType(position),mDatas.get(position));    }    @Override    public int getItemCount() {        return mDatas.size();    }    @Override    public long getItemId(int position) {        return position;    }    /**子类需实现以下三个方法*/    protected abstract int getLayoutIdByType(int viewType);    @Override    public abstract int getItemViewType(int position);    protected abstract void onBindViewHolder(ViewHolder holder,int type,Object data);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

可以看到,我们在使用时,只需要继承这个MultiTypeAdapter,实现三个抽象方法就可以了,比如:

rcv.setAdapter(new MultiTypeAdapter(mContext,mDatas) {    @Override    protected int getLayoutIdByType(int viewType) {//    根据type返回布局        return 0;    }    @Override    public int getItemViewType(int position) {//    根据position返回type        return 0;    }    @Override    protected void onBindViewHolder(ViewHolder holder, int type, Object data) {//        绑定数据    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

系不系so easy?!

那么当我们只需要一种类型的item的时候呢?我们来写一个继承于MultiTypeAdapter的SimpleAdapter:

public abstract class SimpleAdapter<T> extends MultiTypeAdapter {    protected int mLayoutId;    public SimpleAdapter(Context context,ArrayList<T> datas,int layoutId) {        super(context,datas);        this.mLayoutId = layoutId;    }    @Override    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        return super.onCreateViewHolder(parent,viewType);    }    @Override    protected int getLayoutIdByType(int viewType) {        return mLayoutId;    }    @Override    public int getItemViewType(int position) {        return 0;    }    @Override    protected void onBindViewHolder(ViewHolder holder, int type, Object data) {        onBindViewHolder(holder, (T)data);    }    /**子类需实现以下方法*/    protected abstract void onBindViewHolder(ViewHolder holder,T data);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

这下,只需要实现一个方法就可以了,比如:

rcv.setAdapter(new SimpleAdapter<String>(mContext, mDatas, R.layout.item_test) {    @Override    protected void onBindViewHolder(ViewHolder holder, String data) {    }});
  • 1
  • 2
  • 3
  • 4
  • 5

so so easy!!!

虽然只是简单的封装,但可以帮我们节省很多工作,让代码变得更加简洁。

至于万能ViewHolder,有兴趣的可以看看上面提到的鸿洋大神那篇文章,或者PTLRecyclerView的源码。

分割线相关

用过RecyclerView的同学应该知道,RecyclerView的分割线比较麻烦。那么我们来自己继承RecyclerView.ItemDecoration实现一个简单的分割线,在开始写代码之前,一定要问问自己:实现后你想要怎么使用?

对我而言,实际开发中的分割线大部分只是一个颜色,顶多是一个Drawable;

我希望我可以还是调用addItemDecoration方法,传入一个分割线就可以了;

至于这个分割线我希望在构造的时候,只需要传入资源id(res)或者Drawable就可以了,当然,在有需要的时候可以传入宽度和高度就更好了。

PTLRecyclerView的分割线的实现思路是这样的:

  1. BaseItemDecoration继承RecyclerView.ItemDecoration;
  2. BaseItemDecorationHelper,抽象类,用于绘制分割线的类,有一些辅助方法和两个抽象方法:onDraw和getItemOffsets;
  3. GridItemDecorationHelper、LinearItemDecorationHelper、StaggeredItemDecorationHelper,都是继承于BaseItemDecorationHelper的,用于绘制三种布局的分割线;

我们来看看BaseItemDecoration中最重要的两个方法:

@Overridepublic void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {    if (mItemDecorationHelper != null) {        mItemDecorationHelper.onDraw(c, parent, mDivider, mHeight, mWidth);        return;    }    RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();    if (layoutManager instanceof GridLayoutManager) {        mItemDecorationHelper = new GridItemDecorationHelper();    } else if (layoutManager instanceof StaggeredGridLayoutManager) {        mItemDecorationHelper = new StaggeredItemDecorationHelper();    } else if (layoutManager instanceof LinearLayoutManager) {        mItemDecorationHelper = new LinearItemDecorationHelper();    }    if (mItemDecorationHelper != null)        mItemDecorationHelper.onDraw(c, parent, mDivider, mHeight, mWidth);}@Overridepublic void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {    if (mItemDecorationHelper != null) {        mItemDecorationHelper.getItemOffsets(outRect,view,parent,mHeight,mWidth);        return;    }    RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();    if (layoutManager instanceof GridLayoutManager) {        mItemDecorationHelper = new GridItemDecorationHelper();    } else if (layoutManager instanceof StaggeredGridLayoutManager) {        mItemDecorationHelper = new StaggeredItemDecorationHelper();    } else if (layoutManager instanceof LinearLayoutManager) {        mItemDecorationHelper = new LinearItemDecorationHelper();    }    if (mItemDecorationHelper != null)        mItemDecorationHelper.getItemOffsets(outRect,view,parent,mHeight,mWidth);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

可以看到,我们把绘制分割线的具体工作,都外包给了Helper类,这样一来,逻辑和分工就更加明确清晰了。

在这里,Helper类的具体代码就不再贴出了,有兴趣的同学可以去看看源码。

我们来看下使用:

rcv.addItemDecoration(new BaseItemDecoration(this,R.color.colorAccent));
  • 1

一句代码就加入了分割线,其他什么都不用管,方便省心。

后记

这篇文章是PTLRecyclerView的最后一篇文章,现在这个项目还能稚嫩,我真诚地希望各位大牛可以加入到这个项目中,让这个项目越来越好。如果你还有什么建议或者疑问,可以直接留言,或者私信我,感谢您的支持。

源码地址:https://github.com/whichname/PTLRecyclerView

传送门:

android 打造真正的下拉刷新上拉加载recyclerview(一):使用

android 打造真正的下拉刷新上拉加载recyclerview(二):添加删除头尾部

android 打造真正的下拉刷新上拉加载recyclerview(三):下拉刷新上拉加载

版权声明:本文为博主原创文章,未经博主允许不得转载。
阅读全文
0 0
原创粉丝点击