Phát hiện khi RecyclerView đạt đến vị trí cuối cùng nhất trong khi cuộn


96

Tôi có mã này cho 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);

Tôi cần biết khi nào RecyclerView đạt đến vị trí cuối cùng nhất trong khi cuộn. Nó có khả thi không? Nếu có, làm thế nào?




1
RecyclerView.canScrollVerently (int hướng); Tham số như vậy chúng ta phải truyền vào đây là gì?
Adrian

@Adrian, trong trường hợp bạn vẫn quan tâm đến câu hỏi này :)))) stackoverflow.com/a/48514857/6674369
Andriy Antonov

Câu trả lời:


179

cũng có một cách đơn giản để làm điều đó

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

        }
    }
});

số nguyên hướng: -1 cho tăng, 1 cho xuống, 0 sẽ luôn trả về false.


17
onScrolled () ngăn việc phát hiện xảy ra hai lần.
Ian Wambai

Tôi không thể thêm ScrollListnerEvent vào RecyclerView của mình. Mã trong onScrollStateChanged không thực thi.
Sandeep Yohans

1
nếu bạn muốn chỉ này để kích hoạt một lần (hiển thị Toast một lần) thêm cờ boolean (một biến thành viên lớp) để câu lệnh if và đặt nó vào bên trong đúng nếu như: if (!recyclerView.canScrollVertically(1) && !mHasReachedBottomOnce) { mHasReachedBottomOnce = true Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();}
bastami82

@ Bastami82 tôi sử dụng các thủ thuật như bạn đề nghị để ngăn ngừa in gấp đôi so với bánh mì nướng, nhưng nó không làm việc
Vishwa Pratap

4
Thêm newState == RecyclerView.SCROLL_STATE_IDLEvào iftuyên bố sẽ hoạt động.
Lee Chun Hoe

61

Sử dụng mã này để tránh các cuộc gọi lặp lại

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

2
Tôi đã thử có thể trả lời nhưng điều này là đơn giản và hoàn hảo đối với tôi. cảm ơn
Vishwa Pratap

@Venkatesh Chào mừng bạn.
Milind Chaudhary

Câu trả lời duy nhất không cho tôi bất kỳ lỗi nào khác
The Fluffy T Rex

@TheFluffyTRex Cảm ơn
Milind Chaudhary

47

Chỉ cần triển khai addOnScrollListener () trên chế độ xem tái chế của bạn. Sau đó, bên trong trình nghe cuộn triển khai mã bên dưới.

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

1
Bạn có thể giải thích một chút về mIsLoading được không? bạn đang thiết lập nó ở đâu hoặc thay đổi giá trị.
MiguelHincapieC 19/12/16

mLoading chỉ là một biến boolean cho biết chế độ xem có được tham gia hay không. Ví dụ: nếu ứng dụng đang điền chế độ xem lại, mLoading sẽ là true và trở thành false khi danh sách được điền.
Febi M Felix

1
giải pháp này không hoạt động đúng cách. hãy nhìn vào stackoverflow.com/a/48514857/6674369
Andriy Antonov

3
Không thể giải quyết phương pháp 'findFirstVisibleItemPosition'trênandroid.support.v7.widget.RecyclerView
Iman Marashi

2
@Iman Marashi, bạn cần phải cast: (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition(). Công việc này.
bitvale,

17

Câu trả lời là trong Kotlin, nó sẽ hoạt động trong Java. IntelliJ sẽ chuyển đổi nó cho bạn nếu bạn sao chép và dán.

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

Điều này cũng sẽ hoạt động LinearLayoutManagervì nó có các phương pháp tương tự được sử dụng ở trên. Cụ thể là findLastVisibleItemPosition()getItemCount()( itemCounttrong Kotlin).


6
Tốt approache, nhưng có một vấn đề là những điều khoản trong nếu vòng lặp sẽ thực hiện multipltimes
Vishnu

bạn đã tìm được giải pháp cho những vấn đề tương tự chưa
Vishwa Pratap

1
@Vishnu Một biến Boolean đơn giản có thể khắc phục điều đó.
daka

8

Tôi đã không nhận được một giải pháp hoàn hảo cho các câu trả lời trên bởi vì nó đang kích hoạt hai lần ngay cả khi 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")
        }

1
Nhưng recyclerView.canScrollVertically(direction)hướng chỉ là bất kỳ số nguyên + vs - vì vậy nó có thể là 4 nếu nó ở dưới cùng hoặc -12 nếu nó ở trên cùng.
Xenolion

5

Thử đi

Tôi đã sử dụng các câu trả lời ở trên, nó luôn chạy khi bạn đến cuối chế độ xem người tái chế,

