Android RecycleView轻松实现下拉刷新、加载更多

![PullRefresh.gif](http://upload-images.jianshu.io/upload_images/2244299-ea8bb3b32f34f5aa.gif?imageMogr2/auto-orient/strip)

那如同这个题目,这里面涉及的东西其实还是比较多的,RecycleView SwipeRefreshLayout,下拉刷新(这个就是SwipeRefreshLayout的),加载更多。

SwipeRefreshLayout

这个是Google自己封装的一个下拉刷新的控件,里面使用了5.0开始的嵌套滑动机制,有兴趣的朋友可以去看看源码!使用起来其实就涉及到以下方法:

setOnRefreshListener() 下拉刷新的相关回调。

setRefresh() 通知是否开始刷新或者刷新完成。(坑1)

setColorSchemeColors() loading的时候progressbar的颜色,支持多个。

SwipeRefreshLayout的坑

进入页面调用setRefresh(true),根本不显示刷新的小圆圈?!
简单的说,这个就是在onCreate()方法执行的时候,view还没有绘制出来,这个时候你设置刷新不刷新其实都一样的,解决方法,post一下!

mRecyclerView.post(new Runnable() {
        @Override
        public void run() {
            mRefreshLayout.setRefreshing(refresh);
        }
    });

RecycleView

RecycleView其实出现都有一定的年头了,前几天公司来面试的居然说他还没有用过。。这个也是醉醉的!

RecycleViewListView的强力升级!加入了holder便于管理和复用相同的类型。

就我目前掌握的情况,RecycleView对于ListView有了以下的不同:

1、加入了LayoutManager用用管理各种类型的布局,而且通过不同的布局可以实现横向、竖向、瀑布式的等各种复杂的布局。

2、加入Holder来管理相关布局和复用,对于每一种Type的View你都要创建一个对应的Holder来管理它!

3、取消了header和bottom布局。

4、没有现成的itemClick回调。

5、引入了丰富的动画效果。(坑4)

6、添加了丰富的数据刷新的方法,可以局部刷新了!(坑3)

7、可自定义相关分割线。

8、支持swipe删除和drag排序。(ItemTouchHelper 帮助类)

9、默认是不显示scrollBar的(坑2)

10、可以设置不同类型holder占据不同的空间(ItemColumnSpan GridLayoutManager)

上面这些不是所有的都讲,其实本文主要涉及的就是相关adapter,里面对应不同的holder,及相关的封装。然后说说踩了哪些坑。

基本思路

  • 1、明确什么时候开始加载更多?

下拉刷新就调用SwipeRefreshLayout相关就好了,那么加载更多呢?这个就要自己去写相关的布局了。然后第一个问题,什么时候加载更多??因为RecycleView有各种布局,所以判断最后一个也是要区分不同的adapter的!

  • 2、加载更多有多少种情况?
    大致有三种,正在加载更多;加载更多错误;没有更多数据了;

具体实现

1、监听滑动,满足条件开始加载更多。

        @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (null != scrollListener) {
            scrollListener.onScrolled(SwipeRefreshRecycleView.this, dx, dy);
        }
        if (null == manager) {
            throw new RuntimeException("you should call setLayoutManager() first!!");
        }
        if (null == adapter) {
            throw new RuntimeException("you should call setAdapter() first!!");
        }
        if (manager instanceof LinearLayoutManager) {
            int lastCompletelyVisibleItemPosition = ((LinearLayoutManager) manager).findLastCompletelyVisibleItemPosition();

            if (adapter.getItemCount() > 1 && lastCompletelyVisibleItemPosition >= adapter.getItemCount() - 1 && adapter.isHasMore()) {
                adapter.isLoadingMore();
                if (null != listener) {
                    listener.onLoadMore();
                }
            }
            int position = ((LinearLayoutManager) manager).findFirstVisibleItemPosition();
            if (lastTitlePos == position) {
                return;
            }
            lastTitlePos = position;
        }
        if (manager instanceof StaggeredGridLayoutManager) {
            int[] itemPositions = new int[2];
            ((StaggeredGridLayoutManager) manager).findLastVisibleItemPositions(itemPositions);

            int lastVisibleItemPosition = (itemPositions[1] != 0) ? ++itemPositions[1] : ++itemPositions[0];

            if (lastVisibleItemPosition >= adapter.getItemCount()  && adapter.isHasMore()) {
                adapter.isLoadingMore();
                if (null != listener) {
                    listener.onLoadMore();
                }
            }

        }

    }

