zoukankan      html  css  js  c++  java
  • AdapterView 和 RecyclerView 的连续滚动

    AdapterView 和 RecyclerView 的连续滚动

    概述


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

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

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

    enter description here

    示例图 1

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

    enter description here

    示例图片 2

    ListView 和 GridView 的实现方式


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

    1. import android.widget.AbsListView;  
    2.  
    3. public abstract class EndlessScrollListener implements AbsListView.OnScrollListener
    4. // The minimum number of items to have below your current scroll position 
    5. // before loading more. 
    6. private int visibleThreshold = 5
    7. // The current offset index of data you have loaded 
    8. private int currentPage = 0
    9. // The total number of items in the dataset after the last load 
    10. private int previousTotalItemCount = 0
    11. // True if we are still waiting for the last set of data to load. 
    12. private boolean loading = true
    13. // Sets the starting page index 
    14. private int startingPageIndex = 0
    15.  
    16. public EndlessScrollListener()

    17.  
    18. public EndlessScrollListener(int visibleThreshold)
    19. this.visibleThreshold = visibleThreshold; 

    20.  
    21. public EndlessScrollListener(int visibleThreshold, int startPage)
    22. this.visibleThreshold = visibleThreshold; 
    23. this.startingPageIndex = startPage; 
    24. this.currentPage = startPage; 

    25.  
    26. // This happens many times a second during a scroll, so be wary of the code you place here. 
    27. // We are given a few useful parameters to help us work out if we need to load some more data, 
    28. // but first we check if we are waiting for the previous load to finish. 
    29. @Override 
    30. public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)  

    31. // If the total item count is zero and the previous isn't, assume the 
    32. // list is invalidated and should be reset back to initial state 
    33. if (totalItemCount < previousTotalItemCount) { 
    34. this.currentPage = this.startingPageIndex; 
    35. this.previousTotalItemCount = totalItemCount; 
    36. if (totalItemCount == 0) { this.loading = true; }  

    37. // If it's still loading, we check to see if the dataset count has 
    38. // changed, if so we conclude it has finished loading and update the current page 
    39. // number and total item count. 
    40. if (loading && (totalItemCount > previousTotalItemCount)) { 
    41. loading = false
    42. previousTotalItemCount = totalItemCount; 
    43. currentPage++; 

    44.  
    45. // If it isn't currently loading, we check to see if we have breached 
    46. // the visibleThreshold and need to reload more data. 
    47. // If we do need to reload some more data, we execute onLoadMore to fetch the data. 
    48. if (!loading && (firstVisibleItem + visibleItemCount + visibleThreshold) >= totalItemCount ) { 
    49. loading = onLoadMore(currentPage + 1, totalItemCount); 


    50.  
    51. // Defines the process for actually loading more data based on page 
    52. // Returns true if more data is being loaded; returns false if there is no more data to load. 
    53. public abstract boolean onLoadMore(int page, int totalItemsCount)
    54.  
    55. @Override 
    56. public void onScrollStateChanged(AbsListView view, int scrollState)
    57. // 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.  
    17.  
    18. // Append the next page of data into the adapter 
    19. // This method probably sends out a network request and appends new data items to your adapter.  
    20. public void loadNextDataFromApi(int offset)
    21. // Send an API request to retrieve appropriate paginated data  
    22. // --> Send the request including an offset value (i.e `page`) as a query parameter. 
    23. // --> Deserialize and construct new model objects from the API response 
    24. // --> Append the new data objects to the existing set of items inside the array of items 
    25. // --> 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.  
    5. @Override 
    6. protected void onCreate(Bundle savedInstanceState)
    7. // Configure the RecyclerView 
    8. RecyclerView rvItems = (RecyclerView) findViewById(R.id.rvContacts); 
    9. LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); 
    10. rvItems.setLayoutManager(linearLayoutManager); 
    11. // Retain an instance so that you can call `resetState()` for fresh searches 
    12. scrollListener = new EndlessRecyclerViewScrollListener(linearLayoutManager) { 
    13. @Override 
    14. public void onLoadMore(int page, int totalItemsCount, RecyclerView view)
    15. // Triggered only when new data needs to be appended to the list 
    16. // Add whatever code is needed to append new items to the bottom of the list 
    17. loadNextDataFromApi(page); 

    18. }; 
    19. // Adds the scroll listener to RecyclerView 
    20. rvItems.addOnScrollListener(scrollListener); 

    21.  
    22. // Append the next page of data into the adapter 
    23. // This method probably sends out a network request and appends new data items to your adapter.  
    24. public void loadNextDataFromApi(int offset)
    25. // Send an API request to retrieve appropriate paginated data  
    26. // --> Send the request including an offset value (i.e `page`) as a query parameter. 
    27. // --> Deserialize and construct new model objects from the API response 
    28. // --> Append the new data objects to the existing set of items inside the array of items 
    29. // --> 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) 定义两种不同的视图类型,既正常行与最后一行的样子不同。

  • 相关阅读:
    shell中test命令方法详解
    Linux 查看端口使用情况
    jboss 报错处理及端口修改
    jboss的使用和安装
    python 关于列表的增删改查及个别的命令
    Cisco Cat4500系列High CPU故障步骤摘要
    Services: ARP Caching
    Passive Client Feature
    BGP前缀过滤(正则表达式)
    Cisco Spectrum Expert(Wave2 AP)
  • 原文地址:https://www.cnblogs.com/wchhuangya/p/6093025.html
Copyright © 2011-2022 走看看