手把手教你实现Android RecyclerView上拉加载功能
摘要
什么是上拉加载

实现思路
一、XML的实现
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <android.support.v4.widget.SwipeRefreshLayout
- android:id="@+id/refreshLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <android.support.v7.widget.RecyclerView
- android:id="@+id/recyclerView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
- </android.support.v4.widget.SwipeRefreshLayout>
- </LinearLayout>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <TextView
- android:id="@+id/tv"
- android:layout_width="match_parent"
- android:layout_height="120dp"
- android:background="@android:color/holo_blue_dark"
- android:gravity="center"
- android:textSize="30sp"
- android:textColor="#ffffff"
- android:text="11"
- android:layout_marginBottom="1dp"/>
- </LinearLayout>
看到我们效果图都知道,在我们上拉时,还有一个提示的条目,我定义为 footview.xml:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <TextView
- android:id="@+id/tips"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:padding="30dp"
- android:textSize="15sp"
- android:layout_marginBottom="1dp"/>
- </LinearLayout>
二、初始化SwipeRefreshLayout
- private void initRefreshLayout() {
- refreshLayout.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);
- refreshLayout.setOnRefreshListener(this);
- }
- @Override
- public void onRefresh() {
- // 设置可见
- refreshLayout.setRefreshing(true);
- // 重置adapter的数据源为空
- adapter.resetDatas();
- // 获取第第0条到第PAGE_COUNT(值为10)条的数据
- updateRecyclerView(0, PAGE_COUNT);
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- // 模拟网络加载时间,设置不可见
- refreshLayout.setRefreshing(false);
- }
- }, 1000);
- }
三、定义RecyclerView的Adapter
- public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
- private List<String> datas; // 数据源
- private Context context; // 上下文Context
- private int normalType = 0; // 第一种ViewType,正常的item
- private int footType = 1; // 第二种ViewType,底部的提示View
- private boolean hasMore = true; // 变量,是否有更多数据
- private boolean fadeTips = false; // 变量,是否隐藏了底部的提示
- private Handler mHandler = new Handler(Looper.getMainLooper()); //获取主线程的Handler
- public MyAdapter(List<String> datas, Context context, boolean hasMore) {
- // 初始化变量
- this.datas = datas;
- this.context = context;
- this.hasMore = hasMore;
- }
- // 获取条目数量,之所以要加1是因为增加了一条footView
- @Override
- public int getItemCount() {
- return datas.size() + 1;
- }
- // 自定义方法,获取列表中数据源的最后一个位置,比getItemCount少1,因为不计上footView
- public int getRealLastPosition() {
- return datas.size();
- }
- // 根据条目位置返回ViewType,以供onCreateViewHolder方法内获取不同的Holder
- @Override
- public int getItemViewType(int position) {
- if (position == getItemCount() - 1) {
- return footType;
- } else {
- return normalType;
- }
- }
- // 正常item的ViewHolder,用以缓存findView操作
- class NormalHolder extends RecyclerView.ViewHolder {
- private TextView textView;
- public NormalHolder(View itemView) {
- super(itemView);
- textView = (TextView) itemView.findViewById(R.id.tv);
- }
- }
- // // 底部footView的ViewHolder,用以缓存findView操作
- class FootHolder extends RecyclerView.ViewHolder {
- private TextView tips;
- public FootHolder(View itemView) {
- super(itemView);
- tips = (TextView) itemView.findViewById(R.id.tips);
- }
- }
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- // 根据返回的ViewType,绑定不同的布局文件,这里只有两种
- if (viewType == normalType) {
- return new NormalHolder(LayoutInflater.from(context).inflate(R.layout.item, null));
- } else {
- return new FootHolder(LayoutInflater.from(context).inflate(R.layout.footview, null));
- }
- }
- @Override
- public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
- // 如果是正常的imte,直接设置TextView的值
- if (holder instanceof NormalHolder) {
- ((NormalHolder) holder).textView.setText(datas.get(position));
- } else {
- // 之所以要设置可见,是因为我在没有更多数据时会隐藏了这个footView
- ((FootHolder) holder).tips.setVisibility(View.VISIBLE);
- // 只有获取数据为空时,hasMore为false,所以当我们拉到底部时基本都会首先显示“正在加载更多...”
- if (hasMore == true) {
- // 不隐藏footView提示
- fadeTips = false;
- if (datas.size() > 0) {
- // 如果查询数据发现增加之后,就显示正在加载更多
- ((FootHolder) holder).tips.setText("正在加载更多...");
- }
- } else {
- if (datas.size() > 0) {
- // 如果查询数据发现并没有增加时,就显示没有更多数据了
- ((FootHolder) holder).tips.setText("没有更多数据了");
- // 然后通过延时加载模拟网络请求的时间,在500ms后执行
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- // 隐藏提示条
- ((FootHolder) holder).tips.setVisibility(View.GONE);
- // 将fadeTips设置true
- fadeTips = true;
- // hasMore设为true是为了让再次拉到底时,会先显示正在加载更多
- hasMore = true;
- }
- }, 500);
- }
- }
- }
- }
- // 暴露接口,改变fadeTips的方法
- public boolean isFadeTips() {
- return fadeTips;
- }
- // 暴露接口,下拉刷新时,通过暴露方法将数据源置为空
- public void resetDatas() {
- datas = new ArrayList<>();
- }
- // 暴露接口,更新数据源,并修改hasMore的值,如果有增加数据,hasMore为true,否则为false
- public void updateList(List<String> newDatas, boolean hasMore) {
- // 在原有的数据之上增加新数据
- if (newDatas != null) {
- datas.addAll(newDatas);
- }
- this.hasMore = hasMore;
- notifyDataSetChanged();
- }
- }
四、初始化RecyclerView
- private void initRecyclerView() {
- // 初始化RecyclerView的Adapter
- // 第一个参数为数据,上拉加载的原理就是分页,所以我设置常量PAGE_COUNT=10,即每次加载10个数据
- // 第二个参数为Context
- // 第三个参数为hasMore,是否有新数据
- adapter = new MyAdapter(getDatas(0, PAGE_COUNT), this, getDatas(0, PAGE_COUNT).size() > 0 ? true : false);
- mLayoutManager = new GridLayoutManager(this, 1);
- recyclerView.setLayoutManager(mLayoutManager);
- recyclerView.setAdapter(adapter);
- recyclerView.setItemAnimator(new DefaultItemAnimator());
- // 实现上拉加载重要步骤,设置滑动监听器,RecyclerView自带的ScrollListener
- recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- super.onScrollStateChanged(recyclerView, newState);
- // 在newState为滑到底部时
- if (newState == RecyclerView.SCROLL_STATE_IDLE) {
- // 如果没有隐藏footView,那么最后一个条目的位置就比我们的getItemCount少1,自己可以算一下
- if (adapter.isFadeTips() == false && lastVisibleItem + 1 == adapter.getItemCount()) {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- // 然后调用updateRecyclerview方法更新RecyclerView
- updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT);
- }
- }, 500);
- }
- // 如果隐藏了提示条,我们又上拉加载时,那么最后一个条目就要比getItemCount要少2
- if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- // 然后调用updateRecyclerview方法更新RecyclerView
- updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT);
- }
- }, 500);
- }
- }
- }
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- super.onScrolled(recyclerView, dx, dy);
- // 在滑动完成后,拿到最后一个可见的item的位置
- lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
- }
- });
- }
- // 上拉加载时调用的更新RecyclerView的方法
- private void updateRecyclerView(int fromIndex, int toIndex) {
- // 获取从fromIndex到toIndex的数据
- List<String> newDatas = getDatas(fromIndex, toIndex);
- if (newDatas.size() > 0) {
- // 然后传给Adapter,并设置hasMore为true
- adapter.updateList(newDatas, true);
- } else {
- adapter.updateList(null, false);
- }
- }
所以,Activity的完整代码如下:
- public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
- private SwipeRefreshLayout refreshLayout;
- private RecyclerView recyclerView;
- private List<String> list;
- private int lastVisibleItem = 0;
- private final int PAGE_COUNT = 10;
- private GridLayoutManager mLayoutManager;
- private MyAdapter adapter;
- private Handler mHandler = new Handler(Looper.getMainLooper());
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- initData();
- findView();
- initRefreshLayout();
- initRecyclerView();
- }
- private void initData() {
- list = new ArrayList<>();
- for (int i = 1; i <= 40; i++) {
- list.add("条目" + i);
- }
- }
- private void findView() {
- refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refreshLayout);
- recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
- }
- private void initRefreshLayout() {
- refreshLayout.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);
- refreshLayout.setOnRefreshListener(this);
- }
- private void initRecyclerView() {
- adapter = new MyAdapter(getDatas(0, PAGE_COUNT), this, getDatas(0, PAGE_COUNT).size() > 0 ? true : false);
- mLayoutManager = new GridLayoutManager(this, 1);
- recyclerView.setLayoutManager(mLayoutManager);
- recyclerView.setAdapter(adapter);
- recyclerView.setItemAnimator(new DefaultItemAnimator());
- recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- super.onScrollStateChanged(recyclerView, newState);
- if (newState == RecyclerView.SCROLL_STATE_IDLE) {
- if (adapter.isFadeTips() == false && lastVisibleItem + 1 == adapter.getItemCount()) {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT);
- }
- }, 500);
- }
- if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT);
- }
- }, 500);
- }
- }
- }
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- super.onScrolled(recyclerView, dx, dy);
- lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
- }
- });
- }
- private List<String> getDatas(final int firstIndex, final int lastIndex) {
- List<String> resList = new ArrayList<>();
- for (int i = firstIndex; i < lastIndex; i++) {
- if (i < list.size()) {
- resList.add(list.get(i));
- }
- }
- return resList;
- }
- private void updateRecyclerView(int fromIndex, int toIndex) {
- List<String> newDatas = getDatas(fromIndex, toIndex);
- if (newDatas.size() > 0) {
- adapter.updateList(newDatas, true);
- } else {
- adapter.updateList(null, false);
- }
- }
- @Override
- public void onRefresh() {
- refreshLayout.setRefreshing(true);
- adapter.resetDatas();
- updateRecyclerView(0, PAGE_COUNT);
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- refreshLayout.setRefreshing(false);
- }
- }, 1000);
- }
- }
后话
以上代码我是考虑到了更多的边界条件,所以在代码上会稍微多了一点,但是也不影响观看。大家也可以通过改变数据源的数量和PAGE_COUNT等来测试,每个人在具体使用上都会有不同的要求,所以基本代码我摆了出来,众口难调,更多的细节需要大家来优化,例如footView可以设置一个动画条,下拉刷新用其他样式替换原生的样式等,我想,这些对于学习完这篇文章的你来说,都会是简单的问题了。
Demo下载
Github下载:PullToLoadData-RecyclerView
CSDN资源:PullToLoadData-RecyclerView
源码地址:https://github.com/ryanlijianchang/PullToLoadData-RecyclerView
手把手教你实现Android RecyclerView上拉加载功能的更多相关文章
- 手把手教你轻松实现listview上拉加载
上篇讲了如何简单快速的的实现listview下拉刷新,那么本篇将讲解如何简单快速的实现上拉加载更多.其实,如果你已经理解了下拉刷新的实现过程,那么实现上拉加载更多将变得轻松起来,原理完全一致,甚至实现 ...
- RecyclerViewLoadMoreDemo【封装上拉加载功能的RecyclerView,搭配SwipeRefreshLayout实现下拉刷新】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 封装含有上拉加载功能的RecyclerView,然后搭配SwipeRefreshLayout实现下拉刷新.上拉加载功能. 在项目中将 ...
- RecyclerView 上拉加载下拉刷新
RecyclerView 上拉加载下拉刷新 <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/teach_s ...
- android ListView上拉加载更多 下拉刷新功能实现(采用pull-to-refresh)
Android实现上拉加载更多功能以及下拉刷新功能, 采用了目前比较火的PullToRefresh,他是目前实现比较好的下拉刷新的类库. 目前他支持的控件有:ListView, ExpandableL ...
- 下拉刷新,上拉加载功能--dropload.js的使用
这段时间工作太忙了,没时间更新博客内容,在这段时间,也学习到了不少新的知识.今天先整理一下dropload.js的使用方法吧,这个是在为项目中使用过的插件,很好用,但是真正用到项目中还是会有一些小小的 ...
- vux-scroller实现移动端上拉加载功能
本文将讲述vue-cli+vux-scroller实现移动端的上拉加载功能: 纠错声明:网上查阅资料看到很多人都将vux和vuex弄混,在这里我们先解释一下,vuex是vue框架自带的组件,是数据状态 ...
- Android 开发 上拉加载更多功能实现
实现思维 开始之前先废话几句,Android系统没有提供上拉加载的控件,只提供了下拉刷新的SwipeRefreshLayout控件.这个控件我们就不废话,无法实现上拉刷新的功能.现在我们说说上拉加载更 ...
- android 支持上拉加载,下拉刷新的列表控件SwipeRefreshLayout的二次封装
上拉加载,下拉刷新的列表控件,大家一定都封装过,或者使用过 源代码,我会在最后贴出来 这篇代码主要是为了解决两个问题 1.滑动冲突得问题 2.listview无数据时,无数据布局的展示问题 下方列出的 ...
- ionic1 下拉刷新 上拉加载 功能
html页面如下 <ion-content> <ion-refresher pulling-text="刷新" on-refresh="search() ...
随机推荐
- Content Security Policy (CSP) 介绍
当我不经意间在 Twitter 页面 view source 后,发现了惊喜. <!DOCTYPE html> <html lang="en"> <h ...
- LeetCode专题-Python实现之第9题:Palindrome Number
导航页-LeetCode专题-Python实现 相关代码已经上传到github:https://github.com/exploitht/leetcode-python 文中代码为了不动官网提供的初始 ...
- javascript基础修炼(9)——MVVM中双向数据绑定的基本原理
开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一. 概述 1.1 MVVM模型 MVVM模型是前端单页面应用中非常重要的模型之一,也是Single Page Appl ...
- 第38章 刷新令牌 - Identity Server 4 中文文档(v1.0.0)
第38章 刷新令牌 由于访问令牌的生命周期有限,因此刷新令牌允许在没有用户交互的情况下请求新的访问令牌. 以下流程支持刷新令牌:授权代码,混合和资源所有者密码凭据流.需要明确授权客户端通过设置Allo ...
- 解决IIS无法启动w3svc
1>:首先在CMD命令行中输入:fsutil resource setautoreset true c:\ 2>:然后在运行services.msc 3>:找到Windows Pro ...
- 配置多个git账号的ssh密钥
博客改版,请直接访问新版文章:https://www.cnblogs.com/xiaoxi666/p/9975981.html 背景 我们在工作中会以 ssh 的方式配置公司的 git 账号,但是平时 ...
- Lucene.Net3.0.3+盘古分词器学习使用
一.Lucene.Net介绍 Lucene.net是Lucene的.net移植版本,是一个开源的全文检索引擎开发包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索 ...
- Java设计模式-单例模式详解(上)
单例模式整理 敲了多年代码后,回头来看会别有一番滋味在心头.. 概念 单例模式是为了保证在一个jvm环境下,一个类仅有一个对象. 代码中常见的懒汉式.饿汉式,这些实现方式可以通过代码的设计来强制保证的 ...
- WEB 实时推送技术的总结
前言 随着 Web 的发展,用户对于 Web 的实时推送要求也越来越高 ,比如,工业运行监控.Web 在线通讯.即时报价系统.在线游戏等,都需要将后台发生的变化主动地.实时地传送到浏览器端,而不需要用 ...
- 【代码笔记】Web-CSS-CSS盒子模型
一,效果图. 二,代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...