2、定义自己的加载更多的ViewHolder。

3.定义相关的方法实时更新ViewHolder的三种状态。

public class NewBottomViewHolder extends RecyclerView.ViewHolder{
    @Bind(R.id.footer_container)
    public LinearLayout contaier;

    @Bind(R.id.progressbar)
    ProgressBar pb;
    @Bind(R.id.content)
    TextView content;
    @Nullable
    private final SwipeRefreshRecycleView.OnRefreshLoadMoreListener mListener;

    public NewBottomViewHolder(View itemView, SwipeRefreshRecycleView.OnRefreshLoadMoreListener listener) {

        super(itemView);
        ButterKnife.bind(this,itemView);
        mListener = listener;
    }

    public void bindDateView(int state) {
        switch (state) {
            case AdapterLoader.STATE_LASTED:
                contaier.setVisibility(View.VISIBLE);
                contaier.setOnClickListener(null);
                pb.setVisibility(View.GONE);
                content.setText("---  没有更多了  ---");
                break;
            case AdapterLoader.STATE_LOADING:
                contaier.setVisibility(View.VISIBLE);
                content.setText("加载更多!!");
                contaier.setOnClickListener(null);
                pb.setVisibility(View.VISIBLE);
                break;
            case AdapterLoader.STATE_ERROR:
                contaier.setVisibility(View.VISIBLE);
                pb.setVisibility(View.GONE);
                content.setText("--- 加载更多失败点击重试 ---");
                contaier.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mListener != null) {
                            mListener.onLoadMore();
                        }
                        content.setText("加载更多!!");
                        pb.setVisibility(View.VISIBLE);
                    }
                });
                break;
        }
    }

}

4.定义相关扩展方法便于用户自己定义底部布局及相关状态处理。

这里就必须详细讲讲Adapter里面的相关方法了!

getItemCount(),在RecycleView知道它一共有多少数量的Item需要展示,返回0之后不会执行剩余的方法!

onCreateViewHolder(ViewGroup parent, int viewType),某种Type的Holder第一次创建的时候会调用该方法,当然没有复用的时候也会去创建,一旦复用了,改方法不会再执行了!

onBindViewHolder(RecyclerView.ViewHolder holder, int position),每一次更新对应itemView的时候都会调用该方法,所以在该方法中要实时的刷新数据!(因为存在复用,所以刷新的时候一定要彻底!!!

以上三个方法是必须实现的,因为在父类adapter里是抽象滴!

还有一个方法也比较重要:

getItemViewType(int position),这个方法是返回对应pos的类型的,如果你只有一个类型,不需要重写该方法,默认返回的是0。

是不是这么说起来比ListView还要爽一点儿?不用去判断什么convertView==null!

@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    switch (viewType) {
        case TYPE_BOTTOM:
            if (loadMore != null) {
                RecyclerView.ViewHolder holder = onBottomViewHolderCreate(loadMore);
                if (holder == null) {
                    throw new RuntimeException("You must impl onBottomViewHolderCreate() and return your own holder ");
                }
                return holder;
            } else {
                return new BottomViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_footer, parent, false));
            }
        default:
            return onViewHolderCreate(parent, viewType);
    }

}

