Android 实现RecyclerView的下拉刷新和上拉加载

来源:互联网 发布:神仙劫法座进阶数据 编辑:程序博客网 时间:2024/05/19 17:48

需求

先上效果图, Material Design风格的下拉刷新和上拉加载更多。

blog.csdn.net/never_cxb

源码地址(欢迎star) https://github.com/studychen/SeeNewsV2

如果对于RecyclerView还不熟悉,参见这篇 Android Material Design学习之RecyclerView代替 ListView

本文链接 http://blog.csdn.net/never_cxb/article/details/50759109 转载请注明出处

下拉刷新

效果图

上拉时候会有一个圆形动画,刷新加载数据。

http://blog.csdn.net/never_cxb/

思路

使用Google官方的Android.support.v4.widget.SwipeRefreshLayout

列表RecyclerView的xml布局

给原来的RecyclerView增加一个SwipeRefreshLayout的父布局

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="?attr/listBackground"    android:orientation="vertical">    <android.support.v4.widget.SwipeRefreshLayout        android:id="@+id/swiperefreshlayout"        android:layout_width="match_parent"        android:layout_height="wrap_content">        <!-- 新闻列表展示-->        <android.support.v7.widget.RecyclerView            android:id="@+id/rcv_article_origin"            android:layout_width="match_parent"            android:layout_height="match_parent" />    </android.support.v4.widget.SwipeRefreshLayout></LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

java代码

这儿有几个注意点: 
setColorSchemeColors()可以控制圆形动画的颜色,最多设置4个。

setOnRefreshListener 设置下拉刷新的回调事件。

下拉刷新后,使用 AsyncTask 根据当前RecyclerView中首个Item的id来加载更多数据。

数据加载完毕后,使用setRefreshing(false);取消动画。

如果刷新后得到0条记录,提示没有数据更新。若得到>0条数据,把数据加到RecyclerView中

mSwipeRefreshLayout.setColorSchemeColors(Color.RED, Color.BLUE);mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {    @Override    public void onRefresh() {        new MoreArticleTask().execute(mAdapter.getTopArticleId());    }});// Integer 是输入参数// 得到比某个id大的新闻数组class MoreArticleTask extends AsyncTask<Integer, Void, List<SimpleArticleItem>> {    @Override    protected List<SimpleArticleItem> doInBackground(Integer... params) {        try {            Thread.sleep(1500);        } catch (InterruptedException e) {            e.printStackTrace();        }        return getMoreById(mColumn, params[0]);    }    @Override    protected void onPostExecute(List<SimpleArticleItem> simpleArticleItems) {        super.onPostExecute(simpleArticleItems);        if (mSwipeRefreshLayout != null) {            mSwipeRefreshLayout.setRefreshing(false);        }        //没有新的数据,提示消息        if (simpleArticleItems == null || simpleArticleItems.size() == 0) {            Snackbar.with(mActivity.getApplicationContext()) // context                    .text(mActivity.getResources().getString(R.string.list_more_data)) // text to display                    .duration(Snackbar.SnackbarDuration.LENGTH_SHORT) // make it shorter                    .show(mActivity); // activity where it is displayed        } else {            mArticleList.addAll(simpleArticleItems);            mAdapter.notifyDataSetChanged();        }    }}
  • 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
  • 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

首次进入页面就显示加载ing的动画

效果如下:

http://blog.csdn.net/never_cxb/

直接使用 mSwipeRefreshLayout.setRefreshing(true);加载动画初始状态并不显示。

看了 http://stackoverflow.com/questions/26858692/swiperefreshlayout-setrefreshing-not-showing-indicator-initially 的解答, 
改用下面的代码,初始状态就有加载动画显示。

