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) 定義兩種不同的視圖類型,既正常行與最后一行的樣子不同。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM