一、概述

本篇博客介绍的是怎样使用SwipeRefreshLayout和RecyclerView实现高仿简书Android端的下拉刷新和上拉载入很多其它的效果。

依据效果图能够发现,本案例实现了例如以下效果:

  • 第一次进入页面显示SwipeRefreshLayout的下拉刷新效果
  • 当内容铺满屏幕时,向下滑动显示“载入中…”效果并载入很多其它数据
  • 当SwipeRefreshLayout正在下拉刷新时,将屏蔽载入很多其它操作
  • 当载入很多其它数据时,屏蔽有可能的反复的上拉操作
  • 当向上滑动RecyclerView时。隐藏Toolbar以获得更好的用户体验

二、代码实现

  • MainActivity
  1. package com.leohan.refresh;
  2. import android.os.Bundle;
  3. import android.os.Handler;
  4. import android.support.v4.widget.SwipeRefreshLayout;
  5. import android.support.v7.app.AppCompatActivity;
  6. import android.support.v7.widget.LinearLayoutManager;
  7. import android.support.v7.widget.RecyclerView;
  8. import android.support.v7.widget.Toolbar;
  9. import android.util.Log;
  10. import android.view.View;
  11. import java.util.ArrayList;
  12. import java.util.HashMap;
  13. import java.util.List;
  14. import java.util.Map;
  15. import butterknife.ButterKnife;
  16. import butterknife.InjectView;
  17. /**
  18. * @author Leo
  19. */
  20. public class MainActivity extends AppCompatActivity {
  21. @InjectView(R.id.toolbar)
  22. Toolbar toolbar;
  23. @InjectView(R.id.recyclerView)
  24. RecyclerView recyclerView;
  25. @InjectView(R.id.SwipeRefreshLayout)
  26. SwipeRefreshLayout swipeRefreshLayout;
  27. boolean isLoading;
  28. private List<Map<String, Object>> data = new ArrayList<>();
  29. private MyAdapter adapter = new MyAdapter(this, data);
  30. private Handler handler = new Handler();
  31. @Override
  32. public void onCreate(Bundle savedInstanceState) {
  33. super.onCreate(savedInstanceState);
  34. setContentView(R.layout.activity_notice);
  35. ButterKnife.inject(this);
  36. initView();
  37. initData();
  38. }
  39. public void initView() {
  40. setSupportActionBar(toolbar);
  41. toolbar.setTitle(R.string.notice);
  42. toolbar.setNavigationOnClickListener(new View.OnClickListener() {
  43. @Override
  44. public void onClick(View v) {
  45. finish();
  46. }
  47. });
  48. swipeRefreshLayout.setColorSchemeResources(R.color.blueStatus);
  49. swipeRefreshLayout.post(new Runnable() {
  50. @Override
  51. public void run() {
  52. swipeRefreshLayout.setRefreshing(true);
  53. }
  54. });
  55. swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
  56. @Override
  57. public void onRefresh() {
  58. handler.postDelayed(new Runnable() {
  59. @Override
  60. public void run() {
  61. data.clear();
  62. getData();
  63. }
  64. }, 2000);
  65. }
  66. });
  67. final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
  68. recyclerView.setLayoutManager(layoutManager);
  69. recyclerView.setAdapter(adapter);
  70. recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
  71. @Override
  72. public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
  73. super.onScrollStateChanged(recyclerView, newState);
  74. Log.d("test", "StateChanged = " + newState);
  75. }
  76. @Override
  77. public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
  78. super.onScrolled(recyclerView, dx, dy);
  79. Log.d("test", "onScrolled");
  80. int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
  81. if (lastVisibleItemPosition + 1 == adapter.getItemCount()) {
  82. Log.d("test", "loading executed");
  83. boolean isRefreshing = swipeRefreshLayout.isRefreshing();
  84. if (isRefreshing) {
  85. adapter.notifyItemRemoved(adapter.getItemCount());
  86. return;
  87. }
  88. if (!isLoading) {
  89. isLoading = true;
  90. handler.postDelayed(new Runnable() {
  91. @Override
  92. public void run() {
  93. getData();
  94. Log.d("test", "load more completed");
  95. isLoading = false;
  96. }
  97. }, 1000);
  98. }
  99. }
  100. }
  101. });
  102. //加入点击事件
  103. adapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
  104. @Override
  105. public void onItemClick(View view, int position) {
  106. Log.d("test", "item position = " + position);
  107. }
  108. @Override
  109. public void onItemLongClick(View view, int position) {
  110. }
  111. });
  112. }
  113. public void initData() {
  114. handler.postDelayed(new Runnable() {
  115. @Override
  116. public void run() {
  117. getData();
  118. }
  119. }, 1500);
  120. }
  121. /**
  122. * 获取測试数据
  123. */
  124. private void getData() {
  125. for (int i = 0; i < 6; i++) {
  126. Map<String, Object> map = new HashMap<>();
  127. data.add(map);
  128. }
  129. adapter.notifyDataSetChanged();
  130. swipeRefreshLayout.setRefreshing(false);
  131. adapter.notifyItemRemoved(adapter.getItemCount());
  132. }
  133. }
  • RecyclerViewAdapter
  1. package com.leohan.refresh;
  2. import android.content.Context;
  3. import android.support.v7.widget.RecyclerView.Adapter;
  4. import android.support.v7.widget.RecyclerView.ViewHolder;
  5. import android.view.LayoutInflater;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. import android.widget.TextView;
  9. import java.util.List;
  10. public class RecyclerViewAdapter extends Adapter<ViewHolder> {
  11. private static final int TYPE_ITEM = 0;
  12. private static final int TYPE_FOOTER = 1;
  13. private Context context;
  14. private List data;
  15. public interface OnItemClickListener {
  16. void onItemClick(View view, int position);
  17. void onItemLongClick(View view, int position);
  18. }
  19. private OnItemClickListener onItemClickListener;
  20. public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
  21. this.onItemClickListener = onItemClickListener;
  22. }
  23. @Override
  24. public int getItemCount() {
  25. return data.size() == 0 ?
  26. 0 : data.size() + 1;
  27. }
  28. @Override
  29. public int getItemViewType(int position) {
  30. if (position + 1 == getItemCount()) {
  31. return TYPE_FOOTER;
  32. } else {
  33. return TYPE_ITEM;
  34. }
  35. }
  36. public RecyclerViewAdapter(Context context, List data) {
  37. this.context = context;
  38. this.data = data;
  39. }
  40. @Override
  41. public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  42. if (viewType == TYPE_ITEM) {
  43. View view = LayoutInflater.from(context).inflate(R.layout.item_notice, parent,
  44. false);
  45. return new ItemViewHolder(view);
  46. } else if (viewType == TYPE_FOOTER) {
  47. View view = LayoutInflater.from(context).inflate(R.layout.item_foot, parent,
  48. false);
  49. return new FootViewHolder(view);
  50. }
  51. return null;
  52. }
  53. @Override
  54. public void onBindViewHolder(final ViewHolder holder, int position) {
  55. if (holder instanceof ItemViewHolder) {
  56. //holder.tv.setText(data.get(position));
  57. if (onItemClickListener != null) {
  58. holder.itemView.setOnClickListener(new View.OnClickListener() {
  59. @Override
  60. public void onClick(View v) {
  61. int position = holder.getLayoutPosition();
  62. onItemClickListener.onItemClick(holder.itemView, position);
  63. }
  64. });
  65. holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
  66. @Override
  67. public boolean onLongClick(View v) {
  68. int position = holder.getLayoutPosition();
  69. onItemClickListener.onItemLongClick(holder.itemView, position);
  70. return false;
  71. }
  72. });
  73. }
  74. }
  75. }
  76. static class ItemViewHolder extends ViewHolder {
  77. TextView tv;
  78. public ItemViewHolder(View view) {
  79. super(view);
  80. tv = (TextView) view.findViewById(R.id.tv_date);
  81. }
  82. }
  83. static class FootViewHolder extends ViewHolder {
  84. public FootViewHolder(View view) {
  85. super(view);
  86. }
  87. }
  88. }
  • item_base.xml
  1. <?
  2. xml version="1.0" encoding="utf-8"?
  3. >
  4. <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
  5. xmlns:app="http://schemas.android.com/apk/res-auto"
  6. android:layout_width="match_parent"
  7. android:layout_height="wrap_content"
  8. android:layout_marginLeft="@dimen/margin_10"
  9. android:layout_marginRight="@dimen/margin_10"
  10. android:foreground="?android:attr/selectableItemBackgroundBorderless"
  11. android:layout_marginTop="6dp"
  12. android:orientation="vertical"
  13. app:cardBackgroundColor="@color/line"
  14. app:cardPreventCornerOverlap="true"
  15. app:cardUseCompatPadding="true"
  16. app:contentPadding="6dp">
  17. <LinearLayout
  18. android:layout_width="match_parent"
  19. android:layout_height="match_parent"
  20. android:orientation="vertical">
  21. <TextView
  22. android:id="@+id/tv_date"
  23. style="@style/NormalTextView"
  24. android:layout_width="wrap_content"
  25. android:layout_height="wrap_content"
  26. android:text="2015-12-11 12:00" />
  27. <android.support.v7.widget.CardView
  28. android:layout_width="match_parent"
  29. android:layout_height="match_parent"
  30. android:orientation="vertical"
  31. app:cardBackgroundColor="@color/white"
  32. app:cardPreventCornerOverlap="true"
  33. app:cardUseCompatPadding="true"
  34. app:contentPadding="10dp">
  35. <TextView
  36. android:id="@+id/tv_title"
  37. style="@style/SmallGreyTextView"
  38. android:layout_width="wrap_content"
  39. android:layout_height="wrap_content"
  40. android:ellipsize="end"
  41. android:maxLines="2"
  42. android:text="视线好转,0729出口开通,0621进口开通。视线好转,0729出口开通,0621进口开通。" />
  43. </android.support.v7.widget.CardView>
  44. </LinearLayout>
  45. </android.support.v7.widget.CardView>
  • item_foot.xml
  1. <?
  2. xml version="1.0" encoding="utf-8"?>
  3. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="40dp"
  7. android:gravity="center"
  8. android:orientation="horizontal"
  9. >
  10. <ProgressBar
  11. android:layout_marginRight="6dp"
  12. android:id="@+id/progressBar"
  13. style="?android:attr/progressBarStyleSmall"
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:layout_gravity="center" />
  17. <TextView
  18. style="@style/SmallGreyTextView"
  19. android:layout_width="wrap_content"
  20. android:layout_height="wrap_content"
  21. android:layout_gravity="center"
  22. android:text="@string/loading" />
  23. </LinearLayout>

