如何实现无限列表与RecyclerView?

我想把ListView改为RecyclerView。我想使用RecyclerView中的OnScrollListeneronScroll来确定用户是否滚动到列表的末尾。

我如何知道用户是否滚动到列表的末尾以便我可以从REST服务获取新数据?

285031 次浏览

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setOnScrollListener%28android.support.v7.widget.RecyclerView.OnScrollListener%29中有一个方法public void setOnScrollListener (RecyclerView.OnScrollListener listener)。使用这个

编辑:

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
}

好吧,我是通过使用RecyclerView.Adapter的onBindViewHolder方法做到的。

适配器:

public interface OnViewHolderListener {
void onRequestedLastItem();
}


@Override
public void onBindViewHolder(ViewHolder holder, int position) {


...


if (position == getItemCount() - 1) onViewHolderListener.onRequestedLastItem();
}

片段(或活动)

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
contentView = inflater.inflate(R.layout.comments_list, container, false);
recyclerView = (RecyclerView) mContentView.findViewById(R.id.my_recycler_view);
adapter = new Adapter();
recyclerView.setAdapter(adapter);


...


adapter.setOnViewHolderListener(new Adapter.OnViewHolderListener() {
@Override
public void onRequestedLastItem() {
//TODO fetch new data from webservice
}
});
return contentView;
}

我会尝试扩展已使用的LayoutManager(例如LinearLayoutManager)并覆盖scrollVerticallyBy()方法。首先,我将首先调用super,然后检查返回的整数值。如果值等于0,则到达底部或顶部边界。然后我将使用findLastVisibleItemPosition()方法来找出到达哪个边界,并在需要时加载更多数据。只是一个想法。

此外,您甚至可以从该方法中返回您的值,允许滚动并显示“loading”指示器。

做这些变量。

private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;

设置为滚动回收视图。

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {


@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);


visibleItemCount = mRecyclerView.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();


if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
// End has been reached


Log.i("Yaeye!", "end called");


// Do something


loading = true;
}
}
});

注意:确保使用LinearLayoutManager作为RecyclerView的布局管理器。

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);

对于网格来说

GridLayoutManager mLayoutManager;
mLayoutManager = new GridLayoutManager(getActivity(), spanCount);
mRecyclerView.setLayoutManager(mLayoutManager);

有乐趣与你无尽的卷轴!!^ ^。

更新: mRecyclerView。setOnScrollListener ()已弃用,只需替换为mRecyclerView.addOnScrollListener(),警告就会消失!你可以从这个SO问题读到更多。

由于Android现在正式支持Kotlin,这里是相同的更新-

使OnScrollListener

class OnScrollListener(val layoutManager: LinearLayoutManager, val adapter: RecyclerView.Adapter<RecyclerAdapter.ViewHolder>, val dataList: MutableList<Int>) : RecyclerView.OnScrollListener() {
var previousTotal = 0
var loading = true
val visibleThreshold = 10
var firstVisibleItem = 0
var visibleItemCount = 0
var totalItemCount = 0


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


visibleItemCount = recyclerView.childCount
totalItemCount = layoutManager.itemCount
firstVisibleItem = layoutManager.findFirstVisibleItemPosition()


if (loading) {
if (totalItemCount > previousTotal) {
loading = false
previousTotal = totalItemCount
}
}


if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
val initialSize = dataList.size
updateDataList(dataList)
val updatedSize = dataList.size
recyclerView.post { adapter.notifyItemRangeInserted(initialSize, updatedSize) }
loading = true
}
}
}

然后像这样把它添加到你的RecyclerView中

recyclerView.addOnScrollListener(OnScrollListener(layoutManager, adapter, dataList))

有关完整的代码示例,请参阅这个Github回购

感谢@Kushal,这是我实现它的方式

private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;


mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy > 0) { //check for scroll down
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();


if (loading) {
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
loading = false;
Log.v("...", "Last Item Wow !");
// Do pagination.. i.e. fetch new data


loading = true;
}
}
}
}
});

别忘了加上

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);

