AdapterView 和 RecyclerView 的连续滚动

android
RecyclerView
tutorial

概述


应用中一个常见的使用场景就是:当用户滚动浏览的项目时,会自动加载更多的项目(又叫做无限滚动)。它的原理是:当滚动到达底部之前,一旦当前剩余可见的项目达到了一个设定好的阈值,就会触发加载更多数据的请求。

本文列举了 ListViewGridViewRecyclerView 的实现方法。它们的实现都是类似的,除了 RecyclerView 还需要传入 LayoutManager,这是因为它需要给无限滚动提供一些必要的信息。

无论哪个控件,实现无限滚动所需要的信息无非就包括这么几点:检测列表中剩余的可见元素,在到达最后一个元素之前开始获取数据的阈值。这个阈值可以用来决定什么时候开始加载更多。

示例图 1

要实现连续滚动的一个重点就是:一定要在用户到达列表的末尾前就获取数据。因此,添加一个阈值来让列表在预期的时候就加载数据。

示例图片 2

ListView 和 GridView 的实现方式


每个 AdapterView (例如 ListViewGridView)都支持 onScrollListener 事件的绑定,只要用户滑动列表,就会触发该事件。使用该体系,我们就可以定义一个基础类:EndlessScrollListener,它继承自 OnScrollListener,可以适用于大多数情况:

  1. import android.widget.AbsListView;  


  2. public abstract class EndlessScrollListener implements AbsListView.OnScrollListener { 

  3. // The minimum number of items to have below your current scroll position 

  4. // before loading more. 

  5. private int visibleThreshold = 5; 

  6. // The current offset index of data you have loaded 

  7. private int currentPage = 0; 

  8. // The total number of items in the dataset after the last load 

  9. private int previousTotalItemCount = 0; 

  10. // True if we are still waiting for the last set of data to load. 

  11. private boolean loading = true; 

  12. // Sets the starting page index 

  13. private int startingPageIndex = 0; 


  14. public EndlessScrollListener() { 




  15. public EndlessScrollListener(int visibleThreshold) { 

  16. this.visibleThreshold = visibleThreshold; 




  17. public EndlessScrollListener(int visibleThreshold, int startPage) { 

  18. this.visibleThreshold = visibleThreshold; 

  19. this.startingPageIndex = startPage; 

  20. this.currentPage = startPage; 




  21. // This happens many times a second during a scroll, so be wary of the code you place here. 

  22. // We are given a few useful parameters to help us work out if we need to load some more data, 

  23. // but first we check if we are waiting for the previous load to finish. 

  24. @Override 

  25. public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)  



  26. // If the total item count is zero and the previous isn't, assume the 

  27. // list is invalidated and should be reset back to initial state 

  28. if (totalItemCount < previousTotalItemCount) { 

  29. this.currentPage = this.startingPageIndex; 

  30. this.previousTotalItemCount = totalItemCount; 

  31. if (totalItemCount == 0) { this.loading = true; }  



  32. // If it's still loading, we check to see if the dataset count has 

  33. // changed, if so we conclude it has finished loading and update the current page 

  34. // number and total item count. 

  35. if (loading && (totalItemCount > previousTotalItemCount)) { 

  36. loading = false; 

  37. previousTotalItemCount = totalItemCount; 

  38. currentPage++; 




  39. // If it isn't currently loading, we check to see if we have breached 

  40. // the visibleThreshold and need to reload more data. 

  41. // If we do need to reload some more data, we execute onLoadMore to fetch the data. 

  42. if (!loading && (firstVisibleItem + visibleItemCount + visibleThreshold) >= totalItemCount ) { 

  43. loading = onLoadMore(currentPage + 1, totalItemCount); 






  44. // Defines the process for actually loading more data based on page 

  45. // Returns true if more data is being loaded; returns false if there is no more data to load. 

  46. public abstract boolean onLoadMore(int page, int totalItemsCount); 


  47. @Override 

  48. public void onScrollStateChanged(AbsListView view, int scrollState) { 

  49. // Don't take any action on changed 





注意:这是一个抽象类,要使用它,必须实现该类中的抽象方法:onLoadMore,用于检索新的数据。在 activity 中,可以用一个匿名内部类来实现这个抽象类,并把它绑定到适配器上。例如:

  1. public class MainActivity extends Activity { 

  2. @Override 

  3. protected void onCreate(Bundle savedInstanceState) { 

  4. // ... the usual  

  5. ListView lvItems = (ListView) findViewById(R.id.lvItems); 

  6. // Attach the listener to the AdapterView onCreate 

  7. lvItems.setOnScrollListener(new EndlessScrollListener() { 

  8. @Override 

  9. public boolean onLoadMore(int page, int totalItemsCount) { 

  10. // Triggered only when new data needs to be appended to the list 

  11. // Add whatever code is needed to append new items to your AdapterView 

  12. loadNextDataFromApi(page);  

  13. // or loadNextDataFromApi(totalItemsCount);  

  14. return true; // ONLY if more data is actually being loaded; false otherwise. 



  15. }); 





  16. // Append the next page of data into the adapter 

  17. // This method probably sends out a network request and appends new data items to your adapter.  

  18. public void loadNextDataFromApi(int offset) { 

  19. // Send an API request to retrieve appropriate paginated data  

  20. // --> Send the request including an offset value (i.e `page`) as a query parameter. 

  21. // --> Deserialize and construct new model objects from the API response 

  22. // --> Append the new data objects to the existing set of items inside the array of items 

  23. // --> Notify the adapter of the new items made with `notifyDataSetChanged()` 





现在,当你滚动列表时,每当剩余元素到达阈值时,列表就会自动加载下一页的数据。该方法对于 GridView 来说,一样的有效。

RecyclerView 的实现方式


对于 RecyclerView 来说,我们也可以使用一个相似的方法:定义接口 EndlessRecyclerViewScrollListener;一个必须实现的方法 onLoadMore()。在 RecyclerView 中,LayoutManager 用于渲染列表元素并管理滚动,即提供与适配器相关的当前滚动位置的信息。基于上述理由,我们需要传入一个 LayoutManager 的实例,用于收集必须的信息,和用于确定加载更多数据的时机。

因此,RecyclerView 实现连续分页需要以下几个步骤:

  1. EndlessRecyclerViewScrollListener.java 类拷贝到你的项目中

  2. RecyclerView 上调用 addOnScrollListener() 方法来启用连续分页。给该方法传入 EndlessRecyclerViewScrollListener 的实例,当新页需要加载时,实现 onLoadMore() 方法

  3. onLoadMore() 方法中,加载更多数据,并把它们填充到列表中

代码示例如下:

  1. public class MainActivity extends Activity { 

  2. // Store a member variable for the listener 

  3. private EndlessRecyclerViewScrollListener scrollListener; 


  4. @Override 

  5. protected void onCreate(Bundle savedInstanceState) { 

  6. // Configure the RecyclerView 

  7. RecyclerView rvItems = (RecyclerView) findViewById(R.id.rvContacts); 

  8. LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); 

  9. rvItems.setLayoutManager(linearLayoutManager); 

  10. // Retain an instance so that you can call `resetState()` for fresh searches 

  11. scrollListener = new EndlessRecyclerViewScrollListener(linearLayoutManager) { 

  12. @Override 

  13. public void onLoadMore(int page, int totalItemsCount, RecyclerView view) { 

  14. // Triggered only when new data needs to be appended to the list 

  15. // Add whatever code is needed to append new items to the bottom of the list 

  16. loadNextDataFromApi(page); 



  17. }; 

  18. // Adds the scroll listener to RecyclerView 

  19. rvItems.addOnScrollListener(scrollListener); 




  20. // Append the next page of data into the adapter 

  21. // This method probably sends out a network request and appends new data items to your adapter.  

  22. public void loadNextDataFromApi(int offset) { 

  23. // Send an API request to retrieve appropriate paginated data  

  24. // --> Send the request including an offset value (i.e `page`) as a query parameter. 

  25. // --> Deserialize and construct new model objects from the API response 

  26. // --> Append the new data objects to the existing set of items inside the array of items 

  27. // --> Notify the adapter of the new items made with `notifyItemRangeInserted()` 





复位连续滚动状态


当你准备执行新的搜索时,要确保清除列表上已经存在的数据,并马上通知适配器数据的变化。当然,还需要使用 resetState() 方法来重置 EndlessRecyclerViewScrollListener 的状态:

  1. // 1. First, clear the array of data 

  2. listOfItems.clear(); 

  3. // 2. Notify the adapter of the update 

  4. recyclerAdapterOfItems.notifyDataSetChanged(); // or notifyItemRangeRemoved 

  5. // 3. Reset endless scroll listener when performing a new search 

  6. scrollListener.resetState(); 

完整的连续滚动代码可以参考:code sample for usagethis code sample

故障排查


如果在开发中遇到问题,请考虑下述的建议:

  • 对于 ListView 来说,请一定在 ActivityonCreate() 方法 或 FragmentonCreateView() 方法中,给它设置 setOnScrollListener() 监听。否则,你可能会遇到一些想不到的问题

  • 要使分页系统可以可靠地、持续地工作,在给列表添加新的数据之前,你应该确保清除适配器的数据。对 RecyclerView 来说,当需要通知适配器数据有更新时,强烈建议使用精度更细的通知方法。

  • 要触发分页,始终记得 loadNextDataFromApi 方法调用时,需要把新数据添加到已经存在的数据源。按句话说,只有首次加载时才清除数据,以后的每次分页都是把新增的数据添加到原有的数据集中。

  • 如果你遇到了下述的错误:Cannot call this method in a scroll callback. Scroll callbacks might be run during a measure & layout pass where you cannot change the RecyclerView data,那你应该按照 Stack Overflow 中的解决办法对代码进行改造:

  1. // Delay before notifying the adapter since the scroll listeners  

  2. // can be called while RecyclerView data cannot be changed. 

  3. view.post(new Runnable() { 

  4. @Override 

  5. public void run() { 

  6. // Notify adapter with appropriate notify methods 

  7. adapter.notifyItemRangeInserted(curSize, allContacts.size() - 1); 



  8. }); 

在自定义的适配器中显示进度


想要在 ListView 的底部显示加载数据的进度,需要对适配器进行特殊处理。使用 getItemViewType(int position) 定义两种不同的视图类型,既正常行与最后一行的样子不同。

AdapterView 和 RecyclerView 的连续滚动的更多相关文章

  1. Unity3D游戏开发之连续滚动背景

    Unity3D游戏开发之连续滚动背景 原文  http://blog.csdn.net/qinyuanpei/article/details/22983421 在诸如天天跑酷等2D游戏中,因为游戏须要 ...

  2. 用js实现table内容从下到上连续滚动

    网上有很多用ul实现新闻列表滚动的例子,但是很少有直接用table实现列表内容滚动的例子,而Marquee标签滚动的效果不是很好,于是就自己写了一个,提供给攻城师朋友们参考 实现思路:由于table包 ...

  3. jquery连续滚动

    本文非常详细的讲解在jquery里实现图片或文字的连续循环滚动的方法. 连续循环滚动是我们在网页开发中经常要用到的特效,在jquery里,我们要实现文字或图片的连续循环滚动是非常简单的.出处:http ...

  4. jquery 写的图片左右连续滚动

    <style type="text/css"> *{ margin:0; padding:0;} body { font:12px/1.8 Arial; color:# ...

  5. [Unity3D]Unity3D游戏开发之连续滚动背景

    在诸如天天跑酷等2D游戏中.因为游戏须要表现出运动的感觉.通常都会使游戏背景连续循环滚动以增强视觉效果,那么今天.博主就来带领大家一起来实现连续滚动背景吧. 首先来讲述一下原理.准备两张连续的图片(博 ...

  6. JS连续滚动幻灯片:原理与实现

    什么是连续滚动幻灯片?打开一些网站的首页,你会发现有一块这样的区域:一张图片,隔一段时间滑动切换下一张:同时,图片两端各有一个小按钮,供你手动点选下一张:底部有一排小圆圈,供你选定特定的某帧图片.这就 ...

  7. RecyclerView 实现快速滚动

    RecyclerView 实现快速滚动 https://www.cnblogs.com/mamamia/p/8311449.html

  8. RecyclerView 实现快速滚动 (转)

    RecyclerView 实现快速滚动 极小光  简书作者   简评:Android Support Library 26 中终于实现了一个等待已久的功能:RecyclerView 的快速滚动. An ...

  9. Android TV端的(RecyclerView)水平滚动焦点错乱问题

    package com.hhzt.iptv.ui.customview; import android.content.Context;import android.content.res.Typed ...

随机推荐

  1. 【转载】Java NIO学习 & NIO BIO AIO 比较

    可以参考这个页面: http://www.iteye.com/magazines/132-Java-NIO (下面这个页面也有) http://ifeve.com/overview/ 另,在这篇文章里 ...

  2. (转)Android项目重构之路:界面篇

    在前一篇文章<Android项目重构之路:架构篇>中已经简单说明了项目的架构,将项目分为了四个层级:模型层.接口层.核心层.界面层.其中,最上层的界面,是变化最频繁的一个层面,也是最复杂最 ...

  3. 阻止jQuery事件冒泡

    Query对DOM的事件触发具有冒泡特性.有时利用这一特性可以减少重复代码,但有时候我们又不希望事件冒泡.这个时候就要阻止 jQuery.Event冒泡. 在jQuery.Event 的文档 中的开头 ...

  4. MVP+Dagger2+Rxjava+Retrofit+GreenDao 开发的小应用,包括新闻、图片、视频3个大模块,代码封装良好

    练习MVP架构开发的App,算是对自己学过的知识做一个总结,做了有一段时间,界面还算挺多的.代码量还是有的,里面做了大量封装,总体代码整理得非常干净,这个我已经尽力整理了. 不管是文件(java.xm ...

  5. XAMPP + Xdebug+Zend Studio

    建立php开发环境(XAMPP + Xdebug+Zend Studio) 大家知道,运行php可以在apache上运行,但是要在apache上配置php解释器模块,懒得麻烦.就用XAMPP吧,它已经 ...

  6. 浏览器开发调试工具的秘密 - Secrets of the Browser Developer Tools

    来源:GBin1.com 如果你是一个前端开发人员的话,正确的了解和使用浏览器开发工具是一个必须的技能. Secrets of the Browser Developer Tools是一个帮助大家了解 ...

  7. iOS 判断NSString是否包含某个字符串

    主要是使用3个方法 rangeOfString    是否包含 hasPrefix      是否在前缀包含 hasSuffix           是否在末尾包含 如代码: //判断字符是否包含某字 ...

  8. Android 8.0新特性-取消大部分静态注册广播

    今天楼主在写一个广播的demo,功能非常的简单,就是一个应用发送一个自定义的广播,同时在这个应用里面定义了一个广播接受者,并且在AndroidManifest文件中进行静态的注册.Demo看上去非常的 ...

  9. django2自动发现项目中的url

    根据路飞学城luffycity.com 的crm项目修改的 1 url入口:rbac/urls.py urlpatterns = [ ... # 批量操作权限 re_path(r'^multi/per ...

  10. Android 查看 无wifi/usb设备的logcat方法

    Android 查看 无wifi/usb设备的logcat方法 一.情况 一个定制Android设备,wifi被去掉.我须要调试一个USB设备这样也无法用usb来输出logcat. 由于这个USB设备 ...