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

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


当前回答

没有重复调用,但你滚动。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
                }
            }
        })

其他回答

做这些变量。

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 repo。

这里我的解决方案使用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();
                }
            });
        }
    }
}

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

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

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

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

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

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

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

这是我一直在使用的一段代码,它在两个方向上都适用于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();
                }
            }

        });
    }