首页 » FastDev4Android » 正文

RecyclerView完全解析之下拉刷新与上拉加载SwipeRefreshLayout

(一).前言:

话说RecyclerView已经面市很久,也在很多应用中得到广泛的使用,在整个开发者圈子里面也拥有很不错的口碑,那说明RecyclerView拥有比ListView,GridView之类控件有很多的优点,例如:数据绑定,Item View创建,View的回收以及重用等机制。前三三篇文章已经贡呢更新了以下三个部分:

  1. RecyclerView控件的基本使用,包括基础,进阶,高级部分,动画之类(点击进入)
  2. RecyclerView控件的实战实例(点击进入)
  3. RecyclerView控件集合AA(Android Annotations)注入框架实例(点击进入)

本来这个专题不打算更新,不过前两天看到各位童鞋还是挺积极的评论到,希望可以更新RecyclerView加入下拉刷新和上拉加载更多的功能。正好昨天周末,所以我这边也就实现了这样的功能,今天更新一下。具体代码已经上传到下面的项目中,欢迎各位去star和fork一下。

FastDev4Android框架项目地址:https://github.com/jiangqqlmj/FastDev4Android

RecyclerView实现的列表,默认情况下面是不带下拉刷新和上拉记载更多效果的,但是我在我们的实际项目当中,为了提高用户体验,这种效果一般都需要实现,在我以前的博客中已经重写了ListView(Android 列表下拉刷新组件PullToRefreshListView使用)实现上拉刷新和上拉加载更多效果。现在我们已经学会了ListView,GridView的替代品RecyclerView的基本使用方法,那么必不可少的也需要实现上拉刷新和上拉加载更多的效果了。今天我们会通过两种方式来实现,具体会采用Android的另一控件SwipeRefreshLayout。

(二).SwipeRefreshLayout介绍:

SwipeRefrshLayout是Google官方更新的一个Widget,可以实现下拉刷新的效果。该控件集成自ViewGroup在support-v4兼容包下,不过我们需要升级support library的版本到19.1以上。基本使用的方法如下:

  • setOnRefreshListener(OnRefreshListener):添加下拉刷新监听器
  • setRefreshing(boolean):显示或者隐藏刷新进度条
  • isRefreshing():检查是否处于刷新状态
  • setColorSchemeResources():设置进度条的颜色主题,最多设置四种,以前的setColorScheme()方法已经弃用了。

具体使用效果下面我们会看到。

(三).RecyclerView+SwpieRefreshLayout实现下拉刷新效果:

1.SwipeRefreshLayout本身自带下拉刷新的效果,那么我们可以选择在RecyclerView布局外部嵌套一层SwipeRefreshLayout布局即可,具体布局文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
< ?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <include layout="@layout/common_top_bar_layout"/>
    <android .support.v4.widget.SwipeRefreshLayout
        android:id="@+id/demo_swiperefreshlayout"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:scrollbars="vertical"
        >
        </android><android .support.v7.widget.RecyclerView
            android:id="@+id/demo_recycler"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            ></android>
   
</linearlayout>

2.接着在Activity中获取SwipeRefreshLayout控件并且设置OnRefreshListener监听器,同时实现里边的onRefresh()方法,在该方法中进行网络请求最新数据,然后刷新RecyclerView列表同时设置SwipeRefreshLayout的进度Bar的隐藏或者显示效果。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
demo_swiperefreshlayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                Log.d("zttjiangqq", "invoke onRefresh...");
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        List<string> newDatas = new ArrayList</string><string>();
                        for (int i = 0; i < 5; i++) {
                            int index = i + 1;
                            newDatas.add("new item" + index);
                        }
                        adapter.addItem(newDatas);
                        demo_swiperefreshlayout.setRefreshing(false);
                        Toast.makeText(RecyclerRefreshActivity.this, "更新了五条数据...", Toast.LENGTH_SHORT).show();
                    }
                }, 5000);
            }
        });