Nếu bạn chỉ muốn kiểm tra một lần xem nó có ở dưới đáy hay không? Ví dụ: - Nếu tôi có danh sách 10 mục bất cứ khi nào tôi chuyển xuống dưới cùng, nó sẽ hiển thị cho tôi và nếu tôi cuộn từ trên xuống dưới, nó sẽ không in nữa, và nếu bạn thêm nhiều danh sách và bạn đến đó, nó sẽ hiển thị lại.

Lưu ý: - Sử dụng phương pháp này khi bạn xử lý bù trừ trong lần truy cập API

  1. Tạo một lớp có tên là 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);
    
        }
    
  2. sử dụng lớp này như thế này

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

Nó chạy hoàn hảo ở cuối của tôi, thông báo cho tôi nếu bạn đang gặp bất kỳ vấn đề nào


Điều này rất hữu ích :)
Devrath

4

Có sự triển khai của tôi, nó rất hữu ích cho StaggeredGridLayout.

Sử dụng :

private EndlessScrollListener scrollListener =
        new EndlessScrollListener(new EndlessScrollListener.RefreshList() {
            @Override public void onRefresh(int pageNumber) {
                //end of the list
            }
        });

rvMain.addOnScrollListener(scrollListener);

Triển khai trình nghe:

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

Tại sao phải thêm độ trễ 200 mili giây để gọi lại?
Mani

@Mani Tôi không nhớ chính xác bởi thời điểm này, nhưng có lẽ tôi thực hiện nó chỉ cho hiệu ứng mượt mà ...
Aleksey Timoshchenko

3

Tôi cũng đang tìm kiếm câu hỏi này nhưng tôi không tìm thấy câu trả lời khiến tôi hài lòng, vì vậy tôi tạo ra hiện thực hóa của riêng về RecyclerView.

các giải pháp khác kém chính xác hơn là của tôi. ví dụ: nếu mục cuối cùng là khá lớn (nhiều văn bản) thì việc gọi lại các giải pháp khác sẽ đến sớm hơn nhiều sau đó RecyclerView thực sự đạt đến đáy.

sự thận trọng của tôi khắc phục sự cố này.

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

thì trong hoạt động / phân đoạn của bạn:

override fun onCreate() {
    customRecyclerView.layoutManager = LinearLayoutManager(context)
}

override fun onResume() {
    super.onResume()
    customRecyclerView.setTopAndBottomListener(this)
}

override fun onStop() {
    super.onStop()
    customRecyclerView.setTopAndBottomListener(null)
}

hy vọng nó sẽ hepl ai đó ;-)


1
phần nào của giải pháp từ những người khác không làm bạn hài lòng? bạn nên giải thích nó đầu tiên trước khi đưa ra câu trả lời của bạn
Zam chìm

@ZamSunk vì các giải pháp khác kém chính xác hơn của tôi. Ví dụ: nếu mục cuối cùng là khá nhỏ (rất nhiều văn bản) thì việc gọi lại các giải pháp khác sẽ đến sớm hơn nhiều sau đó RecyclerView thực sự chạm đến cuối. sự thận trọng của tôi khắc phục sự cố này.
Andriy Antonov

2

Sau khi không hài lòng với hầu hết các câu trả lời khác trong chủ đề này, tôi đã tìm thấy thứ mà tôi nghĩ là tốt hơn và không có ở bất kỳ đâu trên đây.

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

Điều này rất đơn giản và sẽ đạt được chính xác một lần trong mọi điều kiện khi bạn đã cuộn đến đầu hoặc cuối.


0

Đây là giải pháp của tôi:

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

bạn lấy ở đâu đó state là enum?
raditya gumay

Nhà nước là một enum của tôi, đây là lá cờ để kiểm tra trạng thái cho màn hình đó (tôi đã sử dụng MVVM với databinding tại ứng dụng của tôi)
Alex Zezekalo

làm thế nào bạn sẽ thực hiện gọi lại nếu không có kết nối internet?
Aks4125

0

Chúng ta có thể sử dụng Giao diện để có được vị trí

Giao diện: Tạo giao diện cho người nghe

public interface OnTopReachListener { void onTopReached(int position);}

Hoạt động :

mediaRecycleAdapter = new MediaRecycleAdapter(Class.this, taskList); recycle.setAdapter(mediaRecycleAdapter); mediaRecycleAdapter.setOnSchrollPostionListener(new OnTopReachListener() {
@Override
public void onTopReached(int position) {
    Log.i("Position","onTopReached "+position);  
}
});

Bộ chuyển đổi:

public void setOnSchrollPostionListener(OnTopReachListener topReachListener) {
    this.topReachListener = topReachListener;}@Override public void onBindViewHolder(MyViewHolder holder, int position) {if(position == 0) {
  topReachListener.onTopReached(position);}}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.