스크롤하는 동안 RecyclerView가 최하위 위치에 도달 할 때 감지


94

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

스크롤하는 동안 RecyclerView가 최하위 위치에 도달하는시기를 알아야합니다. 가능합니까? 그렇다면 어떻게?




1
recyclerView.canScrollVertically (int 방향); 여기에 전달해야하는 매개 변수는 무엇입니까?
Adrian

@Adrian, 만약 당신이 여전히이 질문에 관심이 있다면 :)))) stackoverflow.com/a/48514857/6674369
Andriy Antonov

답변:


178

그것을하는 간단한 방법도 있습니다

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

        }
    }
});

방향 정수 : 업은 -1, 다운은 1, 0은 항상 거짓을 반환합니다.


17
onScrolled ()는 감지가 두 번 발생하는 것을 방지합니다.
Ian Wambai

내 RecyclerView에 ScrollListnerEvent를 추가 할 수 없습니다. onScrollStateChanged의 코드가 실행되지 않습니다.
Sandeep Yohans 2018-08-28

1
한 번만 트리거되도록하려면 (Toast를 한 번 표시) if 문에 부울 플래그 (클래스 멤버 변수)를 추가하고 다음과 같은 경우 true로 설정합니다. if (!recyclerView.canScrollVertically(1) && !mHasReachedBottomOnce) { mHasReachedBottomOnce = true Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();}
bastami82

@ bastami82 당신은 토스트 두 번 인쇄를 방지하기위한 제안으로 나는 트릭을 사용하지만 그것은 작동하지 않습니다
인 Vishwa 프라 탑

4
추가 newState == RecyclerView.SCROLL_STATE_IDLEif문이 작동합니다.
Lee Chun Hoe

58

반복 호출을 방지하려면이 코드를 사용하십시오.

    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
나는 대답 할 수 있지만 이것은 간단하고 완벽합니다. 감사합니다
Vishwa Pratap

@Venkatesh 환영합니다.
Milind Chaudhary

나에게 다른 버그를주지 않은 유일한 답변
Fluffy T Rex

@TheFluffyTRex 감사합니다
Milind Chaudhary

46

recyclerview에 addOnScrollListener ()를 구현하기 만하면됩니다. 그런 다음 스크롤 리스너 내부에서 아래 코드를 구현합니다.

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
mIsLoading에 대해 조금 설명해 주시겠습니까? 설정하거나 값을 변경하는 곳은 어디입니까?
MiguelHincapieC

mLoading은 뷰가 연결되었는지 여부를 나타내는 부울 변수입니다. 예를 들어; 앱이 recyclerview를 채우는 경우 mLoading은 true가되고 목록이 채워지면 false가됩니다.
Febi M Felix

1
이 솔루션은 제대로 작동하지 않습니다. 한 번 봐 걸릴 stackoverflow.com/a/48514857/6674369을
안드리 안토 노프에게

3
방법을 확인할 수 없음 'findFirstVisibleItemPosition'android.support.v7.widget.RecyclerView
이만 Marashi

2
@Iman Marashi, 캐스트해야합니다 (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition(). 이 일.
bitvale jul.

16

답변은 Kotlin에 있으며 Java에서 작동합니다. 복사하여 붙여 넣으면 IntelliJ가 자동으로 변환해야합니다.

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

이것은 LinearLayoutManager위에서 사용 된 것과 동일한 방법을 가지고 있기 때문에 작동합니다 . 즉 findLastVisibleItemPosition(), getItemCount()( itemCountKotlin에서).


6
approache 좋은,하지만 하나의 문제는 루프가 multipltimes를 실행하는 경우에 문입니다
비슈누

같은 문제에 대한 해결책을 얻었습니까?
Vishwa Pratap 2019

1
@Vishnu 간단한 부울 변수로 해결할 수 있습니다.
daka

8

위의 답변으로 완벽한 솔루션을 얻지 못했습니다. 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
그러나 recyclerView.canScrollVertically(direction)방향은 정수 + vs-이므로 아래쪽에 있으면 4, 위쪽에 있으면 -12가 될 수 있습니다.
Xenolion

5

이 시도

위의 답변을 사용했는데 리사이클 러 뷰가 끝날 때 항상 실행됩니다.

바닥인지 아닌지 한 번만 확인하고 싶다면? 예 :-내가 아래로 갈 때마다 10 개 항목의 목록이 있으면 나를 표시하고 위에서 아래로 스크롤하면 다시 인쇄되지 않으며 더 많은 목록을 추가하고 거기로 가면 다시 표시됩니다.

참고 : -API 적중시 오프셋 을 처리 할 때이 방법을 사용하십시오 .

  1. 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. 이 수업을 이렇게 사용하십시오

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

내 끝에서 완벽하게 달리고있어 문제가 생기면 칭찬 해줘


이 : 매우 도움이 되었습니까
Devrath

4

내 구현이 있으며 StaggeredGridLayout.

사용법 :

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

rvMain.addOnScrollListener(scrollListener);

리스너 구현 :

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

콜백에 200 밀리 초 지연을 추가하는 이유는 무엇입니까?
Mani

나는이 순간에 의해 정확히 기억하지 @Mani,하지만 어쩌면 난 그냥 부드러운 효과를 위해 일을 ...
알렉세이 Timoshchenko

3

나는 또한이 질문을 찾고 있었지만 나를 만족시키는 답을 찾지 못했기 때문에 recyclerView의 자체 실현을 만듭니다.

다른 솔루션은 내 것보다 덜 정확합니다. 예를 들어, 마지막 항목이 꽤 큰 경우 (텍스트가 많음) 다른 솔루션의 콜백이 훨씬 일찍 올 것이고 recyclerView가 실제로 바닥에 도달했습니다.

내 해결책이이 문제를 해결합니다.

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

그런 다음 활동 / 조각에서 :

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

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

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

누군가를 hepl하기를 바랍니다 ;-)


1
당신을 만족시키지 않는 다른 솔루션의 어떤 부분이 있습니까? 먼저 답변을 제안하기 전에 설명해야
잼 침몰

@ZamSunk cos 다른 솔루션은 내 것보다 덜 정확합니다. 예를 들어 마지막 항목이 꽤 작은 경우 (많은 텍스트) 다른 솔루션의 콜백이 훨씬 더 일찍 올 것이고 recyclerView가 실제로 바닥에 도달합니다. 내 해결책이이 문제를 해결합니다.
Andriy Antonov

2

이 스레드의 다른 대부분의 답변에 만족하지 못한 후 나는 더 낫고 여기 어디에도 없다고 생각하는 것을 발견했습니다.

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

이것은 간단하며 상단 또는 하단으로 스크롤했을 때 모든 조건에서 정확히 한 번만 치게됩니다.


0

이것은 내 해결책입니다.

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

state 그 열거 형은 어디에서 얻었 습니까?
raditya gumay

국가는 광산의 열거입니다, 이것은 (내 응용 프로그램에서 데이터 바인딩으로 MVVM을 사용)이 화면에 대한 상태를 확인하기위한 플래그이다
알렉스 Zezekalo

인터넷에 연결되지 않은 경우 콜백을 어떻게 구현합니까?
Aks4125

0

위치를 얻기 위해 인터페이스를 사용할 수 있습니다.

Interface : 리스너 용 인터페이스 생성

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

활동 :

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

어댑터 :

public void setOnSchrollPostionListener(OnTopReachListener topReachListener) {
    this.topReachListener = topReachListener;}@Override public void onBindViewHolder(MyViewHolder holder, int position) {if(position == 0) {
  topReachListener.onTopReached(position);}}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.