检测 RecyclerView 滚动时何时到达最底部位置

Posted

技术标签:

【中文标题】检测 RecyclerView 滚动时何时到达最底部位置【英文标题】:Detect when RecyclerView reaches the bottom most position while scrolling 【发布时间】:2016-07-07 18:24:11 【问题描述】:

我有这个 RecyclerView 的代码。

    recyclerView = (RecyclerView)rootview.findViewById(R.id.fabric_recyclerView);
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.addItemDecoration(new RV_Item_Spacing(5));
    FabricAdapter fabricAdapter=new FabricAdapter(ViewAdsCollection.getFabricAdsDetailsAsArray());
    recyclerView.setAdapter(fabricAdapter);

我需要知道 RecyclerView 在滚动时何时到达最底部位置。可能吗 ?如果是,如何?

【问题讨论】:

试试***.com/questions/27841740/… ***.com/questions/26543131/… recyclerView.canScrollVertically(int方向);我们必须在这里传递的参数应该是什么? @Adrian,如果你仍然对这个问题感兴趣 :)))) ***.com/a/48514857/6674369 【参考方案1】:

还有一个简单的方法

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() 
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) 
        super.onScrollStateChanged(recyclerView, newState);

        if (!recyclerView.canScrollVertically(1)) 
            Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();

        
    
);

方向整数:-1 表示向上,1 表示向下,0 将始终返回 false。

【讨论】:

onScrolled() 防止检测发生两次。 我无法将 ScrollListnerEvent 添加到我的 RecyclerView。 onScrollStateChanged 中的代码没有执行。 如果您只想触发一次(显示 Toast 一次),请在 if 语句中添加布尔标志(类成员变量)并将其设置为 true,如果喜欢:if (!recyclerView.canScrollVertically(1) && !mHasReachedBottomOnce) mHasReachedBottomOnce = true Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show(); @ bastami82 我使用了你建议的技巧来防止两次打印吐司,但它不起作用 newState == RecyclerView.SCROLL_STATE_IDLE 添加到if 语句中会起作用。【参考方案2】:

使用此代码避免重复调用

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() 
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) 
            super.onScrollStateChanged(recyclerView, newState);

            if (!recyclerView.canScrollVertically(1) && newState==RecyclerView.SCROLL_STATE_IDLE) 
                Log.d("-----","end");
                
            
        
    );

【讨论】:

小心!当您尝试向上滑动而回收器中的元素很少时,这也会触发。它还会触发 TWICE,因为 newState 被忽略了。 只需添加一个额外的检查,看看您是否可以向上滚动。如果你不能向上滚动,也不能向下滚动,那么就忽略结果(因为列表不够长)。 recyclerView.canScrollVertically(-1) && !recyclerView.canScrollVertically(1)【参考方案3】:

只需在您的 recyclerview 上实现 addOnScrollListener() 即可。然后在滚动监​​听器内部实现下面的代码。

RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() 
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) 
            if (mIsLoading)
                return;
            int visibleItemCount = mLayoutManager.getChildCount();
            int totalItemCount = mLayoutManager.getItemCount();
            int pastVisibleItems = mLayoutManager.findFirstVisibleItemPosition();
            if (pastVisibleItems + visibleItemCount >= totalItemCount) 
                //End of list
            
        
    ;

【讨论】:

你能解释一下mIsLoading吗?你在哪里设置或更改值。 mLoading 只是一个布尔变量,指示视图是否被使用。例如;如果应用正在填充 recyclerview,则 mLoading 将为 true,并且在填充列表后变为 false。 此解决方案无法正常工作。看看***.com/a/48514857/6674369 无法在android.support.v7.widget.RecyclerView上解析方法'findFirstVisibleItemPosition' @Iman Marashi,你需要投:(recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()。这项工作。【参考方案4】:

答案在 Kotlin 中,它可以在 Java 中运行。如果您复制和粘贴,IntelliJ 应该会为您转换它。

recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener()

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) 

        // 3 lines below are not needed.
        Log.d("TAG","Last visible item is: $gridLayoutManager.findLastVisibleItemPosition()")
        Log.d("TAG","Item count is: $gridLayoutManager.itemCount")
        Log.d("TAG","end? : $gridLayoutManager.findLastVisibleItemPosition() == gridLayoutManager.itemCount-1")

        if(gridLayoutManager.findLastVisibleItemPosition() == gridLayoutManager.itemCount-1)
            // We have reached the end of the recycler view.
        

        super.onScrolled(recyclerView, dx, dy)
    
)