三、代码分析

  • 上拉载入很多其它数据通过监听RecyclerView的滚动事件RecyclerView.OnScrollListener()实现的。它提供了两个方法:
  1. /**
  2. * 当RecyclerView的滑动状态改变时触发
  3. */
  4. public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
  5. /**
  6. * 当RecyclerView滑动时触发
  7. * 相似点击事件的MotionEvent.ACTION_MOVE
  8. */
  9. public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
  • RecyclerView的滑动状态有例如以下三种:
  1. /**
  2. * The RecyclerView is not currently scrolling.
  3. * 手指离开屏幕
  4. */
  5. public static final int SCROLL_STATE_IDLE = 0;
  6. /**
  7. * The RecyclerView is currently being dragged by outside input such as user touch input.
  8. * 手指触摸屏幕
  9. */
  10. public static final int SCROLL_STATE_DRAGGING = 1;
  11. /**
  12. * The RecyclerView is currently animating to a final position while not under
  13. * outside control.
  14. * 手指加速滑动并放开,此时滑动状态伴随SCROLL_STATE_IDLE
  15. */
  16. public static final int SCROLL_STATE_SETTLING = 2;
  • 因为简书APP的上拉载入很多其它的是在滑动到最后一个item时自己主动触发的,与手指是否在屏幕上无关。即与滑动状态无关。

    因此,实现这样的效果仅仅须要在public void onScrolled(RecyclerView recyclerView, int dx, int dy) 方法中操作,无需关注当时的滑动状态:

  1. @Override
  2. public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
  3. super.onScrolled(recyclerView, dx, dy);
  4. Log.d("test", "onScrolled");
  5. int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
  6. if (lastVisibleItemPosition + 1 == adapter.getItemCount()) {
  7. Log.d("test", "loading executed");
  8. boolean isRefreshing = swipeRefreshLayout.isRefreshing();
  9. if (isRefreshing) {
  10. adapter.notifyItemRemoved(adapter.getItemCount());
  11. return;
  12. }
  13. if (!isLoading) {
  14. isLoading = true;
  15. handler.postDelayed(new Runnable() {
  16. @Override
  17. public void run() {
  18. getData();
  19. Log.d("test", "load more completed");
  20. isLoading = false;
  21. }
  22. }, 1000);
  23. }
  24. }
  25. }
  • 假设要实现当且仅当滑动到最后一项而且手指上拉抛出时才运行上拉载入很多其它效果的话。须要配合onScrollStateChanged(RecyclerView recyclerView, int newState的使用,能够将代码改为:
  1. @Override
  2. public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
  3. super.onScrollStateChanged(recyclerView, newState);
  4. Log.d("test", "StateChanged = " + newState);
  5. if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition + 1 == adapter.getItemCount()) {
  6. Log.d("test", "loading executed");
  7. boolean isRefreshing = swipeRefreshLayout.isRefreshing();
  8. if (isRefreshing) {
  9. adapter.notifyItemRemoved(adapter.getItemCount());
  10. return;
  11. }
  12. if (!isLoading) {
  13. isLoading = true;
  14. handler.postDelayed(new Runnable() {
  15. @Override
  16. public void run() {
  17. getData();
  18. Log.d("test", "load more completed");
  19. isLoading = false;
  20. }
  21. }, 1000);
  22. }
  23. }
  24. }
  25. @Override
  26. public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
  27. super.onScrolled(recyclerView, dx, dy);
  28. Log.d("test", "onScrolled");
  29. lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
  30. }
  • 载入很多其它的效果能够通过item_foot.xml自己定义,滑动到最后一项时显示该item并运行载入很多其它。当载入数据完成时须要将该item移除掉
  1. adapter.notifyItemRemoved(adapter.getItemCount());

