Mendeteksi saat RecyclerView mencapai posisi paling bawah saat menggulir

95

Saya memiliki kode ini untuk 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);

Saya perlu tahu kapan RecyclerView mencapai posisi paling bawah saat menggulir. Apa itu mungkin ? Jika ya, bagaimana caranya?

Adrian
sumber
1
recyclerView.canScrollVertically (arah int); Apa yang harus menjadi parameter seperti yang harus kita lewati di sini?
Adrian
@Adrian, jika Anda masih tertarik dengan pertanyaan ini :)))) stackoverflow.com/a/48514857/6674369
Andriy Antonov

Jawaban:

178

ada juga cara sederhana untuk melakukannya

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

        }
    }
});

arah bilangan bulat: -1 untuk atas, 1 untuk bawah, 0 akan selalu mengembalikan false.

Kaustubh Bhagwat
sumber
17
onScrolled () mencegah pendeteksian terjadi dua kali.
Ian Wambai
Saya tidak dapat menambahkan ScrollListnerEvent ke RecyclerView Saya. Kode di onScrollStateChanged tidak dijalankan.
Sandeep Yohans
1
jika Anda hanya ingin ini memicu sekali (tampilkan Toast sekali) tambahkan flag boolean (variabel anggota kelas) ke pernyataan if dan setel ke true inside if like: if (!recyclerView.canScrollVertically(1) && !mHasReachedBottomOnce) { mHasReachedBottomOnce = true Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();}
bastami82
@ bastami82 saya menggunakan trik seperti yang Anda sarankan untuk mencegah dua kali cetakan roti panggang tetapi tidak berhasil
Vishwa Pratap
4
Tambahkan newState == RecyclerView.SCROLL_STATE_IDLEke dalam ifpernyataan itu akan berhasil.
Lee Chun Hoe
60

Gunakan kode ini untuk menghindari panggilan berulang

    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");
                
            }
        }
    });
Milind Chaudhary
sumber
2
Saya mencoba mungkin menjawab tetapi ini sederhana dan cocok untuk saya. terima kasih
Vishwa Pratap
@Venkatesh Selamat datang.
Milind Chaudhary
Satu-satunya jawaban yang tidak memberi saya bug lain
The Fluffy T Rex
@TheFluffyTRex Terima kasih
Milind Chaudhary
46

Cukup terapkan addOnScrollListener () di recyclerview Anda. Kemudian di dalam pendengar gulir, terapkan kode di bawah ini.

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
            }
        }
    };
Febi M Felix
sumber
1
Bisakah Anda menjelaskan sedikit tentang mIsLoading? dimana kamu mengaturnya atau mengubah nilai.
MiguelHincapieC
mLoading hanyalah variabel boolean yang menunjukkan apakah tampilan digunakan atau tidak. Misalnya; jika aplikasi mengisi recyclerview, mLoading akan bernilai true dan menjadi false setelah daftar terisi.
Febi M Felix
1
solusi ini tidak berfungsi dengan baik. lihat di stackoverflow.com/a/48514857/6674369
Andriy Antonov
3
Tidak dapat menyelesaikan metode 'findFirstVisibleItemPosition'padaandroid.support.v7.widget.RecyclerView
Iman Marashi
2
@Iman Marashi, Anda perlu cor: (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition(). Pekerjaan ini.
bitvale
16

Jawabannya ada di Kotlin, ini akan berfungsi di Java. IntelliJ harus mengubahnya untuk Anda jika Anda menyalin dan menempel.

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

Ini juga akan berfungsi LinearLayoutManagerkarena memiliki metode yang sama yang digunakan di atas. Yaitu findLastVisibleItemPosition()dan getItemCount()( itemCountdi Kotlin).

daka
sumber
6
Pendekatan yang baik, tetapi satu masalah adalah pernyataan jika loop akan dieksekusi berkali-kali
Wisnu
apakah Anda mendapatkan solusi untuk masalah yang sama
Vishwa Pratap
1
@ Wisnu Variabel Boolean sederhana dapat memperbaikinya.
daka
8

Saya tidak mendapatkan solusi sempurna dengan jawaban di atas karena itu memicu dua kali bahkan pada 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")
        }
