Kaydırma sırasında RecyclerView en alt konuma ulaştığında algıla


94

RecyclerView için bu koda sahibim.

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

Kaydırma sırasında RecyclerView'ın en alt konuma ne zaman ulaştığını bilmem gerekiyor. Mümkün mü ? Varsa nasıl?




1
recyclerView.canScrollVertically (int yön); Buraya geçmemiz gereken parametre ne olmalı?
Adrian

@Adrian, yine de bu soruyla ilgileniyorsanız :)))) stackoverflow.com/a/48514857/6674369
Andriy Antonov

Yanıtlar:


178

bunu yapmanın da basit bir yolu var

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

        }
    }
});

yön tamsayıları: Yukarı için -1, aşağı için 1, 0 her zaman yanlış döndürür.


17
onScrolled (), algılamanın iki kez gerçekleşmesini engeller.
Ian Wambai

ScrollListnerEvent'i RecyclerView'a ekleyemiyorum. OnScrollStateChanged içindeki kod çalıştırılmaz.
Sandeep Yohans

1
bunu yalnızca bir kez tetiklemek istiyorsanız (bir kez if (!recyclerView.canScrollVertically(1) && !mHasReachedBottomOnce) { mHasReachedBottomOnce = true Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();}
Toast'ı gösterin

@ Bastami82 Eğer tost iki kez baskı önlemek için önerdiği gibi ben hile kullanmak ama çalışmıyor
Vishwa Pratap

4
Ekle newState == RecyclerView.SCROLL_STATE_IDLEiçine ifdeyimi çalışacak.
Lee Chun Hoe

58

Tekrarlanan aramalardan kaçınmak için bu kodu kullanın

    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
Cevap vermeyi denedim ama bu benim için basit ve mükemmel. teşekkürler
Vishwa Pratap

@Venkatesh Hoşgeldiniz.
Milind Chaudhary

Bana başka böcek vermeyen tek cevap
The Fluffy T Rex

@TheFluffyTRex Teşekkürler
Milind Chaudhary

46

Sadece geri dönüşüm görünümünüze bir addOnScrollListener () uygulayın. Ardından kaydırma dinleyicisinin içinde aşağıdaki kodu uygulayın.

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 hakkında biraz bilgi verebilir misiniz lütfen? nerede kuruyorsunuz veya değeri değiştiriyorsunuz.
MiguelHincapieC

mLoading, görünümün devreye girip girmediğini gösteren bir boole değişkenidir. Örneğin; Uygulama geri dönüşüm görünümünü dolduruyorsa, mLoading doğru olur ve liste doldurulduktan sonra yanlış olur.
Febi M Felix

1
bu çözüm düzgün çalışmıyor. stackoverflow.com/a/48514857/6674369
Andriy Antonov

3
Yöntemini çözülemiyor 'findFirstVisibleItemPosition'üzerindeandroid.support.v7.widget.RecyclerView
Iman Marashi

2
@Iman Marashi, sen döküm gerekir: (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition(). Bu iş.
bitvale

16

Cevap Kotlin'de, Java'da çalışacak. Kopyalayıp yapıştırırsanız IntelliJ bunu sizin için dönüştürmelidir.

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

Bu, LinearLayoutManageryukarıda kullanılan yöntemlerin aynısına sahip olduğu için de işe yarayacaktır . Yani findLastVisibleItemPosition()ve getItemCount()( itemCountKotlin'de).


6
İyi bir yaklaşım, ancak sorunlardan biri, if döngüsündeki ifadeler birden çok kez yürütülecektir
Vishnu

aynı sorunlar için bir çözüm buldunuz mu
Vishwa Pratap

1
@Vishnu Basit bir Boolean değişkeni bunu düzeltebilir.
daka

8

Yukarıdaki cevaplardan mükemmel bir çözüm elde edemiyordum çünkü şu günlerde bile iki kez tetikliyordu 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
Ancak recyclerView.canScrollVertically(direction)yön, herhangi bir tam sayıdır + vs - bu nedenle, altta ise 4 veya üstte ise -12 olabilir.
Xenolion

5

Bunu dene

Yukarıdaki cevapları kullandım, geri dönüşümcü görünümünün sonuna gittiğinizde her zaman çalışır,

Dipte olup olmadığını sadece bir kez kontrol etmek istersen? Örnek: - 10 öğelik bir listeye sahip olduğumda, en alta gittiğimde beni gösterecek ve yukarıdan aşağıya kaydırırsam bir daha yazdırılmayacak ve daha fazla liste eklerseniz ve oraya giderseniz tekrar görüntülenecektir.

Not: - API isabetinde ofset ile uğraşırken bu yöntemi kullanın

  1. EndlessRecyclerViewScrollListener adlı bir sınıf oluşturun

        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. bu sınıfı böyle kullan

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

Sonumda mükemmel çalışıyor, herhangi bir sorunla karşılaşırsan bana haber ver


Bu çok yardımcı oldu :)
Devrath

4

Benim uygulamam var, çok faydalı StaggeredGridLayout .

Kullanım:

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

rvMain.addOnScrollListener(scrollListener);

Dinleyici uygulaması:

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

Geri aramaya neden 200 milisaniye gecikme eklenmeli?
Mani

@Mani Şu ana kadar tam olarak hatırlamıyorum, ama belki de sadece pürüzsüz bir etki için yaptım ...
Aleksey Timoshchenko

3

Ben de bu soruyu arıyordum ama beni tatmin eden cevabı bulamadım, bu yüzden recyclerView'ün kendi gerçekleştirimini yarattım.

diğer çözümler benimkinden daha az kesin. örneğin: son öğe oldukça büyükse (çok fazla metin), diğer çözümlerin geri araması çok daha erken gelecektir ve daha sonra recyclerView gerçekten dibe ulaşacaktır.

benim çözümüm bu sorunu çözdü.

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

sonra faaliyet / bölümünüzde:

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

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

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

umarım birisi ;-)


1
sizi tatmin etmeyen diğerlerinden çözümün hangi kısmı? cevabınızı önermeden önce bunu açıklamalısınız
Zam Sunk

@ZamSunk çünkü diğer çözümler benimkinden daha az kesin. örneğin, son öğe oldukça bitse (çok fazla metin), diğer çözümlerin geri araması çok daha erken gelecektir ve daha sonra recyclerView gerçekten dibe ulaşacaktır. benim çözümüm bu sorunu çözdü.
Andriy Antonov

2

Bu konudaki diğer cevapların çoğundan memnun kalmadıktan sonra, daha iyi olduğunu ve burada hiçbir yerde olmadığını düşündüğüm bir şey buldum.

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

Bu basittir ve yukarı veya aşağı kaydırdığınızda her koşulda tam olarak bir kez vurulacaktır.


0

Bu benim çözümüm:

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

Nereden aldın state o enum?
raditya gumay

Eyalet benim bir enum, bu ekranlar için durumları kontrol etmek için bayrak (uygulamamda veri bağlama ile MVVM kullandım)
Alex Zezekalo

İnternet bağlantısı yoksa geri aramayı nasıl uygularsınız?
Aks4125

0

Pozisyon almak için Arayüzü kullanabiliriz

Arayüz: Dinleyici için bir Arayüz oluşturun

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

Aktivite :

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

Adaptör:

public void setOnSchrollPostionListener(OnTopReachListener topReachListener) {
    this.topReachListener = topReachListener;}@Override public void onBindViewHolder(MyViewHolder holder, int position) {if(position == 0) {
  topReachListener.onTopReached(position);}}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.