以下的代码就是RecyclerView的多个item布局的实现方法:

  1. @Override
  2. public int getItemCount() {
  3. return data.size() == 0 ? 0 : data.size() + 1;
  4. }
  5. @Override
  6. public int getItemViewType(int position) {
  7. if (position + 1 == getItemCount()) {
  8. return TYPE_FOOTER;
  9. } else {
  10. return TYPE_ITEM;
  11. }
  12. }
  13. @Override
  14. public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  15. if (viewType == TYPE_ITEM) {
  16. View view = LayoutInflater.from(context).inflate(R.layout.item_base, parent,
  17. false);
  18. return new ItemViewHolder(view);
  19. } else if (viewType == TYPE_FOOTER) {
  20. View view = LayoutInflater.from(context).inflate(R.layout.item_foot, parent,
  21. false);
  22. return new FootViewHolder(view);
  23. }
  24. return null;
  25. }

该案例源代码:https://github.com/leoleohan/RefreshDemo,欢迎Star、Fork。

使用SwipeRefreshLayout和RecyclerView实现仿“简书”下拉刷新和上拉载入很多其它的更多相关文章

  1. Android 5.X新特性之为RecyclerView添加下拉刷新和上拉加载及SwipeRefreshLayout实现原理

    RecyclerView已经写过两篇文章了,分别是Android 5.X新特性之RecyclerView基本解析及无限复用 和 Android 5.X新特性之为RecyclerView添加Header ...

  2. Android实现RecyclerView的下拉刷新和上拉载入很多其它

    需求 先上效果图, Material Design风格的下拉刷新和上拉载入很多其它. 源代码地址(欢迎star) https://github.com/studychen/SeeNewsV2 假设对于 ...

  3. RecyclerView下拉刷新和上拉加载更多实现

    RecyclerView下拉刷新和上拉加载更多实现 转 https://www.jianshu.com/p/4ea7c2d95ecf   在Android开发中,RecyclerView算是使用频率非 ...

  4. 实现RecyclerView下拉刷新和上拉加载更多以及RecyclerView线性、网格、瀑布流效果演示

    实现RecyclerView下拉刷新和上拉加载更多以及RecyclerView线性.网格.瀑布流效果演示 效果预览 实例APP 小米应用商店 使用方法 build.gradle文件 dependenc ...

  5. 手把手教你实现RecyclerView的下拉刷新和上拉加载更多

    手把手教你实现RecyclerView的下拉刷新和上拉加载更多     版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https:// ...

  6. Android 实现下拉刷新和上拉加载更多的RECYCLERVIEW和SCROLLVIEW

    PullRefreshRecyclerView.java /** * 类说明:下拉刷新上拉加载更多的RecyclerView * Author: gaobaiq * Date: 2016/5/9 18 ...

  7. 给RecyclerView最纯粹的下拉刷新和上拉加载更多

    转自 http://blog.csdn.net/jerrywu145/article/details/52225898 http://www.jianshu.com/p/3bf125b4917d

  8. RecyclerView下拉刷新上拉加载(二)

    listview下拉刷新上拉加载扩展(一) http://blog.csdn.net/baiyuliang2013/article/details/50252561 listview下拉刷新上拉加载扩 ...

  9. RecyclerView下拉刷新上拉加载(一)

    listview下拉刷新上拉加载扩展(一) http://blog.csdn.net/baiyuliang2013/article/details/50252561 listview下拉刷新上拉加载扩 ...

随机推荐

  1. mysql 更改字符集

    Windows: 安装目录下新建my.ini文件,输入一下内容 [mysqld]#修改服务器端默认字符编码格式为utf8character-set-server = utf8 [client]#修改客 ...

  2. vs2017 创建C#类时添加文件头

    C#类模板地址:C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\ItemTemplates\C ...

  3. Vue的特性

    1.数据驱动视图 <div id="app"> <p> {{ message }}<p> </div> var app = new ...

  4. du 命令计算隐藏文件夹或文件

    du -sh * .[^.]*

  5. 调用微信扫一扫功能,踩坑'invalid signature'

    在vue项目中,调用微信扫一扫功能,在安卓系统下完全正常,ios系统下却报错'invalid signature'的错误,这可能令许多小伙伴困惑,经过查询大量博客相关资料,才找到了解决的方法. 原因: ...

  6. idea之快速查看类所在jar包

  7. git 的 基础操作及使用

    /* git svn版本控制器 */ /*git把文件对应的储存空间分为三个区: 1.工作区 2.缓存区 3.历史区 直接操作文件,不做add时,咱们是在工作区做的修改 右键 git bash her ...

  8. 如何同步iframe与嵌入内容的高度

    最近频繁的做一些通过iframe在a页面嵌入b页面需求.总结下来,有以下问题需要解决 1.如何同步iframe与嵌入内容的高度 2.将b页面载入到a页面后,如何隐藏掉b页面上的元素,如左导航,顶部导航 ...

  9. 字符、散列、模拟--P1055 ISBN号码

    题目描述 每一本正式出版的图书都有一个ISBN号码与之对应,ISBN码包括9位数字.1位识别码和3位分隔符,其规定格式如x-xxx-xxxxx-x,其中符号-就是分隔符(键盘上的减号),最后一位是识别 ...

  10. Daydreaming Stockbroker(2016 NCPC 贪心)

    题目: Gina Reed, the famous stockbroker, is having a slow day at work, and between rounds of solitaire ...