我在RecyclerView.Adapter类的onBindViewHolder方法中使用此逻辑实现了无限滚动类型。

    if (position == mItems.size() - 1 && mCurrentPage <= mTotalPageCount) {
if (mCurrentPage == mTotalPageCount) {
mLoadImagesListener.noMorePages();
} else {
int newPage = mCurrentPage + 1;
mLoadImagesListener.loadPage(newPage);
}
}

使用这段代码,当RecyclerView到达最后一项时,它会增加当前页面和接口上的回调,该接口负责从api加载更多数据并将新结果添加到适配器。

我可以张贴更完整的例子,如果这不是很清楚?

 recyclerList.setOnScrollListener(new RecyclerView.OnScrollListener()
{
@Override
public void onScrolled(RecyclerView recyclerView, int dx,int dy)
{
super.onScrolled(recyclerView, dx, dy);
}


@Override
public void onScrollStateChanged(RecyclerView recyclerView,int newState)
{
int totalItemCount = layoutManager.getItemCount();
int lastVisibleItem = layoutManager.findLastVisibleItemPosition();


if (totalItemCount> 1)
{
if (lastVisibleItem >= totalItemCount - 1)
{
// End has been reached
// do something
}
}
}
});

我的答案是一个改良版的努尔。我从一个ListView传递到一个RecyclerView,其中我有EndlessScrollListener(你可以很容易地在SO上的许多答案中找到),所以我想要一个EndlessRecyclScrollListener来轻松地更新我过去的侦听器。

下面是代码,希望能有所帮助:

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);


}

对我来说,这很简单:

     private boolean mLoading = false;


mList.setOnScrollListener(new RecyclerView.OnScrollListener() {


@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);


int totalItem = mLinearLayoutManager.getItemCount();
int lastVisibleItem = mLinearLayoutManager.findLastVisibleItemPosition();


if (!mLoading && lastVisibleItem == totalItem - 1) {
mLoading = true;
// Scrolled to bottom. Do something here.
mLoading = false;
}
}
});

小心异步作业:mLoading必须在异步作业结束时更改。希望对大家有所帮助!

对于那些只想在最后一项完全显示时得到通知的人,可以使用View.canScrollVertically()

这是我的实现:

public abstract class OnVerticalScrollListener
extends RecyclerView.OnScrollListener {


@Override
public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (!recyclerView.canScrollVertically(-1)) {
onScrolledToTop();
} else if (!recyclerView.canScrollVertically(1)) {
onScrolledToBottom();
} else if (dy < 0) {
onScrolledUp();
} else if (dy > 0) {
onScrolledDown();
}
}


public void onScrolledUp() {}


public void onScrolledDown() {}


public void onScrolledToTop() {}


public void onScrolledToBottom() {}
}

注意:你可以使用recyclerView.getLayoutManager().canScrollVertically()如果你想支持API <14.

对于使用StaggeredGridLayoutManager的人,这里是我的实现,它适用于我。

 private class ScrollListener extends RecyclerView.OnScrollListener {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {


firstVivisibleItems = mLayoutManager.findFirstVisibleItemPositions(firstVivisibleItems);


if(!recyclerView.canScrollVertically(1) && firstVivisibleItems[0]!=0) {
loadMoreImages();
}


}


private boolean loadMoreImages(){
Log.d("myTag", "LAST-------HERE------");
return true;
}
}

这是另一种方法。它将与任何布局管理器工作。

  1. 使Adapter类抽象
  2. 然后在适配器类中创建一个抽象方法。load ())
  3. onBindViewHolder中检查位置if last并调用load()
  4. 在活动或片段中创建适配器对象时重写load()函数。
  5. 在重载加载函数实现你的loadmore调用
为了详细了解,我写了一篇博客文章和示例项目,请在这里获得它 http://sab99r.com/blog/recyclerview-endless-load-more/ < / p >

MyAdapter.java

public abstract class MyAdapter extends RecyclerView.Adapter<ViewHolder>{


@Override
public void onBindViewHolder(ViewHolder holder, int position) {
//check for last item
if ((position >= getItemCount() - 1))
load();
}


public abstract void load();
}

MyActivity.java

public class MainActivity extends AppCompatActivity {
List<Items> items;
MyAdapter adapter;


@Override
protected void onCreate(Bundle savedInstanceState) {
...
adapter=new MyAdapter(items){
@Override
public void load() {
//implement your load more here
Item lastItem=items.get(items.size()-1);
loadMore();
}
};
}
}