3.除此之外我们也来看一下Adapter和Activity中的其他代码,也方便各位童鞋查看。
RecyclerRefreshActivity.java

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class RecyclerRefreshActivity extends BaseActivity {
    private LinearLayout top_bar_linear_back;
    private TextView top_bar_title;
    private SwipeRefreshLayout demo_swiperefreshlayout;
    private RecyclerView demo_recycler;
    private RefreshRecyclerAdapter adapter;
    private LinearLayoutManager linearLayoutManager;
    private int lastVisibleItem;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.recycler_refresh_layout);
        top_bar_linear_back=(LinearLayout)this.findViewById(R.id.top_bar_linear_back);
        top_bar_linear_back.setOnClickListener(new CustomOnClickListener());
        top_bar_title=(TextView)this.findViewById(R.id.top_bar_title);
        top_bar_title.setText("RecyclerView下拉刷新,下拉加载更多...");
        demo_swiperefreshlayout=(SwipeRefreshLayout)this.findViewById(R.id.demo_swiperefreshlayout);
        demo_recycler=(RecyclerView)this.findViewById(R.id.demo_recycler);
        //设置刷新时动画的颜色,可以设置4个
        demo_swiperefreshlayout.setProgressBackgroundColorSchemeResource(android.R.color.white);
        demo_swiperefreshlayout.setColorSchemeResources(android.R.color.holo_blue_light,
                android.R.color.holo_red_light, android.R.color.holo_orange_light,
                android.R.color.holo_green_light);
        demo_swiperefreshlayout.setProgressViewOffset(false, 0, (int) TypedValue
                .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources()
                        .getDisplayMetrics()));
        linearLayoutManager=new LinearLayoutManager(this);
        linearLayoutManager.setOrientation(OrientationHelper.VERTICAL);
        demo_recycler.setLayoutManager(linearLayoutManager);
        //添加分隔线
        demo_recycler.addItemDecoration(new AdvanceDecoration(this, OrientationHelper.VERTICAL));
        demo_recycler.setAdapter(adapter = new RefreshRecyclerAdapter(this));
        demo_swiperefreshlayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                Log.d("zttjiangqq", "invoke onRefresh...");
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        List<String> newDatas = new ArrayList</string><string>();
                        for (int i = 0; i < 5; i++) {
                            int index = i + 1;
                            newDatas.add("new item" + index);
                        }
                        adapter.addItem(newDatas);
                        demo_swiperefreshlayout.setRefreshing(false);
                        Toast.makeText(RecyclerRefreshActivity.this, "更新了五条数据...", Toast.LENGTH_SHORT).show();
                    }
                }, 5000);
            }
        });
          class CustomOnClickListener implements View.OnClickListener{
        @Override
        public void onClick(View v) {
            RecyclerRefreshActivity.this.finish();
        }
    }
}
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
RefreshRecyclerAdapter.java
[cc lang="java"]
public class RefreshRecyclerAdapter extends RecyclerView.Adapter<RefreshRecyclerAdapter.ViewHolder>{
    private LayoutInflater mInflater;
    private List</string><string> mTitles=null;
    public  RefreshRecyclerAdapter(Context context){
        this.mInflater=LayoutInflater.from(context);
        this.mTitles=new ArrayList</string><string>();
        for (int i=0;i&lt;20;i++){
            int index=i+1;
            mTitles.add("item"+index);
        }
    }
    /**
     * item显示类型
     * @param parent
     * @param viewType
     * @return
     */

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final View view=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
        //这边可以做一些属性设置,甚至事件监听绑定
        //view.setBackgroundColor(Color.RED);
        ViewHolder viewHolder=new ViewHolder(view);

        return viewHolder;
    }

    /**
     * 数据的绑定显示
     * @param holder
     * @param position
     */

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.item_tv.setText(mTitles.get(position));
        holder.itemView.setTag(position);
    }
    @Override
    public int getItemCount() {
        return mTitles.size();
    }

    //自定义的ViewHolder,持有每个Item的的所有界面元素
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView item_tv;
        public ViewHolder(View view){
            super(view);
            item_tv = (TextView) view.findViewById(R.id.item_tv);
        }
    }

    //添加数据
    public void addItem(List</string><string> newDatas) {
        //mTitles.add(position, data);
        //notifyItemInserted(position);
        newDatas.addAll(mTitles);
        mTitles.removeAll(mTitles);
        mTitles.addAll(newDatas);
        notifyDataSetChanged();
    }

    public void addMoreItem(List</string><string> newDatas) {
        mTitles.addAll(newDatas);
        notifyDataSetChanged();
    }
}

以上重要地方的注释已经加上去了。

5.运行效果如下:

(四).RecyclerView设置滚动事件加入上拉加载更多功能

下面我们再来看RecyclerView和相关类的一些特性,

LayoutManger给我们提供了以下几个方法来让开发者方便的获取到屏幕上面的顶部item和顶部item相关的信息:

  • findFirstVisibleItemPosition()
  • findFirstCompletlyVisibleItemPosition()
  • findLastVisibleItemPosition()
  • findLastCompletlyVisibleItemPosition()

同时通过Recycler.Adapter的getItemCount()方法可以轻松获取到RecyclerView列表中Item View的个数。

那么下面我们通过监听滑动(滚动)事件,然后在里边判断是否已经滑动到最底部来加载更多的数据,使用方法如下:

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
 //RecyclerView滑动监听
        demo_recycler.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == adapter.getItemCount()) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            List</string><string> newDatas = new ArrayList</string><string>();
                            for (int i = 0; i < 5; i++) {
                                int index = i + 1;
                                newDatas.add("more item" + index);
                            }
                            adapter.addMoreItem(newDatas);
                        }
                    },1000);
                }
            }
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
            }
        });