Manoj Perumarath
sumber
1
Tapi recyclerView.canScrollVertically(direction)arahnya hanyalah bilangan bulat + vs - jadi bisa 4 jika di bawah atau -12 jika di atas.
Xenolion
5

Coba ini

Saya telah menggunakan jawaban di atas, itu selalu berjalan ketika Anda akan pergi di ujung tampilan pendaur ulang,

Jika Anda ingin memeriksa hanya satu kali apakah di bawah atau tidak? Contoh: - Jika saya memiliki daftar 10 item setiap kali saya pergi ke bawah itu akan menampilkan saya dan lagi jika saya menggulir dari atas ke bawah itu tidak akan mencetak lagi, dan jika Anda menambahkan lebih banyak daftar dan Anda pergi ke sana itu akan ditampilkan lagi.

Catatan: - Gunakan metode ini saat Anda menangani offset dalam menekan API

  1. Buat kelas bernama 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. gunakan kelas ini seperti ini

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

Ini berjalan sempurna di akhir saya, beri tahu saya jika Anda mendapatkan masalah apa pun

Sunil
sumber
Ini sangat membantu :)
Devrath
4

Ada implementasi saya, itu sangat berguna untuk StaggeredGridLayout.

Penggunaan:

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

rvMain.addOnScrollListener(scrollListener);

Implementasi pendengar:

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);
}  }
Aleksey Timoshchenko
sumber
Mengapa menambahkan penundaan 200 milidetik ke panggilan balik?
Mani
@Mani Saya tidak ingat persis saat ini, tapi mungkin saya melakukannya hanya untuk efek halus ...
Aleksey Timoshchenko
3

Saya juga mencari pertanyaan ini tetapi saya tidak menemukan jawaban yang memuaskan saya, jadi saya membuat realisasi recyclerView sendiri.

solusi lain kurang tepat dibandingkan solusi saya. misalnya: jika item terakhir cukup besar (banyak teks) maka callback solusi lain akan datang jauh lebih awal, kemudian recyclerView benar-benar mencapai paling bawah.

solusi saya memperbaiki masalah ini.

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

lalu di aktivitas / fragmen Anda:

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

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

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

berharap itu akan membantu seseorang ;-)

Andriy Antonov
sumber
1
bagian mana dari solusi dari orang lain yang tidak memuaskan Anda? Anda harus menjelaskannya terlebih dahulu sebelum menyarankan jawaban Anda
Zam Sunk
@ZamSunk karena solusi lain kurang tepat dibandingkan solusi saya. misalnya jika item terakhir cukup sedikit (banyak teks) maka callback solusi lain akan datang jauh lebih awal kemudian recyclerView benar-benar mencapai bagian bawah. solusi saya memperbaiki masalah ini.
Andriy Antonov
2

Setelah tidak puas dengan sebagian besar jawaban lain di utas ini, saya menemukan sesuatu yang menurut saya lebih baik dan tidak ada di mana pun di sini.

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

Ini sederhana dan akan memukul tepat satu kali dalam semua kondisi ketika Anda menggulir ke atas atau bawah.

noone392
sumber
0

Ini solusi saya:

    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
        }
    }
}
Alex Zezekalo
sumber
dari mana Anda mendapatkan state enum itu?
raditya gumay
Status adalah enum saya, ini adalah bendera untuk memeriksa status untuk layar itu (saya menggunakan MVVM dengan penyatuan data di aplikasi saya)
Alex Zezekalo
bagaimana Anda menerapkan panggilan balik jika tidak ada konektivitas internet?
Aks4125
0

Kita bisa menggunakan Interface untuk mendapatkan posisi tersebut

Antarmuka: Buat Antarmuka untuk pendengar

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

Aktivitas :

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

Adaptor:

public void setOnSchrollPostionListener(OnTopReachListener topReachListener) {
    this.topReachListener = topReachListener;}@Override public void onBindViewHolder(MyViewHolder holder, int position) {if(position == 0) {
  topReachListener.onTopReached(position);}}
Amuthan S
sumber