我有一个关于如何使用RecyclerView进行分页的非常详细的示例。在高层次,我有一个设置PAGE_SIZE,让我们说30。所以我请求30个项目,如果我得到30个,我就请求下一页。如果我得到的条目少于30个,我就标记一个变量来指示已经到达最后一页,然后停止请求更多的页面。看看吧,告诉我你的想法。

https://medium.com/@etiennelawlor/pagination-with-recyclerview-1cb7e66a502b

虽然接受的答案工作得很好,但下面的解决方案使用addOnScrollListener,因为setOnScrollListener已弃用,并减少了变量的数量,以及if条件。

final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
feedsRecyclerView.setLayoutManager(layoutManager);


feedsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);


if (dy > 0) {
if ((layoutManager.getChildCount() + layoutManager.findFirstVisibleItemPosition()) >= layoutManager.getItemCount()) {
Log.d("TAG", "End of list");
//loadMore();
}
}
}
});

检查这每件事都有详细的解释: 使用RecyclerView从A到Z分页 < / p >

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


@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();


if (!mIsLoading && !mIsLastPage) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
})

loadMoreItems ():

private void loadMoreItems() {
mAdapter.removeLoading();
//load data here from the server


// in case of success
mAdapter.addData(data);
// if there might be more data
mAdapter.addLoading();
}

在MyAdapter中:

private boolean mIsLoadingFooterAdded = false;


public void addLoading() {
if (!mIsLoadingFooterAdded) {
mIsLoadingFooterAdded = true;
mLineItemList.add(new LineItem());
notifyItemInserted(mLineItemList.size() - 1);
}
}


public void removeLoading() {
if (mIsLoadingFooterAdded) {
mIsLoadingFooterAdded = false;
int position = mLineItemList.size() - 1;
LineItem item = mLineItemList.get(position);


if (item != null) {
mLineItemList.remove(position);
notifyItemRemoved(position);
}
}
}


public void addData(List<YourDataClass> data) {


for (int i = 0; i < data.size(); i++) {
YourDataClass yourDataObject = data.get(i);
mLineItemList.add(new LineItem(yourDataObject));
notifyItemInserted(mLineItemList.size() - 1);
}
}

我让你接近我。对我来说没问题。

我希望这对你有所帮助。

/**
* 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);


}

这些答案都没有考虑到列表是否太小。

这是我一直在使用的一段代码,它在两个方向上都适用于RecycleViews。

@Override
public boolean onTouchEvent(MotionEvent motionEvent) {


if (recyclerViewListener == null) {
return super.onTouchEvent(motionEvent);
}


/**
* If the list is too small to scroll.
*/
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
if (!canScrollVertically(1)) {
recyclerViewListener.reachedBottom();
} else if (!canScrollVertically(-1)) {
recyclerViewListener.reachedTop();
}
}


return super.onTouchEvent(motionEvent);
}


public void setListener(RecyclerViewListener recycleViewListener) {
this.recyclerViewListener = recycleViewListener;
addOnScrollListener(new OnScrollListener() {


@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);


if (recyclerViewListener == null) {
return;
}


recyclerViewListener.scrolling(dy);


if (!canScrollVertically(1)) {
recyclerViewListener.reachedBottom();
} else if (!canScrollVertically(-1)) {
recyclerViewListener.reachedTop();
}
}


});
}
这里我的解决方案使用AsyncListUtil,在网络上说: 注意,这个类使用一个线程来加载数据,所以它适合从二级存储(如磁盘)加载数据,但不适合从网络加载数据。 但是我使用odata来读取数据并工作良好。 我在示例中遗漏了数据实体和网络方法。

public class AsyncPlatoAdapter extends RecyclerView.Adapter {


private final AsyncPlatoListUtil mAsyncListUtil;
private final MainActivity mActivity;
private final RecyclerView mRecyclerView;
private final String mFilter;
private final String mOrderby;
private final String mExpand;


public AsyncPlatoAdapter(String filter, String orderby, String expand, RecyclerView recyclerView, MainActivity activity) {
mFilter = filter;
mOrderby = orderby;
mExpand = expand;
mRecyclerView = recyclerView;
mActivity = activity;
mAsyncListUtil = new AsyncPlatoListUtil();


}


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.plato_cardview, parent, false);