@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (getItemViewType(position) == TYPE_BOTTOM) {
        loadState = loadState == STATE_ERROR ? STATE_ERROR : isHasMore() ? STATE_LOADING : STATE_LASTED;
        if (loadMore != null) {
            try {
                onBottomViewHolderBind(holder, loadState);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            try {
                ((BottomViewHolder) holder).bindDateView(loadState);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    } else {
        onViewHolderBind(holder, position);
    }
}

这里在RefreshRecycleAdapter<T>中实现了刚刚说的三个方法,并且相关的已经加final修饰了!所以之后你只需要实现如下方法来完成你自己的item填充就好了:

   RecyclerView.ViewHolder onViewHolderCreate(ViewGroup parent, int viewType);

void onViewHolderBind(RecyclerView.ViewHolder holder, int position);

对于加载更多的几种状态的更改,提供如下的相关方法!

boolean isHasMore();

void isLoadingMore();

void loadMoreError();

对于创建自己制定的加载更多的布局,提供如下方法扩展!

void setLoadMoreView(View view);

RecyclerView.ViewHolder onBottomViewHolderCreate(View loadMore);

void onBottomViewHolderBind(RecyclerView.ViewHolder holder, int loadState);

还没有说的那就是数据源相关的方法。提供了set和append两种方式!

void setList(List<T> data);

void appendList(List<T> data);

@Override
public final void appendList(List<T> data) {
    int positionStart = list.size();
    list.addAll(data);
    int itemCount = list.size() - positionStart;

    if (positionStart == 0) {
        notifyDataSetChanged();
    } else {
        notifyItemRangeInserted(positionStart + 1, itemCount);
    }
}

还是那话,这些方法都是RefreshRecycleAdapter<T>里面写好的,我们写自己的adapter时更本不用去care!只需要去调用setList()或者appendList()就好了!!

说到这里不得不提提RecycleView刷新数据的相关方法和坑!

notifyDataSetChanged()的基础上, RecycleView增加了一系列的方法用于增删改。所以不要再任性的一味使用notifyDataSetChanged(),这样也不专业了!

notifyItemInserted();
notifyItemRangeInserted();

notifyItemChanged();
notifyItemRangeChanged();

notifyItemRemoved();
notifyItemRangeRemoved();

在使用的过程中,发现调用notifyItemChanged()之后不会去执行onBindViewHolder(),(坑3 坑4)这个就导致刷新没有触发了!最后搜到的结果是因为mRecyclerView.setItemAnimator(new DefaultItemAnimator())引起的,解决方案是复写相关方法

@Override
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull List<Object> payloads) {
    return true;
}

小结

首先通过addList()或者appendList()的方法设置相关数据源。

滑动过程中需要加载更多时回调相关方法,并在adapter中通知相关状态刷新。

 adapter.isLoadingMore();
 if (null != listener) {
    listener.onLoadMore();
 }

加载错误的时候调用相关的方法通知状态改变。

adapter.loadMoreError();

创建BottomHolder的时候判断有没有设置自定义的view,如果有,那么就去走子类的onBottomViewHolderCreate()方法创建自定义的Bottomholder,然后实时更新相关数据!

    @Override
public final void setLoadMoreView(@NonNull View view) {
    loadMore = view;
}

if (loadMore != null) {
    RecyclerView.ViewHolder holder = onBottomViewHolderCreate(loadMore);
    if (holder == null) {
        throw new RuntimeException("You must impl onBottomViewHolderCreate() and return your own holder ");
    }
    return holder;
        } 

最后在onBottomViewHolderBind(RecyclerView.ViewHolder holder, int state)的方法中执行bindDateView(state)实时刷新相关的状态。

public void bindDateView(int state) {
    switch (state) {
        case AdapterLoader.STATE_LASTED:
            contaier.setVisibility(View.VISIBLE);
            contaier.setOnClickListener(null);
            pb.setVisibility(View.GONE);
            content.setText("---  没有更多了  ---");
            break;
        case AdapterLoader.STATE_LOADING:
            contaier.setVisibility(View.VISIBLE);
            content.setText("加载更多!!");
            contaier.setOnClickListener(null);
            pb.setVisibility(View.VISIBLE);
            break;
        case AdapterLoader.STATE_ERROR:
            contaier.setVisibility(View.VISIBLE);
            pb.setVisibility(View.GONE);
            content.setText("--- 加载更多失败点击重试 ---");
            contaier.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mListener != null) {
                        mListener.onLoadMore();
                    }
                    content.setText("加载更多!!");
                    pb.setVisibility(View.VISIBLE);
                }
            });
            break;
    }
}

PS 最后还有默认是不显示scrollBar的问题,这个问题,似乎必须在xml里面配置,不能代码直接new RecycleView。然后可以使用相关代码控制ScrollBar是否显示!

mRecyclerView.setVerticalScrollBarEnabled(true)

<android.support.v7.widget.RecyclerView
    android:id="@+id/recycle_view"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

另外对于特性8、10这里就不详细介绍了,滑动删除和拖拽排序在TouchHelperCallback中有相关支持!方法如下:

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
    if (callBack != null) {
        callBack.onItemMove(viewHolder.getAdapterPosition(),
                target.getAdapterPosition());
    }
    return true;
}

@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    if (callBack != null) {
        callBack.onItemDismiss(viewHolder.getAdapterPosition());
    }
}

详细的可以参照相关Demo-FangShiActivity


gradle快速集成

  compile 'com.lovejjfg.powerrecycle:powerrecycle:1.0.0'

相关下载

演示Demo下载

项目中的使用

---- Edit By Joe ----

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容