Виявляє, коли RecyclerView досягає самого нижнього положення під час прокрутки


96

У мене є цей код для 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.canScrollVertical (напрямок int); Яким повинен бути такий параметр, який ми маємо передати тут?
Адріан,

@Adrian, на випадок, якщо вас все ще цікавить це питання :)))) 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
onScroll () запобігає виявленню двічі.
Ян Вамбаї

Я не можу додати ScrollListnerEvent до мого RecyclerView. Код у onScrollStateChanged не виконується.
Sandeep Yohans,

1
якщо ви хочете лише один раз активувати (показати Toast один раз), додайте логічний прапор (змінну члена класу) до оператора 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заяву буде працювати.
Lee Chun Hoe

61

Використовуйте цей код, щоб уникнути повторних дзвінків

    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 Ласкаво просимо.
Мілінд Чаудхарі

Єдина відповідь, яка не дала мені жодних інших помилок
The Fluffy T Rex

@TheFluffyTRex Дякую
Milind Chaudhary

47

Просто застосуйте addOnScrollListener () у своєму перегляді recycler. Потім всередині прослуховувача прокрутки реалізуйте наведений нижче код.

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

17

Відповідь у 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 Проста логічна змінна може це виправити.
дака

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)напрямок - це просто будь-яке ціле число + проти - тому воно може дорівнювати 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();
                }
            });
    

Це працює ідеально в моєму кінці, зауважте мені, якщо у вас виникли проблеми


Це було дуже корисно :)
Деврат,

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

Це просто і вдарить рівно один раз за будь-яких умов, коли ви прокручуєте угору або знизу.


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 із прив'язкою даних у своєму додатку)
Алекс Зезекало,

як би ви реалізували зворотний дзвінок, якщо немає підключення до Інтернету?
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);}}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.