// Create a ViewHolder to find and hold these view references, and
// register OnClick with the view holder:
return new PlatoViewHolderAsync(itemView, this);
}


@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final Plato item = mAsyncListUtil.getItem(position);
PlatoViewHolderAsync vh = (PlatoViewHolderAsync) holder;
if (item != null) {
Integer imagen_id = item.Imagen_Id.get();
vh.getBinding().setVariable(BR.plato, item);
vh.getBinding().executePendingBindings();
vh.getImage().setVisibility(View.VISIBLE);
vh.getProgress().setVisibility(View.GONE);
String cacheName = null;
String urlString = null;
if (imagen_id != null) {
cacheName = String.format("imagenes/imagen/%d", imagen_id);
urlString = String.format("%s/menusapi/%s", MainActivity.ROOTPATH, cacheName);
}
ImageHelper.downloadBitmap(mActivity, vh.getImage(), vh.getProgress(), urlString, cacheName, position);
} else {
vh.getBinding().setVariable(BR.plato, item);
vh.getBinding().executePendingBindings();
//show progress while loading.
vh.getImage().setVisibility(View.GONE);
vh.getProgress().setVisibility(View.VISIBLE);
}
}


@Override
public int getItemCount() {
return mAsyncListUtil.getItemCount();
}


public class AsyncPlatoListUtil extends AsyncListUtil<Plato> {
/**
* Creates an AsyncListUtil.
*/
public AsyncPlatoListUtil() {
super(Plato.class, //my data class
10, //page size
new DataCallback<Plato>() {
@Override
public int refreshData() {
//get count calling ../$count ... odata endpoint
return countPlatos(mFilter, mOrderby, mExpand, mActivity);
}


@Override
public void fillData(Plato[] data, int startPosition, int itemCount) {
//get items from odata endpoint using $skip and $top
Platos p = loadPlatos(mFilter, mOrderby, mExpand, startPosition, itemCount, mActivity);
for (int i = 0; i < Math.min(itemCount, p.value.size()); i++) {
data[i] = p.value.get(i);
}


}
}, new ViewCallback() {
@Override
public void getItemRangeInto(int[] outRange) {
//i use LinearLayoutManager in the RecyclerView
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
outRange[0] = layoutManager.findFirstVisibleItemPosition();
outRange[1] = layoutManager.findLastVisibleItemPosition();
}


@Override
public void onDataRefresh() {
mRecyclerView.getAdapter().notifyDataSetChanged();
}


@Override
public void onItemLoaded(int position) {
mRecyclerView.getAdapter().notifyItemChanged(position);
}
});
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
onRangeChanged();
}
});
}
}
}

虽然这个问题有很多答案,但我还是想分享一下我们创建无尽列表视图的经验。我们最近实现了自定义Carousel LayoutManager,可以在循环中无限滚动列表以及到某个点。这是一个在GitHub上的详细描述

我建议你看看这篇关于创建自定义LayoutManagers: http://cases.azoft.com/create-custom-layoutmanager-android/的简短但有价值的建议

有一个名为随意翻阅的易于使用的库。支持ListView和RecyclerView(线性布局,GridLayout和StaggeredGridLayout)。

下面是Github上项目的链接

@kushal @abdulaziz

为什么不使用这个逻辑呢?

public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int totalItemCount, lastVisibleItemPosition;


if (dy > 0) {
totalItemCount = _layoutManager.getItemCount();
lastVisibleItemPosition = _layoutManager.findLastVisibleItemPosition();


if (!_isLastItem) {
if ((totalItemCount - 1) == lastVisibleItemPosition) {
LogUtil.e("end_of_list");


_isLastItem = true;
}
}
}
}

试试下面:

import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;


/**
* Abstract Endless ScrollListener
*
*/
public abstract class EndlessScrollListener extends
RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 10;
// The current offset index of data you have loaded
private int currentPage = 1;
// True if we are still waiting for the last set of data to load.
private boolean loading = true;
// The total number of items in the data set after the last load
private int previousTotal = 0;
private int firstVisibleItem;
private int visibleItemCount;
private int totalItemCount;
private LayoutManager layoutManager;


public EndlessScrollListener(LayoutManager layoutManager) {
validateLayoutManager(layoutManager);
this.layoutManager = layoutManager;
}


public EndlessScrollListener(int visibleThreshold,
LayoutManager layoutManager, int startPage) {
validateLayoutManager(layoutManager);
this.visibleThreshold = visibleThreshold;
this.layoutManager = layoutManager;
this.currentPage = startPage;
}


private void validateLayoutManager(LayoutManager layoutManager)
throws IllegalArgumentException {
if (null == layoutManager
|| !(layoutManager instanceof GridLayoutManager)
|| !(layoutManager instanceof LinearLayoutManager)) {
throw new IllegalArgumentException(
"LayoutManager must be of type GridLayoutManager or LinearLayoutManager.");
}
}


@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount();
if (layoutManager instanceof GridLayoutManager) {
firstVisibleItem = ((GridLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
} else if (layoutManager instanceof LinearLayoutManager) {
firstVisibleItem = ((LinearLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
}
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading
&& (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
// End has been reached do something
currentPage++;
onLoadMore(currentPage);
loading = true;
}
}


// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page);


}

我检测加载事件的方法不是检测滚动,而是监听最后一个视图是否附加。如果附加了最后一个视图,我认为这是加载更多内容的时机。

class MyListener implements RecyclerView.OnChildAttachStateChangeListener {
RecyclerView mRecyclerView;


MyListener(RecyclerView view) {
mRecyclerView = view;
}


@Override
public void onChildViewAttachedToWindow(View view) {


RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
RecyclerView.LayoutManager mgr = mRecyclerView.getLayoutManager();
int adapterPosition = mgr.getPosition(view);


if (adapterPosition == adapter.getItemCount() - 1) {
// last view was attached
loadMoreContent();
}


@Override
public void onChildViewDetachedFromWindow(View view) {}
}

大多数答案是假设RecyclerView使用LinearLayoutManager,或GridLayoutManager,甚至StaggeredGridLayoutManager,或假设滚动是垂直或水平的,但没有人给出一个完全通用的答案

使用ViewHolder的适配器显然不是一个好的解决方案。一个适配器可能有多个RecyclerView在使用它。它“改编”了它们的内容。它应该是Recycler视图(它是一个负责当前显示给用户的类,而不是适配器,它只负责向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.
}
}

我已经使用Abdulaziz Noor回答创建了LoadMoreRecyclerView

LoadMoreRecyclerView

public class LoadMoreRecyclerView extends RecyclerView  {


private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
//WrapperLinearLayout is for handling crash in RecyclerView
private WrapperLinearLayout mLayoutManager;
private Context mContext;
private OnLoadMoreListener onLoadMoreListener;


public LoadMoreRecyclerView(Context context) {
super(context);
mContext = context;
init();
}


public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}


public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init();
}


private void init(){
mLayoutManager = new WrapperLinearLayout(mContext,LinearLayoutManager.VERTICAL,false);
this.setLayoutManager(mLayoutManager);
this.setItemAnimator(new DefaultItemAnimator());
this.setHasFixedSize(true);
}


@Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);


if(dy > 0) //check for scroll down
{
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();


if (loading)
{
if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount)
{
loading = false;
Log.v("...", "Call Load More !");
if(onLoadMoreListener != null){
onLoadMoreListener.onLoadMore();
}
//Do pagination.. i.e. fetch new data
}
}
}
}


@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
}


public void onLoadMoreCompleted(){
loading = true;
}


public void setMoreLoading(boolean moreLoading){
loading = moreLoading;
}


public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
}

WrapperLinearLayout

public class WrapperLinearLayout extends LinearLayoutManager
{
public WrapperLinearLayout(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}


@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("probe", "meet a IOOBE in RecyclerView");
}
}
}

//在xml中添加

<your.package.LoadMoreRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</your.package.LoadMoreRecyclerView>

OnCreate或onViewCreated

mLoadMoreRecyclerView = (LoadMoreRecyclerView) view.findViewById(R.id.recycler_view);
mLoadMoreRecyclerView.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore() {
callYourService(StartIndex);
}
});

callYourService

private void callYourService(){
//callyour Service and get response in any List


List<AnyModelClass> newDataFromServer = getDataFromServerService();
//Enable Load More
mLoadMoreRecyclerView.onLoadMoreCompleted();


if(newDataFromServer != null && newDataFromServer.size() > 0){
StartIndex += newDataFromServer.size();


if (newDataFromServer.size() < Integer.valueOf(MAX_ROWS)) {
//StopLoading..
mLoadMoreRecyclerView.setMoreLoading(false);
}
}
else{
mLoadMoreRecyclerView.setMoreLoading(false);
mAdapter.notifyDataSetChanged();
}
}

也可以在没有滚动侦听器的情况下实现,只使用数据模型的纯逻辑。滚动视图需要按位置获取项目以及最大项目计数。模型可以具有后台逻辑,以块的形式获取所需的项,而不是一个一个地获取,并在后台线程中执行此操作,在数据准备就绪时通知视图。

这种方法允许获取队列优先选择最近请求的(所以当前可见)项而不是旧的(很可能已经滚动了)提交,控制使用的并行线程的数量等等。此方法的完整源代码(演示应用程序和可重用库)可用在这里

这个解决方案对我来说非常有效。

//Listener


public abstract class InfiniteScrollListener     extendsRecyclerView.OnScrollListener {


public static String TAG = InfiniteScrollListener.class.getSimpleName();
int firstVisibleItem, visibleItemCount, totalItemCount;
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 1;
private int current_page = 1;


private LinearLayoutManager mLinearLayoutManager;


public InfiniteScrollListener(LinearLayoutManager linearLayoutManager) {
this.mLinearLayoutManager = linearLayoutManager;
}


@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);


visibleItemCount = recyclerView.getChildCount();
totalItemCount = mLinearLayoutManager.getItemCount();
firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();


if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount - firstVisibleItem <= visibleThreshold)) {


current_page++;


onLoadMore(current_page);


loading = true;
}
}


public void resetState() {
loading = true;
previousTotal = 0;
current_page = 1;
}


public abstract void onLoadMore(int current_page);
}


//Implementation into fragment
private InfiniteScrollListener scrollListener;


scrollListener = new InfiniteScrollListener(manager) {
@Override
public void onLoadMore(int current_page) {
//Load data
}
};
rv.setLayoutManager(manager);
rv.addOnScrollListener(scrollListener);

@erdna请参考我下面的代码。也许它会对你有帮助。

     int firstVisibleItem, visibleItemCount, totalItemCount;
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {


@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);


visibleItemCount = layoutManager.getChildCount();
totalItemCount = layoutManager.getItemCount();
firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
Log.e("firstVisibleItem", firstVisibleItem + "");
Log.e("visibleItemCount", visibleItemCount + "");
Log.e("totalItemCount", totalItemCount + "");


if (page != total_page_index) {
if (loading) {
if ((visibleItemCount + firstVisibleItem) >= totalItemCount) {
Log.e("page", String.valueOf(page));
page=page+1;
new GetSummary().execute(String.valueOf(page), "");
loading = false;
}
}
}


}




});

借助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的回答。然而,这是一个更好的,因为:

  1. 它使用onScrollStateChanged而不是onScroll。这样更好,因为每次在RecyclerView中有任何类型的移动时都会调用onScroll,而onScrollStateChanged只在RecyclerView的状态发生变化时才会调用。使用onScrollStateChanged将节省CPU时间,从而节省电池。
  2. 因为它使用扩展函数,所以可以在任何RecyclerView中使用。客户端代码只有一行。
if (layoutManager.findLastCompletelyVisibleItemPosition() ==
recyclerAdapter.getItemCount() - 1) {
//load more items.
}

公平而简单。

我刚试了一下:

    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);
}
}
});

我搞不清楚在哪里把loadingtruefalse放在一起,所以我尝试删除它,这段代码如我预期的那样工作。

我是这样做的,简单而简短:

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
if(!recyclerView.canScrollVertically(1) && dy != 0)
{
// Load more results here


}
}
});

下面是无尽滚动RecyclerView的简单实现示例,使用从各种源编译的简单库。

build.gradle中添加这一行

implementation 'com.hereshem.lib:awesomelib:2.0.1'

在活动中创建RecyclerView布局