运行效果如下:

(五).升级RecyclerView加入FootView实现上拉加载

上面我们虽然已经实现了上拉加载更多的效果,但是还比较丑陋,最起码要让用户知道确实在上拉加载的过程吧,例如加载一个底部的进度布局。这样一想,那么我们就按照ListView方式addFootView()呗,不过很可惜的是RecyclerView没有给我们提供addFootView()方法,那该怎么样办呢?我们来看RecyclerView.Apapter类:

我们要实现一个自定义Adapter一定需要实现onCreateViewHolder(ViewGroup paren,int viewType)方法,注意看方法中的第二个参数viewType,是不是想到布局类型了,也就是说该也支持多套布局显示的,那么查看基类中的所有方法如下:

上面有一个方法getItemType(),这个就和ListView的Adapter的实现差不多了,那么我们这边可以使用多套布局给RecyclerView加入一个FootView布局即可。RefreshFootAdapter.java具体实现流程如下:

1.加入布局状态标志-用来判断此时加载是普通Item还是foot view:

1
2
private static final int TYPE_ITEM = 0;  //普通Item View
private static final int TYPE_FOOTER = 1;  //顶部FootView

2.重写getItemCount()方法,返回的Item数量在数据的基础上面+1,增加一项FootView布局项

1
2
3
public int getItemCount() {
        return mTitles.size()+1;
    }

3.重写getItemViewType方法来判断返回加载的布局的类型

1
2
3
4
5
6
7
8
public int getItemViewType(int position) {
    // 最后一个item设置为footerView
    if (position + 1 == getItemCount()) {
                return TYPE_FOOTER;
            } else {
                return TYPE_ITEM;
            }
        }

4.接着onCreateViewHolder(ViewGroup parent,int viewType)加载布局的时候根据viewType的类型来选择指定的布局创建,返回即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //进行判断显示类型,来创建返回不同的View
        if(viewType==TYPE_ITEM){
            View view=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
            //这边可以做一些属性设置,甚至事件监听绑定
            //view.setBackgroundColor(Color.RED);
            ItemViewHolder itemViewHolder=new ItemViewHolder(view);
            return itemViewHolder;
        }else if(viewType==TYPE_FOOTER){
            View foot_view=mInflater.inflate(R.layout.recycler_load_more_layout,parent,false);
            //这边可以做一些属性设置,甚至事件监听绑定
            //view.setBackgroundColor(Color.RED);
            FootViewHolder footViewHolder=new FootViewHolder(foot_view);
            return footViewHolder;
        }
       return null;
    }

5.最后进行判断数据的时候(onBindViewHolder),判断holder的类型来进行判定数据即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if(holder instanceof ItemViewHolder) {
            ((ItemViewHolder)holder).item_tv.setText(mTitles.get(position));
            holder.itemView.setTag(position);
        }else if(holder instanceof FootViewHolder){
            FootViewHolder footViewHolder=(FootViewHolder)holder;
            switch (load_more_status){
                case PULLUP_LOAD_MORE:
                    footViewHolder.foot_view_item_tv.setText("上拉加载更多...");
                    break;
                case LOADING_MORE:
                    footViewHolder.foot_view_item_tv.setText("正在加载更多数据...");
                    break;
            }
        }
    }

