检测 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 滚动时何时到达最底部位置的主要内容,如果未能解决你的问题,请参考以下文章