这也适用于LinearLayoutManager,因为它与上面使用的方法相同。即findLastVisibleItemPosition()getItemCount()(在Kotlin 中为itemCount)。

【讨论】:

好方法,但一个问题是 if 循环中的语句将执行多次 你是否得到了相同问题的解决方案 @Vishnu 一个简单的布尔变量可以解决这个问题。【参考方案5】:

在对该线程中的大多数其他答案不满意之后,我发现了一些我认为更好的东西,并且这里没有任何地方。

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() 
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) 
        if (!recyclerView.canScrollVertically(1) && dy > 0)
        
             //scrolled to BOTTOM
        else if (!recyclerView.canScrollVertically(-1) && dy < 0)
        
            //scrolled to TOP
        
    
);

这很简单,当您滚动到顶部或底部时,在所有情况下都会恰好点击一次。

【讨论】:

【参考方案6】:

我没有通过上述答案得到完美的解决方案,因为即使在 onScrolled 上它也会触发两次

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) 
            super.onScrolled(recyclerView, dx, dy)
           if( !recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN))
               context?.toast("Scroll end reached")
        

我几天前发现的替代解决方案,

  rv_repatriations.addOnScrollListener(object : RecyclerView.OnScrollListener() 
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) 
            super.onScrollStateChanged(recyclerView, newState)
            if (!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN) && recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE
                && !isLoaded
            ) 
                isLoaded = true
               //do what you want here and after calling the function change the value of boolean
                Log.e("RepatriationFragment", "Scroll end reached")
            
        
    )

使用布尔值确保在我们触底时不会多次调用它。

【讨论】:

但是recyclerView.canScrollVertically(direction)方向只是任何整数+ vs - 所以如果它在底部则可以是4,如果在顶部则可以是-12。 这个逻辑第一次执行两次 @andrey2ag 你也可以试试替代解决方案吗?【参考方案7】:

试试这个

我已经使用了上面的答案,当你在回收站视图结束时它总是运行,

如果你只想检查一次它是否在底部? 示例:- 如果我在底部时有 10 个项目的列表,它会显示我,如果我从上到下滚动它不会再次打印,如果你添加更多列表并且你去那里它会再次显示。

注意:-在打API中处理offset时使用此方法

    创建一个名为 EndlessRecyclerViewScrollListener 的类

        import android.support.v7.widget.GridLayoutManager;
        import android.support.v7.widget.LinearLayoutManager;
        import android.support.v7.widget.RecyclerView;
        import android.support.v7.widget.StaggeredGridLayoutManager;
    
        public abstract class EndlessRecyclerViewScrollListener extends RecyclerView.OnScrollListener 
            // The minimum amount of items to have below your current scroll position
            // before loading more.
            private int visibleThreshold = 5;
            // The current offset index of data you have loaded
            private int currentPage = 0;
            // The total number of items in the dataset after the last load
            private int previousTotalItemCount = 0;
            // True if we are still waiting for the last set of data to load.
            private boolean loading = true;
            // Sets the starting page index
            private int startingPageIndex = 0;
    
            RecyclerView.LayoutManager mLayoutManager;
    
            public EndlessRecyclerViewScrollListener(LinearLayoutManager layoutManager) 
                this.mLayoutManager = layoutManager;
            
    
        //    public EndlessRecyclerViewScrollListener() 
        //        this.mLayoutManager = layoutManager;
        //        visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
        //    
    
            public EndlessRecyclerViewScrollListener(StaggeredGridLayoutManager layoutManager) 
                this.mLayoutManager = layoutManager;
                visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
            
    
            public int getLastVisibleItem(int[] lastVisibleItemPositions) 
                int maxSize = 0;
                for (int i = 0; i < lastVisibleItemPositions.length; i++) 
                    if (i == 0) 
                        maxSize = lastVisibleItemPositions[i];
                    
                    else if (lastVisibleItemPositions[i] > maxSize) 
                        maxSize = lastVisibleItemPositions[i];
                    
                
                return maxSize;
            
    
            // This happens many times a second during a scroll, so be wary of the code you place here.
            // We are given a few useful parameters to help us work out if we need to load some more data,
            // but first we check if we are waiting for the previous load to finish.
            @Override
            public void onScrolled(RecyclerView view, int dx, int dy) 
                int lastVisibleItemPosition = 0;
                int totalItemCount = mLayoutManager.getItemCount();
    
                if (mLayoutManager instanceof StaggeredGridLayoutManager) 
                    int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
                    // get maximum element within the list
                    lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions);
                 else if (mLayoutManager instanceof GridLayoutManager) 
                    lastVisibleItemPosition = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition();
                 else if (mLayoutManager instanceof LinearLayoutManager) 
                    lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
                
    
                // If the total item count is zero and the previous isn't, assume the
                // list is invalidated and should be reset back to initial state
                if (totalItemCount < previousTotalItemCount) 
                    this.currentPage = this.startingPageIndex;
                    this.previousTotalItemCount = totalItemCount;
                    if (totalItemCount == 0) 
                        this.loading = true;
                    
                
                // If it’s still loading, we check to see if the dataset count has
                // changed, if so we conclude it has finished loading and update the current page
                // number and total item count.
                if (loading && (totalItemCount > previousTotalItemCount)) 
                    loading = false;
                    previousTotalItemCount = totalItemCount;
                
    
                // If it isn’t currently loading, we check to see if we have breached
                // the visibleThreshold and need to reload more data.
                // If we do need to reload some more data, we execute onLoadMore to fetch the data.
                // threshold should reflect how many total columns there are too
                if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) 
                    currentPage++;
                    onLoadMore(currentPage, totalItemCount, view);
                    loading = true;
                
            
    
            // Call this method whenever performing new searches
            public void resetState() 
                this.currentPage = this.startingPageIndex;
                this.previousTotalItemCount = 0;
                this.loading = true;
            
    
            // Defines the process for actually loading more data based on page
            public abstract void onLoadMore(int page, int totalItemsCount, RecyclerView view);
    
        
    

    像这样使用这个类

         LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
            recyclerView.setLayoutManager(linearLayoutManager);
            recyclerView.addOnScrollListener(new EndlessRecyclerViewScrollListener( linearLayoutManager) 
                @Override
                public void onLoadMore(int page, int totalItemsCount, RecyclerView view) 
                    Toast.makeText(getActivity(),"LAst",Toast.LENGTH_LONG).show();
                
            );
    

它在我的最后运行完美,如果您遇到任何问题,请评论我

【讨论】:

【参考方案8】:

有我的实现,对StaggeredGridLayout很有用。

用法:

private EndlessScrollListener scrollListener =
        new EndlessScrollListener(new EndlessScrollListener.RefreshList() 
            @Override public void onRefresh(int pageNumber) 
                //end of the list
            
        );

rvMain.addOnScrollListener(scrollListener);

监听器实现:

class EndlessScrollListener extends RecyclerView.OnScrollListener 
private boolean isLoading;
private boolean hasMorePages;
private int pageNumber = 0;
private RefreshList refreshList;
private boolean isRefreshing;
private int pastVisibleItems;

EndlessScrollListener(RefreshList refreshList) 
    this.isLoading = false;
    this.hasMorePages = true;
    this.refreshList = refreshList;


@Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) 
    super.onScrolled(recyclerView, dx, dy);
    StaggeredGridLayoutManager manager =
            (StaggeredGridLayoutManager) recyclerView.getLayoutManager();

    int visibleItemCount = manager.getChildCount();
    int totalItemCount = manager.getItemCount();
    int[] firstVisibleItems = manager.findFirstVisibleItemPositions(null);
    if (firstVisibleItems != null && firstVisibleItems.length > 0) 
        pastVisibleItems = firstVisibleItems[0];
    

    if (visibleItemCount + pastVisibleItems >= totalItemCount && !isLoading) 
        isLoading = true;
        if (hasMorePages && !isRefreshing) 
            isRefreshing = true;
            new Handler().postDelayed(new Runnable() 
                @Override public void run() 
                    refreshList.onRefresh(pageNumber);
                
            , 200);
        
     else 
        isLoading = false;
    


public void noMorePages() 
    this.hasMorePages = false;


void notifyMorePages() 
    isRefreshing = false;
    pageNumber = pageNumber + 1;


interface RefreshList 
    void onRefresh(int pageNumber);
  

【讨论】:

为什么要给回调增加200毫秒的延迟? @Mani 我现在不记得了,但也许我这样做只是为了平滑效果......【参考方案9】:

我也在寻找这个问题,但我没有找到让我满意的答案,所以我创建了自己的recyclerView实现。

其他解决方案不如我的精确。例如:如果最后一项非常大(大量文本),那么其他解决方案的回调将早得多,然后 recyclerView 真正达到底部。

我的解决方案解决了这个问题。

class CustomRecyclerView: RecyclerView

    abstract class TopAndBottomListener
        open fun onBottomNow(onBottomNow:Boolean)
        open fun onTopNow(onTopNow:Boolean)
    


    constructor(c:Context):this(c, null)
    constructor(c:Context, attr:AttributeSet?):super(c, attr, 0)
    constructor(c:Context, attr:AttributeSet?, defStyle:Int):super(c, attr, defStyle)


    private var linearLayoutManager:LinearLayoutManager? = null
    private var topAndBottomListener:TopAndBottomListener? = null
    private var onBottomNow = false
    private var onTopNow = false
    private var onBottomTopScrollListener:RecyclerView.OnScrollListener? = null


    fun setTopAndBottomListener(l:TopAndBottomListener?)
        if (l != null)
            checkLayoutManager()

            onBottomTopScrollListener = createBottomAndTopScrollListener()
            addOnScrollListener(onBottomTopScrollListener)
            topAndBottomListener = l
         else 
            removeOnScrollListener(onBottomTopScrollListener)
            topAndBottomListener = null
        
    

    private fun createBottomAndTopScrollListener() = object :RecyclerView.OnScrollListener()
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) 
            checkOnTop()
            checkOnBottom()
        
    

    private fun checkOnTop()
        val firstVisible = linearLayoutManager!!.findFirstCompletelyVisibleItemPosition()
        if(firstVisible == 0 || firstVisible == -1 && !canScrollToTop())
            if (!onTopNow) 
                onTopNow = true
                topAndBottomListener?.onTopNow(true)
            
         else if (onTopNow)
            onTopNow = false
            topAndBottomListener?.onTopNow(false)
        
    

    private fun checkOnBottom()
        var lastVisible = linearLayoutManager!!.findLastCompletelyVisibleItemPosition()
        val size = linearLayoutManager!!.itemCount - 1
        if(lastVisible == size || lastVisible == -1 && !canScrollToBottom())
            if (!onBottomNow)
                onBottomNow = true
                topAndBottomListener?.onBottomNow(true)
            
         else if(onBottomNow)
            onBottomNow = false
            topAndBottomListener?.onBottomNow(false)
        
    


    private fun checkLayoutManager()
        if (layoutManager is LinearLayoutManager)
            linearLayoutManager = layoutManager as LinearLayoutManager
        else
            throw Exception("for using this listener, please set LinearLayoutManager")
    

    private fun canScrollToTop():Boolean = canScrollVertically(-1)
    private fun canScrollToBottom():Boolean = canScrollVertically(1)

然后在您的活动/片段中:

override fun onCreate() 
    customRecyclerView.layoutManager = LinearLayoutManager(context)


override fun onResume() 
    super.onResume()
    customRecyclerView.setTopAndBottomListener(this)


override fun onStop() 
    super.onStop()
    customRecyclerView.setTopAndBottomListener(null)

希望它能帮助某人 ;-)

【讨论】:

别人的解决方案中哪一部分让你不满意?你应该先解释一下,然后再提出你的答案 @ZamSunk 因为其他解决方案不如我的精确。例如,如果最后一个项目相当多(很多文本),那么其他解决方案的回调将早得多,然后 recyclerView 真正到达底部。我的解决方案解决了这个问题。【参考方案10】:

使用 Kotlin

   recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() 
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) 
                if (!recyclerView.canScrollVertically(1)) 
                    Toast.makeText(context, "Last", Toast.LENGTH_LONG).show();
                
            
        )

【讨论】:

【参考方案11】:

我已经看到很多关于这个问题的回答,我认为他们都没有给出准确的行为作为结果。但是,如果您遵循这种方法,我相信您将获得最佳行为。

rvCategories 是您的 RecyclerView

categoriesList 是传递给您的适配器的列表

binding.rvCategories.addOnScrollListener(object : RecyclerView.OnScrollListener() 
  override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) 
            val position = (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
            if (position + 1 == categoriesList.size) 
               // END OF RECYCLERVIEW IS REACHED
             else 
                // END OF RECYCLERVIEW IS NOT REACHED
            
    
)

【讨论】:

【参考方案12】:

Kotlin 答案

您可以将此 Kotlin 函数用于底部滚动跟踪最佳实践,以创建无限无限滚动.