<com.hereshem.lib.recycler.MyRecyclerView
android:id="@+id/recycler"
app:layoutManager="LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

通过传递ViewHolder支持的类来创建一个ViewHolder

public static class EVHolder extends MyViewHolder<Events> {
TextView date, title, summary;
public EVHolder(View v) {
super(v);
date = v.findViewById(R.id.date);
title = v.findViewById(R.id.title);
summary = v.findViewById(R.id.summary);
}
@Override
public void bindView(Events c) {
date.setText(c.date);
title.setText(c.title);
summary.setText(c.summary);
}
}

通过在适配器中传递项目、类和布局来创建项目列表变量和很少行的适配器

List<Events> items = new ArrayList<>();
MyRecyclerView recycler = findViewById(R.id.recycler);
RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, items, EVHolder.class, R.layout.row_event);
recycler.setAdapter(adapter);

ClickListener和LoadMore Listener可以通过以下几行添加

recycler.setOnItemClickListener(new MyRecyclerView.OnItemClickListener() {
@Override
public void onItemClick(int position) {
Toast.makeText(MainActivity.this, "Recycler Item Clicked " + position, Toast.LENGTH_SHORT).show();
}
});


recycler.setOnLoadMoreListener(new MyRecyclerView.OnLoadMoreListener() {
@Override
public void onLoadMore() {
loadData();
}
});
loadData();

数据加载后必须调用此函数

recycler.loadComplete();

当不需要LoadMore时,可以通过调用LoadMore来隐藏布局

recycler.hideLoadMore();

更多的例子可以找到在这里

希望这对你有所帮助。

  1. 创建一个抽象类并扩展RecyclerView。OnScrollListener

    public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
    private int previousTotal = 0;
    private boolean loading = true;
    private int visibleThreshold;
    private int firstVisibleItem, visibleItemCount, totalItemCount;
    private RecyclerView.LayoutManager layoutManager;
    
    
    public EndlessRecyclerOnScrollListener(RecyclerView.LayoutManager layoutManager, int visibleThreshold) {
    this.layoutManager = layoutManager; this.visibleThreshold = visibleThreshold;
    }
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    super.onScrolled(recyclerView, dx, dy);
    
    
    visibleItemCount = recyclerView.getChildCount();
    totalItemCount = layoutManager.getItemCount();
    firstVisibleItem = ((LinearLayoutManager)layoutManager).findFirstVisibleItemPosition();
    
    
    if (loading) {
    if (totalItemCount > previousTotal) {
    loading = false;
    previousTotal = totalItemCount;
    }
    }
    if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
    onLoadMore();
    loading = true;
    }
    }
    
    
    public abstract void onLoadMore();}
    
  2. in activity (or fragment) add addOnScrollListener to recyclerView

    LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(mLayoutManager);
    recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager, 3) {
    @Override
    public void onLoadMore() {
    //TODO
    ...
    }
    });
    

没有重复调用,但你滚动。fetchData只会在您位于列表的末尾并且有更多数据要获取时调用

关于我的代码:
1.fetchData()每次调用取10个项 2.isScrolling是一个类成员变量 3.previousTotalCount也是类成员。它存储之前访问的最后一个项目

recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
isScrolling = true
}
}


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


val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val scrolledOutItems = layoutManager.findFirstVisibleItemPosition()




if (isScrolling && (visibleItemCount + scrolledOutItems == totalItemCount)
&& (visibleItemCount + scrolledOutItems != previousTotalCount)) {
previousTotalCount = totalItemCount
isScrolling = false
fetchData()  //fetches 10 new items from Firestore
pagingProgressBar.visibility = View.VISIBLE
}
}
})

正如约翰·T所建议的。只需使用下面的代码块,真的很简短,美观简单:D

public void loadMoreOnRecyclerView() {
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull @NotNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1) && dy != 0) {
//Load more items here
}
}
});
}

你可以跟随我的回购来了解它的工作方式。

https://github.com/Nghien-Nghien/PokeAPI-Java/blob/0d8d69d348e068911b883f0ae7791d904cc75cb5/app/src/main/java/com/example/pokemonapi/MainActivity.java

关于应用程序的描述信息:https://github.com/skydoves/Pokedex#readme