mSwipeRefreshLayout.post(new Runnable() {    @Override    public void run() {        mSwipeRefreshLayout.setRefreshing(true);        new MoreArticleTask().execute(mAdapter.getTopArticleId());    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上拉加载更多

RecyclerView 展示列表是个通用需求,那么当数据较多时候,如何实现分页加载呢?

比如笔者的项目中,先加载15条新闻,当滑动到底部时候,再加载15条。

效果图

http://blog.csdn.net/never_cxb/

思路

在RecyclerView底部增加一个Footer的ViewHolder,数据加载完毕取出底部的ViewHolder。

底部Footer的xml布局文件

很简单,就是一个ProgressBar。本项目为了Material Design,使用了一个开源ProgressBar https://github.com/Todd-Davies/ProgressWheel,你也可以使用原生的ProgressBar。

<?xml version="1.0" encoding="utf-8"?><!--上拉加载更多 RecyclerView 底部--><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:wheel="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <com.pnikosis.materialishprogress.ProgressWheel        android:id="@+id/rcv_load_more"        android:layout_width="40dp"        android:layout_height="40dp"        android:layout_gravity="center_horizontal"        wheel:matProg_barColor="@color/accent"        wheel:matProg_progressIndeterminate="true" /></LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

底部Footer的ViewHolder

/** * 底部加载更多 */class FooterViewHolder extends RecyclerView.ViewHolder {    @InjectView(R.id.rcv_load_more)    ProgressWheel rcvLoadMore;    public FooterViewHolder(View itemView) {        super(itemView);        ButterKnife.inject(this, itemView);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在Fragment中给RecyclerView增加滑动监听

layoutManager.getItemCount() 可以得到当前RecyclerView中Item的总数目

layoutManager.findLastVisibleItemPosition() 得到最后一个可见的Item的位置position

如果 totalItemCount < (lastVisibleItem + Constant.VISIBLE_THRESHOLD)

比如一共15个Item,当前到达第13个, Constant.VISIBLE_THRESHOLD设为3 
总数小于最后一个+阈值,就加载更多新闻数据,同时把loading标记为 true 。

private boolean loading = false;@Overridepublic void onActivityCreated(@Nullable Bundle savedInstanceState) {    super.onActivityCreated(savedInstanceState);    mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity));    mAdapter = new OriginArticleAdapter(mActivity, mArticleList);    mRecyclerView.setAdapter(mAdapter);    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {        @Override        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {            super.onScrolled(recyclerView, dx, dy);            LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();            int totalItemCount = layoutManager.getItemCount();            int lastVisibleItem = layoutManager.findLastVisibleItemPosition();            if (!loading && totalItemCount < (lastVisibleItem + Constant.VISIBLE_THRESHOLD)) {                new ArticleTask(mActivity).execute(mAdapter.getBottomArticleId());                loading = true;            }        }    });}
  • 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
  • 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

在Fragment中控制底部Footer

我们依然使用AsyncTask。

注意 onPreExecute() 给 mArticleList 增加了一个null标记Footer,如果是第一次进入页面(mArticleList为空)不需要加Footer。

当数据加载完毕后,用 mArticleList.remove(mArticleList.size() - 1);把最下面的Footer删除。

再用 mArticleList.addAll(moreArticles); 增加新增的新闻数据,

并用 mAdapter.notifyDataSetChanged(); 通知 RecyclerView.Adapter 有数据改变。

private List<SimpleArticleItem> mArticleList = new ArrayList<SimpleArticleItem>();class ArticleTask extends AsyncTask<Integer, Void, List<SimpleArticleItem>> {    private Context mContext;    public ArticleTask(Context context) {        mContext = context;    }    /**     * Runs on the UI thread before {@link #doInBackground}.     */    @Override    protected void onPreExecute() {        super.onPreExecute();        if (mArticleList != null && mArticleList.size() > 0) {            mArticleList.add(null);            // notifyItemInserted(int position),这个方法是在第position位置            // 被插入了一条数据的时候可以使用这个方法刷新,            // 注意这个方法调用后会有插入的动画,这个动画可以使用默认的,也可以自己定义。            mAdapter.notifyItemInserted(mArticleList.size() - 1);        }    }    /**     * @param params 偏移量 aid     * @return     */    @Override    protected List<SimpleArticleItem> doInBackground(Integer... params) {        try {            Thread.sleep(1500);        } catch (InterruptedException e) {            e.printStackTrace();        }        return getArticleList(mColumn, params[0]);    }    @Override    protected void onPostExecute(final List<SimpleArticleItem> moreArticles) {        // 新增新闻数据        super.onPostExecute(moreArticles);        if (mArticleList.size() == 0) {            mArticleList.addAll(moreArticles);            mAdapter.notifyDataSetChanged();        } else {            //删除 footer            mArticleList.remove(mArticleList.size() - 1);            mArticleList.addAll(moreArticles);            mAdapter.notifyDataSetChanged();            loading = false;        }    }}
  • 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
  • 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

Override RecyclerView.Adapter 中的 getItemViewType

extends RecyclerView.Adapter<RecyclerView.ViewHolder> 如果是null,返回Footer的Type;否则,返回正常新闻的Type。

本文,为了UI美观,把新闻分为两种:大于3幅图片 、小于3幅图片,相应返回不同的Type和ViewHolder。

public final static int TYPE_MULTI_IMAGES = 2; // 多个图片的文章public final static int TYPE_FOOTER = 3;//底部--往往是loading_morepublic final static int TYPE_NORMAL = 1; // 正常的一条文章@Overridepublic int getItemViewType(int position) {    SimpleArticleItem article = articleList.get(position);    if (article == null) {        return TYPE_FOOTER;    } else if (article.getImageUrls().length >= 3) {        return TYPE_MULTI_IMAGES;    } else {        return TYPE_NORMAL;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Override RecyclerView.Adapter 中的 onCreateViewHolder

在该方法中利用 switch (viewType) 返回不同的xml布局文件及ViewHolder

@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {    RecyclerView.ViewHolder vh;    View view;    switch (viewType) {        //其他无法处理的情况使用viewholder_article_simple        default:        case TYPE_NORMAL:            view = mLayoutInflater.inflate(                    R.layout.item_article_normal, parent, false);            vh = new ItemArticleViewHolder(view);            return vh;        case TYPE_FOOTER:            view = mLayoutInflater.inflate(                    R.layout.recyclerview_footer, parent, false);            vh = new FooterViewHolder(view);            return vh;        case TYPE_MULTI_IMAGES:            view = mLayoutInflater.inflate(                    R.layout.item_article_multi_images, parent, false);            vh = new MultiImagesViewHolder(view);            return vh;    }}
  • 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
  • 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

Override RecyclerView.Adapter 中的 onBindViewHolder

利用 instanceof 判断是何种类型的ViewHolder,来给控件赋值、绑定数据。

注意:因为Footer是用null表示的,为了防止NullPointerException, 
我们先用if把FooterViewHolder的情况处理了。

@Overridepublic void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {    //这时候 article是 null,先把 footer 处理了    if (holder instanceof FooterViewHolder) {        ((FooterViewHolder) holder).rcvLoadMore.spin();        return;    }    SimpleArticleItem article = articleList.get(position);    String[] imageUrls = article.getImageUrls();    if (holder instanceof ItemArticleViewHolder) {        ItemArticleViewHolder newHolder = (ItemArticleViewHolder) holder;        newHolder.rcvArticleTitle.setText(article.getTitle());        newHolder.rcvArticleDate.setText(article.getPublishDate());        //当图片小于3张时候 选取第1张图片        if (imageUrls.length > 0) {            newHolder.rcvArticlePhoto.setImageURI(Uri.parse(Constant.BUCKET_HOST_NAME                    + imageUrls[0]));        } else {            newHolder.rcvArticlePhoto.setImageURI(Uri.parse(ApiUrl.randomImageUrl(article.getId())));        }        //注意这个阅读次数是 int 类型,需要转化为 String 类型        newHolder.rcvArticleReadtimes.setText("浏览: " + article.getReadTimes());        newHolder.rcvArticleSummary.setText(article.getSummary());    } else {        MultiImagesViewHolder newHolder = (MultiImagesViewHolder) holder;        newHolder.articleTitle.setText(article.getTitle());        newHolder.articlePic1.setImageURI(Uri.parse(Constant.BUCKET_HOST_NAME + imageUrls[0]));        newHolder.articlePic2.setImageURI(Uri.parse(Constant.BUCKET_HOST_NAME + imageUrls[1]));        newHolder.articlePic3.setImageURI(Uri.parse(Constant.BUCKET_HOST_NAME + imageUrls[2]));        newHolder.countPics.setText("图片: " + imageUrls.length);        newHolder.countRead.setText("浏览: " + article.getReadTimes());    }}
  • 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
  • 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

下拉刷新和上拉加载更多的源码地址(欢迎star) https://github.com/studychen/SeeNewsV2

本文链接 http://blog.csdn.net/never_cxb/article/details/50759109 转载请注明出处

一些坑

注意给RecyclerView setLayoutManager,不然RecyclerView可能不显示

// 1. get a reference to recyclerViewRecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);// 2. set layoutMangerrecyclerView.setLayoutManager(new LinearLayoutManager(this));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

参考文章

  • LoadMore RecyclerView with progress bar showing at bottom
  • Android Material Design学习之RecyclerView代替 ListView
  • Swipe/Pull to Refresh for Android RecyclerView (or any other vertically scrolling view)
阅读全文
0 0
原创粉丝点击