如何使用RecyclerView实现无限列表?

erd*_*dna 323 android listview onscrolllistener material-design android-recyclerview

我想换ListViewRecyclerView.我想使用onScroll的的OnScrollListenerRecyclerView确定用户滚动到列表的末尾.

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

Abd*_*oor 401

感谢@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
                }
            }
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

别忘了添加

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
Run Code Online (Sandbox Code Playgroud)

  • 对于所有`findFirstVisibleItemPosition()`未解决的人,你应该将`RecyclerView.LayoutManager`改为`LinearLayoutManager` (43认同)
  • 再次使`loading = true`的条件在哪里? (25认同)
  • 怎么样`mLayoutManager.findLastCompletelyVisibleItemPosition()== mLayoutManager.getItemCount() - 1 (15认同)
  • 我能为'StaggeredGridLayoutManager`做些什么 (11认同)
  • 不要忘记在`// Dogination`部分后添加`loading = true`.否则此代码将只运行一次,您无法加载更多项目. (8认同)
  • 对于`StaggeredGridLayout`,设置`pastVisiblesItems = mLayoutManager.findFirstVisibleItemPositions(null)[0]`。其中 mLayoutManager 是 StaggeredGridLayoutManager 的一个实例。来自 [分页](https://github.com/MarkoMilos/Paginate) (3认同)
  • 不推荐使用setOnScrollListener - >使用addOnScrollListener和removeOnSrollListener来代替 (2认同)
  • 如何为StaggeredGridLayoutManager执行此操作? (2认同)
  • 如果你不能访问提到的方法,请考虑在```onScrolled()```中进行上传.```LinearLayoutManager layoutManager =(LinearLayoutManager)recyclerView.getLayoutManager();``` (2认同)
  • 不推荐使用`setOnScrollListener`,所以现在使用`addOnScrollListener` (2认同)
  • 对于未来的观众:Kushal Sharma的答案更精确,更容易使用. (2认同)
  • 在加载数据时将 loading 设置为 true(完成时为 false)并在加载前检查 if(!loading) 不是更有意义吗? (2认同)

Kus*_*rma 160

制作这些变量.

private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;
Run Code Online (Sandbox Code Playgroud)

在Scroll上设置回收站视图.

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;
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

注意:确保您使用的LinearLayoutManager是布局管理器RecyclerView.

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
Run Code Online (Sandbox Code Playgroud)

并为一个网格

GridLayoutManager mLayoutManager;
mLayoutManager = new GridLayoutManager(getActivity(), spanCount);
mRecyclerView.setLayoutManager(mLayoutManager);
Run Code Online (Sandbox Code Playgroud)

享受无尽的卷轴!^.^

更新: 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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

并将其添加到您的RecyclerView中

recyclerView.addOnScrollListener(OnScrollListener(layoutManager, adapter, dataList))
Run Code Online (Sandbox Code Playgroud)

有关完整的代码示例,请随时参考此Github仓库.

  • 知道如何使用GridLayoutManager应用此解决方案吗?由于`findFirstVisibleItemPosition()`方法仅适用于LinearLayoutManager,我无法弄清楚如何使其工作. (14认同)
  • 更新了ans!只需使用GridLayoutManager insped LinearLayoutManager ... (3认同)
  • 将代码放在onScrollStateChanged()方法而不是onScrolled()方法中. (3认同)
  • @ toobsco42您可以使用:`int [] firstVisibleItemPositions = new int [yourNumberOfColumns]; pastVisiblesItems = layoutManager.findFirstVisibleItemPositions(firstVisibleItemPositions)[0];` (2认同)

Hai*_*ang 70

对于那些只想在完全显示最后一项时获得通知的人,您可以使用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() {}
}
Run Code Online (Sandbox Code Playgroud)

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

  • 通过检查!recyclerView.canScrollVertically(-1),可以使用类似的解决方案进行刷新. (2认同)

Sab*_*med 64

这是另一种方法.它适用于任何布局管理器.

  1. 使适配器类抽象
  2. 然后在适配器类中创建一个抽象方法(例如load())
  3. onBindViewHolder中检查位置是否为last并调用load()
  4. 在活动或片段中创建适配器对象时覆盖load()函数.
  5. 在重载加载功能中实现您的loadmore调用