// Scroll listener.
private fun setupListenerPostListScroll() 
    val scrollDirectionDown = 1 // Scroll down is +1, up is -1.
    var currentListSize = 0

    mRecyclerView.addOnScrollListener(
        object : RecyclerView.OnScrollListener() 

            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) 
                super.onScrollStateChanged(recyclerView, newState)

                if (!recyclerView.canScrollVertically(scrollDirectionDown)
                    && newState == RecyclerView.SCROLL_STATE_IDLE
                ) 
                    val listSizeAfterLoading = recyclerView.layoutManager!!.itemCount

                    // List has more item.
                    if (currentListSize != listSizeAfterLoading) 
                        currentListSize = listSizeAfterLoading

                        // Get more posts.
                        postListScrollUpAction(listSizeAfterLoading)
                    
                    else  // List comes limit.
                        showToastMessageShort("No more items.")
                    
                
            
        )

【讨论】:

【参考方案13】:

你可以使用这个,如果你放 1 那将在你停留在列表末尾时显示,如果你现在想要当你停留在列表开头时,你将 1 更改为 -1

recyclerChat.addOnScrollListener(object : RecyclerView.OnScrollListener() 
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) 
            super.onScrollStateChanged(recyclerView, newState)
            if (!recyclerView.canScrollVertically(1)) 
               
            
        
    )

【讨论】:

【参考方案14】:

这是我在阅读了这篇文章中的所有答案后的解决方案。 我只想在最后一项显示在列表末尾并且listview 长度大于屏幕高度时显示loading,这意味着如果列表中只有一个或两个项目,则不会显示@987654323 @。

private var isLoadMoreLoading: Boolean = false

mRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() 

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) 
        super.onScrollStateChanged(recyclerView, newState)

        if (!isLoadMoreLoading) 
            if (linearLayoutManager.findLastCompletelyVisibleItemPosition() == (list.size-1)) 
                if (recyclerView.canScrollVertically(-1)) 

                    adapter?.addLoadMoreView()
                    loadMore()
                
            
        
    
)

private fun loadMore() 
    isLoadMoreLoading = true
    //call loadMore api

由于这个linearLayoutManager.findLastCompletelyVisibleItemPosition() == (list.size-1),我们可以知道最后一项显示出来了,但我们还需要知道listview是否可以滚动。

因此我添加了recyclerView.canScrollVertically(-1)。一旦你点击列表的底部,它就不能再向下滚动了。 -1 表示列表可以向上滚动。这意味着listview 长度大于屏幕高度。

这个答案在 kotlin 中。

【讨论】:

如果您向上滚动,到达最顶部的项目也会触发此选项【参考方案15】:

最简单的方法是在这样的适配器中:

@Override
public void onBindViewHolder(HistoryMessageListAdapter.ItemViewHolder holder, int position) 
            if (position == getItemCount()-1)
                listener.onLastItemReached();
            
        

因为一旦最后一个项目被回收,监听器就会被触发。

【讨论】:

【参考方案16】:

这是我的解决方案:

    val onScrollListener = object : RecyclerView.OnScrollListener() 

    override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) 
        directionDown = dy > 0
    

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) 
        if (recyclerView.canScrollVertically(1).not()
                && state != State.UPDATING
                && newState == RecyclerView.SCROLL_STATE_IDLE
                && directionDown) 
               state = State.UPDATING
            // TODO do what you want  when you reach bottom, direction
            // is down and flag for non-duplicate execution
        
    

【讨论】:

你从哪里得到state 是那个枚举? State 是我的一个枚举,这是检查屏幕状态的标志(我在我的应用程序中使用了带有数据绑定的 MVVM) 如果没有互联网连接,你将如何实现回调?【参考方案17】:

我们可以使用接口来获取位置

界面: 为监听器创建一个接口

public interface OnTopReachListener  void onTopReached(int position);

活动:

mediaRecycleAdapter = new MediaRecycleAdapter(Class.this, taskList); recycle.setAdapter(mediaRecycleAdapter); mediaRecycleAdapter.setOnSchrollPostionListener(new OnTopReachListener() 
@Override
public void onTopReached(int position) 
    Log.i("Position","onTopReached "+position);  

);

适配器:

public void setOnSchrollPostionListener(OnTopReachListener topReachListener) 
    this.topReachListener = topReachListener;@Override public void onBindViewHolder(MyViewHolder holder, int position) if(position == 0) 
  topReachListener.onTopReached(position);

【讨论】:

以上是关于检测 RecyclerView 滚动时何时到达最底部位置的主要内容,如果未能解决你的问题,请参考以下文章

检测 UIScrollview 底部到达

检测UIScrollview底部到达

确定何时显示 recyclerview 快速滚动拇指?

我如何知道用户何时到达 UITextView 中文本的末尾

如何创建一个循环(无尽的)RecyclerView?

NestedScrollview 内的 RecyclerView 滚动不顺畅