Обнаруживать, когда RecyclerView достигает самого нижнего положения при прокрутке

95

У меня есть этот код для 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 достигает самой нижней позиции при прокрутке. Является ли это возможным ? Если да, то как?

Адриан
источник
Попробуйте stackoverflow.com/questions/27841740/…
Рагхавендра
stackoverflow.com/questions/26543131/…
Бхавеш Рангани
1
recyclerView.canScrollVertical (направление int); Какой должен быть параметр, который мы должны здесь передать?
Адриан
@ Адриан, если вас все еще интересует этот вопрос :)))) stackoverflow.com/a/48514857/6674369
Андрей Антонов

Ответы:

179

есть также простой способ сделать это

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 всегда будет возвращать false.

Каустубх Бхагват
источник
17
onScrolled () предотвращает повторное обнаружение дважды.
Ian Wambai
Я не могу добавить ScrollListnerEvent в My RecyclerView. Код в onScrollStateChanged не выполняется.
Сандип Йоханс
1
если вы хотите, чтобы это срабатывало только один раз (показывать тост один раз), добавьте логический флаг (переменную члена класса) в оператор if и установите его в значение true внутри, если, например: if (!recyclerView.canScrollVertically(1) && !mHasReachedBottomOnce) { mHasReachedBottomOnce = true Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();}
bastami82
@ bastami82 Я использую уловку, как вы предлагаете, для предотвращения двойной печати тостов, но он не работает
Вишва Пратап
4
Добавить newState == RecyclerView.SCROLL_STATE_IDLEв ifзаявление будет работать.
Ли Чун Хо
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
Я попробовал ответить, но это просто и идеально подходит для меня. спасибо
Вишва Пратап
@Venkatesh Добро пожаловать.
Милинд Чаудхари
Единственный ответ, который не дал мне других ошибок
The Fluffy T Rex
@TheFluffyTRex Спасибо
Милинд Чаудхари
46

Просто реализуйте addOnScrollListener () в своем recyclerview. Затем внутри слушателя прокрутки реализуйте приведенный ниже код.

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 будет истинным и станет ложным после заполнения списка.
Febi M Felix
1
это решение не работает должным образом. взгляните на stackoverflow.com/a/48514857/6674369
Андрей Антонов
3
Невозможно разрешить метод 'findFirstVisibleItemPosition'наandroid.support.v7.widget.RecyclerView
Иман Мараши
2
@Iman Marashi, вам нужно гипс: (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition(). Эта работа.
bitvale
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()( itemCountв Котлине).

дака
источник
6
Хороший подход, но одна проблема - это утверждения в цикле if, которые будут выполняться многократно
Вишну,
получили ли вы решение тех же проблем
Вишва Пратап,
1
@Vishnu Простая логическая переменная может исправить это.
daka 05
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, если оно вверху.
Ксенолион
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 Я точно не помню к этому моменту, но, может быть, я сделал это просто для плавного эффекта ...
Алексей Тимощенко
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)
}

надеюсь, это поможет кому-то ;-)

Андрей Антонов
источник
1
какая часть решения от других вас не удовлетворила? вы должны сначала объяснить это, прежде чем предлагать свой ответ
Zam Sunk
@ZamSunk, потому что другие решения менее точны, чем мои. например, если последний элемент довольно бит (много текста), то обратный вызов других решений произойдет намного раньше, чем recyclerView действительно достигнет дна. мое решение исправить эту проблему.
Андрей Антонов
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
        }
    }
});

Это просто и ударит ровно один раз при любых условиях при прокрутке вверх или вниз.

нет392
источник
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 это перечисление?
радитья гумай
State - это мое перечисление, это флаг для проверки состояний для этих экранов (я использовал MVVM с привязкой данных в моем приложении)
Alex Zezekalo
как бы вы реализовали обратный вызов, если бы нет подключения к Интернету?
Aks4125
0

Мы можем использовать интерфейс для получения позиции

Интерфейс: создать интерфейс для слушателя

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);}}
Амутан С
источник