6.整个RefreshFootAdapter完整代码如下:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package com.chinaztt.fda.adapter;
public class RefreshFootAdapter extends RecyclerView.Adapter<recyclerview .ViewHolder>{
    //上拉加载更多
    public static final int  PULLUP_LOAD_MORE=0;
    //正在加载中
    public static final int  LOADING_MORE=1;
    //上拉加载更多状态-默认为0
    private int load_more_status=0;
    private LayoutInflater mInflater;
    private List<string> mTitles=null;
    private static final int TYPE_ITEM = 0;  //普通Item View
    private static final int TYPE_FOOTER = 1;  //顶部FootView
    public RefreshFootAdapter(Context context){
        this.mInflater=LayoutInflater.from(context);
        this.mTitles=new ArrayList</string><string>();
        for (int i=0;i&lt;20;i++){
            int index=i+1;
            mTitles.add("item"+index);
        }
    }
    /**
     * item显示类型
     * @param parent
     * @param viewType
     * @return
     */

    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //进行判断显示类型,来创建返回不同的View
        if(viewType==TYPE_ITEM){
            View view=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
            //这边可以做一些属性设置,甚至事件监听绑定
            //view.setBackgroundColor(Color.RED);
            ItemViewHolder itemViewHolder=new ItemViewHolder(view);
            return itemViewHolder;
        }else if(viewType==TYPE_FOOTER){
            View foot_view=mInflater.inflate(R.layout.recycler_load_more_layout,parent,false);
            //这边可以做一些属性设置,甚至事件监听绑定
            //view.setBackgroundColor(Color.RED);
            FootViewHolder footViewHolder=new FootViewHolder(foot_view);
            return footViewHolder;
        }
       return null;
    }

    /**
     * 数据的绑定显示
     * @param holder
     * @param position
     */

    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if(holder instanceof ItemViewHolder) {
            ((ItemViewHolder)holder).item_tv.setText(mTitles.get(position));
            holder.itemView.setTag(position);
        }else if(holder instanceof FootViewHolder){
            FootViewHolder footViewHolder=(FootViewHolder)holder;
            switch (load_more_status){
                case PULLUP_LOAD_MORE:
                    footViewHolder.foot_view_item_tv.setText("上拉加载更多...");
                    break;
                case LOADING_MORE:
                    footViewHolder.foot_view_item_tv.setText("正在加载更多数据...");
                    break;
            }
        }
    }

    /**
     * 进行判断是普通Item视图还是FootView视图
     * @param position
     * @return
     */

    @Override
    public int getItemViewType(int position) {
    // 最后一个item设置为footerView
    if (position + 1 == getItemCount()) {
                return TYPE_FOOTER;
            } else {
                return TYPE_ITEM;
            }
        }
    @Override
    public int getItemCount() {
        return mTitles.size()+1;
    }
    //自定义的ViewHolder,持有每个Item的的所有界面元素
    public static class ItemViewHolder extends RecyclerView.ViewHolder {
        public TextView item_tv;
        public ItemViewHolder(View view){
            super(view);
            item_tv = (TextView) view.findViewById(R.id.item_tv);
        }
    }
    /**
     * 底部FootView布局
     */

    public static class FootViewHolder extends  RecyclerView.ViewHolder{
        private TextView foot_view_item_tv;
        public FootViewHolder(View view) {
            super(view);
            foot_view_item_tv=(TextView)view.findViewById(R.id.foot_view_item_tv);
        }
    }

    //添加数据
    public void addItem(List</string><string> newDatas) {
        //mTitles.add(position, data);
        //notifyItemInserted(position);
        newDatas.addAll(mTitles);
        mTitles.removeAll(mTitles);
        mTitles.addAll(newDatas);
        notifyDataSetChanged();
    }

    public void addMoreItem(List</string><string> newDatas) {
        mTitles.addAll(newDatas);
        notifyDataSetChanged();
    }

    /**
     * //上拉加载更多
     * PULLUP_LOAD_MORE=0;
     *  //正在加载中
     * LOADING_MORE=1;
     * //加载完成已经没有更多数据了
     * NO_MORE_DATA=2;
     * @param status
     */

    public void changeMoreStatus(int status){
        load_more_status=status;
        notifyDataSetChanged();
    }
}

同时该Adaper中我还定义一个changeMoreStatus()方法和两个字符串常量可以来进行修改FootView中字符串提醒文本的。

7.整体Activity中还是设置监听RecyclerView的滚动事件.代码和第一个例子差不多,不过RecyclerView需要设置这边的Adapter了,demo_recycler.setAdapter(adapter = new RefreshFootAdapter(this));

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
demo_recycler.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == adapter.getItemCount()) {
                    adapter.changeMoreStatus(RefreshFootAdapter.LOADING_MORE);
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            List</string><string> newDatas = new ArrayList</string><string>();
                            for (int i = 0; i < 5; i++) {
                                int index = i + 1;
                                newDatas.add("more item" + index);
                            }
                            adapter.addMoreItem(newDatas);
                            adapter.changeMoreStatus(RefreshFootAdapter.PULLUP_LOAD_MORE);
                        }
                    }, 2500);
                }
            }
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
            }
        });

查看代码之后,大家肯定也发现在onScrollStateChanged()方法中,Adapter.addMoreItem()和Adapter.changeMoreStatus()方法内部都调用了notifyDataSetChanged()方法,也就是说这边刷新了两次,针对这个问题,大家可以把第二个方法放入到addMoreItem()内部去,调用一次刷新操作接口。同时也可以自己按照实际需求进行优化。

8.运行效果如下:

(六).最后总结

今天我们通过SwipeRefreshLayout和RecyclerView结合以及改造Adapter方式实现了RecyclerView的下拉刷新和上拉加载更多的效果。

本次具体实例注释过的全部代码已经上传到FastDev4Android项目中了。同时欢迎大家去Github站点进行clone或者下载浏览:

https://github.com/jiangqqlmj/FastDev4Android 同时欢迎大家star和fork整个开源快速开发框架项目~

尊重原创,转载请注明:From Sky丶清(http://www.lcode.org/) 侵权必究!

关注我的订阅号(codedev123),每天分享移动开发技术(Android/IOS),项目管理以及博客文章!(欢迎关注,第一时间推送精彩文章)

关注我的微博,可以获得更多精彩内容