为了详细了解,我写了一篇博文和示例项目,请点击这里 http://sab99r.com/blog/recyclerview-endless-load-more/

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();
}
Run Code Online (Sandbox Code Playgroud)

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();
            }
        };
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 与解决方案一样,您不必将适配器变为抽象,而是可以在适配器中创建接口并使您的活动实现它,使其保持灵活和优雅,例如:ItemDisplayListener => onItemDisplayed(int position).这样你可以在任何位置加载:N,N-1,N-2 .. (3认同)
  • 如果用户主动滚动并返回到最后一个项目,@ mcwise它只会无休止地调用.onBindViewHolder仅在每次视图进入屏幕时调用一次,并且不会经常调用它. (3认同)
  • 在第一个解决方案的普及下埋藏的优秀解决方案的一个很好的例子.通常,最后一行将绑定到单独的viewtype,用作"Loading ..."横幅.当此viewtype获得绑定调用时,它将设置一个回调,当有更多数据可用时,该回调会触发数据更改通知.连接滚动侦听器的接受答案是浪费,并且可能不适用于其他布局类型. (2认同)

Fed*_*nzi 17

我的回答是Noor的修改版本.我从ListView我所拥有的地方EndlessScrollListener(你可以很容易地在很多答案中找到)传递给RecyclerView我,所以我希望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);

}
Run Code Online (Sandbox Code Playgroud)

  • 在 onScrolled 中,您将 LayoutManager 从 RecyclerView 转换为 LinearLayoutManager。它不适用于 StaggredGridLayout 或其他东西。StaggredGridLayout 中没有 findFirstVisibleItemPosition() 。 (2认同)

Min*_*yen 10

对我来说,这很简单:

     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;
            }
        }
    });
Run Code Online (Sandbox Code Playgroud)

注意异步作业:必须在异步作业结束时更改mLoading.希望它会有所帮助!


Yai*_*pro 9

大多数答案是假设RecyclerView使用a LinearLayoutManager,或者GridLayoutManager甚至StaggeredGridLayoutManager,或者假设滚动是垂直或水平的,但没有人发布完整的通用答案.

使用ViewHolder适配器显然不是一个好的解决方案.适配器RecyclerView使用它可能有多于1个.它"适应"他们的内容.它应该是Recycler View(它是负责当前向用户显示的内容的一个类,而不是仅负责提供内容的适配器RecyclerView),它必须通知您的系统需要更多项目(到加载).

这是我的解决方案,只使用RecyclerView(RecycerView.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.
    }
}
Run Code Online (Sandbox Code Playgroud)


Tia*_*ago 9

借助 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
                }
            }
        }
    })
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

youRecyclerView.addOnScrolledToEnd {
    //What you want to do once the end is reached
}
Run Code Online (Sandbox Code Playgroud)

此解决方案基于 Kushal Sharma 的回答。但是,这要好一些,因为:

  1. 它使用onScrollStateChanged代替onScroll. 这更好,因为onScroll每次在 RecyclerView 中发生任何类型的移动时onScrollStateChanged都会调用,而仅在 RecyclerView 的状态更改时调用。使用onScrollStateChanged将节省您的 CPU 时间,从而节省电池。
  2. 由于它使用扩展功能,因此可以在您拥有的任何 RecyclerView 中使用。客户端代码只有 1 行。


cap*_*wag 6

尽管接受的答案完美无缺,但下面的解决方案使用addOnScrollListener,因为不推荐使用setOnScrollListener,并且减少了变量的数量,并且条件也是如此.

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();
            }
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

  • dy&gt;0 始终为 false (2认同)

erd*_*dna 5

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

适配器:

public interface OnViewHolderListener {
    void onRequestedLastItem();
}

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

    ...

    if (position == getItemCount() - 1) onViewHolderListener.onRequestedLastItem();
}
Run Code Online (Sandbox Code Playgroud)

片段(或活动)

@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;
}
Run Code Online (Sandbox Code Playgroud)

  • 在我的FlexibleAdapter库中,我选择通过调用“onBindViewHolder()”来采用无限滚动,它的工作方式就像一个魅力。与滚动侦听器相比,该实现很小,并且更易于使用。 (2认同)

小智 5

虽然这个问题有很多答案,但我想分享我们创建无尽列表视图的经验.我们最近实现了自定义Carousel LayoutManager,它可以通过无限滚动列表以及某个点来在循环中工作.以下是GitHub详细说明.

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


Joh*_*n T 5

这是我的操作方式,简单而简短:

    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

            }
        }
    });
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

212100 次

最近记录:

6 年,2 月 前