The contents of the list are paging data returned by the server, which is pulled down each time you browse to the end of the current page. This interrupts the user’s browsing and inevitably creates a wait. The product wants to make this process unconscious. One way to do this is to preload, which is to request the next page of data before the page is finished, giving the user the impression that the list is infinite.

To monitor list rolling state The first thought of solution is to monitor list rolling state, execute when the list quickly scroll to the bottom, preload, RecyclerView. OnScrollListener provides two callback:

public class RecyclerView {
    public abstract static class OnScrollListener {
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState){}
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){}
    }
}
Copy the code

LayoutManager can be reached in onScrolled(), which provides a number of methods related to the entry position:

/ / for RecyclerView new extension methods, listens for preload event fun RecyclerView. AddOnPreloadListener (preloadCount: Int, onPreload: AddOnScrollListener (object: RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, Dy) / / get LayoutManger val layoutManager. = recyclerView layoutManager / / if the layoutManager LinearLayoutManager the if {// If the list is scrolling up, Finally see table and table item item index value equal to the preload threshold if (dy > 0 && layoutManager. FindLastVisibleItemPosition () = = layoutManager. ItemCount - 1 - preloadCount) { onPreload() } } } }) }Copy the code

When the list is rolled, the system checks whether the index of the last visible entry in the list is equal to the preloading threshold. If the index is equal, the list is nearly rolled to the bottom, and the preloading callback is triggered. Preloading can then be implemented like this:

RecyclerView. AddOnPreloadListener (3) {/ / when the distance there are three tables at the bottom of the list item implement preload / / preload business logic}Copy the code

A bug was detected when running the Demo: onPreload() was not executed when scrolling the list quickly, and onPrelaod() was executed multiple times when scrolling the list slowly.

The reason is that RecyclerView does not guarantee that onScrolled() will be called when every entry appears. If the scrolling is very fast, it is possible for an entry to miss the callback.

To avoid missing out, only relax the terms:

if (dy > 0 && layoutManager.findLastVisibleItemPosition() >= layoutManager.itemCount - 1 - preloadCount) {
    onPreload()
}
Copy the code

Change == to >=, on condition that it is relaxed, but the problem of multiple calls is worse. In the normal sliding process, this scheme cannot accurately match the preloading threshold, that is, onPreload() cannot be called back only once, because onScroll() is a callback of pixel granularity, while preloading requires entry granularity detection.

Another disadvantage of this scenario is coupling to the LayoutManager type. Code using the if (layoutManager is LinearLayoutManager) such judgment, if you want to fit StaggeredGridLayoutManager must add else branch, What if you add a custom LayoutManager?

Type is independent of preloading determine whether preload is the key to get table item index, just by layoutManager. FindLastVisibleItemPosition (), actually spared a big circle.

OnBindViewHolder (holder: ViewHolder, position: Int); onBindViewHolder(holder: ViewHolder, Int);

Class PreloadAdapter: recyclerView. Adapter<ViewHolder>() {var onPreload: (() -> Unit)? = null // Preloading offset var preloadItemCount = 0 // List rolling status private var scrollState = SCROLL_STATE_IDLE Override fun onBindViewHolder(holder: ViewHolder, position: Int) { checkPreload(position) } override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {/ / update the rolling state scrollState = newState because super. OnScrollStateChanged (recyclerView, Private fun checkPreload(position: Int) {if (onPreload! = null && position == Max (ItemCount-1 - preloadItemCount, 0)// Index value equals threshold && scrollState! = SCROLL_STATE_IDLE // list is scrolling) {onPreload? .invoke() } } }Copy the code

It can then be used like this:

Val preloadAdapter = preloadAdapter (). Apply {// preloadItemCount = 2 onPreload = {// preload business logic}}Copy the code

This scheme has the following advantages:

You don’t need to worry about how fast the list slides because all entries will go through onBindViewHolder(), and the index value and preload threshold can be determined using ==. Don’t worry about the user pulling up multiple times at the bottom of the list causing the callback to be preloaded multiple times, because onBindViewHolder() won’t be executed multiple times in this case. When RecyclerView replaces LayoutManager, there is no need to change the code. The only thing to worry about is if the list is rolled back after a preload has been triggered, and if the preload has not completed, the list is rolled back again, and the threshold entry removed from the screen needs to be re-executed ‘onBindViewHolder(), which will trigger another preload.

Of course, you can solve this problem by increasing the tag bit:

class VarietyAdapter: Recyclerview. Adapter<ViewHolder>() {var isPreloading = false override fun onBindViewHolder(holder: ViewHolder, position: Int) {checkPreload(position)} private fun checkPreload(position: Int) { if (onPreload ! = null && position == Max (ItemCount-1 - preloadItemCount, 0)// Index value equals threshold && scrollState! = SCROLL_STATE_IDLE // List is scrolling &&! IsPreloading // no preloading in progress) {isPreloading = true // indicates that onPreload is being loaded? .invoke() } } }Copy the code

This flag bit is then controlled in the business layer, setting the flag position to false when the list content request succeeds, fails, or times out.

But I prefer to let the business layer maintain this bit, because if Adapter simply provides preload time, it doesn’t need to care when the business layer load ends. Original link: juejin.cn/post/688514…

At the end of the article

Your likes collection is the biggest encouragement to me! Welcome to follow me, share Android dry goods, exchange Android technology. If you have any comments or technical questions about this article, please leave a comment in the comments section.