我想把ListView改为RecyclerView。我想在RecyclerView中使用OnScrollListener的onScroll来确定用户是否滚动到列表的末尾。
我如何知道用户是否滚动到列表的末尾以便我可以从REST服务获取新数据?
我想把ListView改为RecyclerView。我想在RecyclerView中使用OnScrollListener的onScroll来确定用户是否滚动到列表的末尾。
我如何知道用户是否滚动到列表的末尾以便我可以从REST服务获取新数据?
当前回答
大多数答案是假设RecyclerView使用LinearLayoutManager,或GridLayoutManager,甚至StaggeredGridLayoutManager,或假设滚动是垂直或水平的,但没有人张贴一个完全通用的答案。
使用ViewHolder的适配器显然不是一个好的解决方案。一个适配器可能有超过1个RecyclerView在使用它。它“改编”了它们的内容。它应该是RecyclerView(它是一个负责当前显示给用户的类,而不是适配器,它只负责向RecyclerView提供内容),它必须通知您的系统需要更多的项(加载)。
下面是我的解决方案,只使用了RecyclerView (RecycerView. view)的抽象类。LayoutManager和RecycerView.Adapter):
/**
* Listener to callback when the last item of the adpater is visible to the user.
* It should then be the time to load more items.
**/
public abstract class LastItemListener extends RecyclerView.OnScrollListener {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// init
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (layoutManager.getChildCount() > 0) {
// Calculations..
int indexOfLastItemViewVisible = layoutManager.getChildCount() -1;
View lastItemViewVisible = layoutManager.getChildAt(indexOfLastItemViewVisible);
int adapterPosition = layoutManager.getPosition(lastItemViewVisible);
boolean isLastItemVisible = (adapterPosition == adapter.getItemCount() -1);
// check
if (isLastItemVisible)
onLastItemVisible(); // callback
}
}
/**
* Here you should load more items because user is seeing the last item of the list.
* Advice: you should add a bollean value to the class
* so that the method {@link #onLastItemVisible()} will be triggered only once
* and not every time the user touch the screen ;)
**/
public abstract void onLastItemVisible();
}
// --- Exemple of use ---
myRecyclerView.setOnScrollListener(new LastItemListener() {
public void onLastItemVisible() {
// start to load more items here.
}
}
其他回答
我让你接近我。对我来说没问题。
我希望这对你有所帮助。
/**
* Created by Daniel Pardo Ligorred on 03/03/2016.
*/
public abstract class BaseScrollListener extends RecyclerView.OnScrollListener {
protected RecyclerView.LayoutManager layoutManager;
public BaseScrollListener(RecyclerView.LayoutManager layoutManager) {
this.layoutManager = layoutManager;
this.init();
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
this.onScroll(recyclerView, this.getFirstVisibleItem(), this.layoutManager.getChildCount(), this.layoutManager.getItemCount(), dx, dy);
}
private int getFirstVisibleItem(){
if(this.layoutManager instanceof LinearLayoutManager){
return ((LinearLayoutManager) this.layoutManager).findFirstVisibleItemPosition();
} else if (this.layoutManager instanceof StaggeredGridLayoutManager){
int[] spanPositions = null; //Should be null -> StaggeredGridLayoutManager.findFirstVisibleItemPositions makes the work.
try{
return ((StaggeredGridLayoutManager) this.layoutManager).findFirstVisibleItemPositions(spanPositions)[0];
}catch (Exception ex){
// Do stuff...
}
}
return 0;
}
public abstract void init();
protected abstract void onScroll(RecyclerView recyclerView, int firstVisibleItem, int visibleItemCount, int totalItemCount, int dx, int dy);
}
有一个方法public void setOnScrollListener (RecyclerView。OnScrollListener监听器)在https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setOnScrollListener%28android.support.v7.widget.RecyclerView.OnScrollListener%29。使用这个
编辑:
在onScrollListener中重写onScrollStateChanged方法并执行此操作
boolean loadMore = firstVisibleItem + visibleItemCount >= totalItemCount;
//loading is used to see if its already loading, you have to manually manipulate this boolean variable
if (loadMore && !loading) {
//end of list reached
}
借助Kotlin扩展函数的强大功能,代码可以看起来更加优雅。把它放在你想要的任何地方(我把它放在一个ExtensionFunctions中。kt文件):
/**
* WARNING: This assumes the layout manager is a LinearLayoutManager
*/
fun RecyclerView.addOnScrolledToEnd(onScrolledToEnd: () -> Unit){
this.addOnScrollListener(object: RecyclerView.OnScrollListener(){
private val VISIBLE_THRESHOLD = 5
private var loading = true
private var previousTotal = 0
override fun onScrollStateChanged(recyclerView: RecyclerView,
newState: Int) {
with(layoutManager as LinearLayoutManager){
val visibleItemCount = childCount
val totalItemCount = itemCount
val firstVisibleItem = findFirstVisibleItemPosition()
if (loading && totalItemCount > previousTotal){
loading = false
previousTotal = totalItemCount
}
if(!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)){
onScrolledToEnd()
loading = true
}
}
}
})
}
然后像这样使用它:
youRecyclerView.addOnScrolledToEnd {
//What you want to do once the end is reached
}
这个解决方案基于Kushal Sharma的回答。然而,这是一个更好的,因为:
它使用onScrollStateChanged而不是onScroll。这样做比较好,因为每当RecyclerView中有任何类型的移动时,onScroll都会被调用,而onScrollStateChanged只在RecyclerView的状态改变时被调用。使用onScrollStateChanged将节省CPU时间,从而节省电池。 因为它使用扩展函数,所以可以在任何RecyclerView中使用。客户端代码只有一行。
我的答案是一个改良版的努尔。我从一个ListView,在那里我有EndlessScrollListener(你可以很容易地在SO上找到许多答案)到一个RecyclerView,所以我想要一个endlessrecyscrolllistener轻松地更新我过去的监听器。
下面是代码,希望能有所帮助:
public abstract class EndlessScrollRecyclListener extends RecyclerView.OnScrollListener
{
// The total number of items in the dataset after the last load
private int previousTotalItemCount = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;
private int startingPageIndex = 0;
private int currentPage = 0;
@Override
public void onScrolled(RecyclerView mRecyclerView, int dx, int dy)
{
super.onScrolled(mRecyclerView, dx, dy);
LinearLayoutManager mLayoutManager = (LinearLayoutManager) mRecyclerView
.getLayoutManager();
visibleItemCount = mRecyclerView.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
onScroll(firstVisibleItem, visibleItemCount, totalItemCount);
}
public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
// 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;
currentPage++;
}
// 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.
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem +
visibleThreshold))
{
onLoadMore(currentPage + 1, totalItemCount);
loading = true;
}
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page, int totalItemsCount);
}
我刚试了一下:
final LinearLayoutManager rvLayoutManager = new LinearLayoutManager(this);
rvMovieList = (RecyclerView) findViewById(R.id.rvMovieList);
rvMovieList.setLayoutManager(rvLayoutManager);
adapter = new MovieRecyclerAdapter(this, movies);
rvMovieList.setAdapter(adapter);
adapter.notifyDataSetChanged();
rvMovieList.addOnScrollListener(new OnScrollListener() {
boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = rvLayoutManager.getChildCount();
totalItemCount = rvLayoutManager.getItemCount();
pastVisiblesItems = rvLayoutManager.findFirstVisibleItemPosition();
int lastitem = visibleItemCount + pastVisiblesItems;
if ( lastitem == totalItemCount
&& page < total_pages
&& !keyword.equals("") ) {
// hit bottom ..
String strnext;
if (page == total_pages - 1) {
int last = total_results - (page * 20);
strnext = String.format(getResources().getString(R.string.next), last);
} else {
strnext = String.format(getResources().getString(R.string.next), 20);
}
btNext.setText(strnext);
btNext.setVisibility(View.VISIBLE);
} else {
btNext.setVisibility(View.INVISIBLE);
}
if(pastVisiblesItems == 0 && page > 1){
// hit top
btPrev.setVisibility(View.VISIBLE);
} else {
btPrev.setVisibility(View.INVISIBLE);
}
}
});
我搞不清楚在哪里加载真或假,所以我试图删除它,这段代码工作如我所料。