Endless Scroll RecyclerView с разнородными размерами массивов от API

В настоящее время я использую scrollListener для включения бесконечного прокрутки - вы можете найти мой вопрос и ответ Вилена здесь:

Добавление элементов в Endless Scroll RecyclerView с помощью ProgressBar в нижняя

Это работает, когда у вас есть размер тот же , когда вы активируете свой адаптер при прокрутке. Например, в коде Вилена он увеличивает размер набора данных с помощью Количество, 15, каждый раз, когда он прокручивает.

Однако в реальном мире это часто бывает не так. У вас есть разнородные размеры данных. В моем примере я хотел бы сделать следующее:

Я хотел бы реализовать геопространственный поиск, чтобы он возвращал список ближайших мне мест. Теперь, если я буду сидеть в пустыне, мой вызов REST API вернет места в пределах 10 км от меня, и это может быть только 4 места (хижина верблюда, пирамиды, водопоя и кокосовое дерево).

Заполнение 4 элементов в recyclerview не сделает recyclerview достаточно длинным, чтобы позволить ему прокручивать дальше, чтобы мое приложение снова вызвало вызов API, чтобы найти больше мест ближе ко мне. Следующие 10 км (20 км от моего текущего местоположения) могут быть городом с большим количеством мест, чтобы вернуться, но поскольку для повторного просмотра недостаточно времени для прокрутки, вызов API не выполняется.

Я также сделал крошечное репо, чтобы продемонстрировать, что прослушиватель прокрутки не запускается, когда recyclerview слишком короткий:

https://github.com/Winghin2517/DissimilarDataSetSizeRV.git

Как я могу обойти эту проблему?

Я почти чувствую, что scrolllistener должен запускаться до тех пор, пока recyclerview не заполнит экран, но как я могу определить, когда экран будет заполнен, так как не существует обратного вызова для recyclerview-fill-up-screen, насколько я знаю .

Я попытался прекратить изменять boolean loading, чтобы увидеть, что это поможет, но без логической проверки состояния загрузки, progressBar не обязательно будет удален, что приведет к этому типу эффекта:

Ответ 1

Благодаря этой теме мне удалось определить, достаточно ли повторного просмотра для прокрутки: Как узнать, есть ли у RecyclerView достаточно контента для прокрутки?

Затем я немного сыграл с кодом и, похоже, работает таким образом.

Я создаю две новые переменные:

final static String PROGRESS_BAR = "progressBar"; //this will be the unique object that will be added to the arraylist when you want to display the progress bar
boolean progressBarAdded; //the progressbar must only be added once so this will keep track of it when it is added.

   //adapter code is changed to take into account when the recyclerview can/cannot scroll
    mAdapter.setOnLoadMoreListener(new MyAdapter.OnLoadMoreListener() {
        @Override
        public void onLoadMore() {
            //add progress item
            //we can scroll and progress bar is not yet added
            if (!progressBarAdded && llm.findLastCompletelyVisibleItemPosition() != mAdapter.getItemCount() - 1) {
                progressBarAdded = true;
                myDataset.add(PROGRESS_BAR);
                mAdapter.notifyItemInserted(myDataset.size() - 1);
            }

            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //remove progress item
                    boolean removed = myDataset.remove(PROGRESS_BAR);
                    if (removed) {
                        mAdapter.notifyItemRemoved(myDataset.size());
                        progressBarAdded = false; //progress bar is now removed
                    }
                    //add items one by one
                    for (int i = 0; i < 3; i++) {
                        myDataset.add("Item" + (myDataset.size() + 1));
                        mAdapter.notifyItemInserted(myDataset.size());
                    }
                //we can scroll
                if(llm.findLastCompletelyVisibleItemPosition()!=mAdapter.getItemCount()-1) {
                    mAdapter.setLoaded();
                }
            }
        }, 500);

Затем меняем scrolllistener следующим образом:

    if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {

        final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
                if (totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    // End has been reached
                    // Do something
                    if (onLoadMoreListener != null) {
                        onLoadMoreListener.onLoadMore();
                    }
                    if (linearLayoutManager.findLastCompletelyVisibleItemPosition() != MyAdapter.this.getItemCount() - 1) { //determines whether the recyclerview is full or not. If not full, this will cause it to continue to load
                        loading = true;
                    }
                }
            }
        });
    }

Также убедитесь, что вы учитываете новый getItemType, поскольку каждый раз, когда он обнаруживает строку «progressBar», она должна загружать зрителя для progressBar:

@Override
public int getItemViewType(int position) {
    return mDataset.get(position) != MainActivity.PROGRESS_BAR ? VIEW_ITEM : VIEW_PROG;
}

Ответ 2

Вы можете попробовать этот подход (приведенный ниже код относится к одному из моих приложений, поэтому вам придется заполнить пробелы, я просто добавил части, которые помогут решить вашу проблему)>

public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener 
// Or use yourListView.setOnScrollListener(this);
{

    int preLast = -1;

    @Override
    public void onScroll(AbsListView lw, final int aFirstVisibleItemIndex, final int aVisibleItemCount, final int aTotalItemsCount) {
        int id = mListView.getId(); // Or use case android.R.id.list:     

        if(lw.getId() == id) {
                     // Sample calculation to determine if the last item is fully visible.
            final int lastItem = aFirstVisibleItemIndex + aVisibleItemCount;
            if(lastItem > 0 && lastItem == aTotalItemsCount) // lastItem > 0 helps to avoid continuous call if the list is empty.
            {
                if(preLast != lastItem) { //to avoid multiple calls for last item
                    preLast = lastItem;

                }
              }
        }
      }
}

@Override //it will be called before any calls to Adapter.getView(.....)
public void onScrollStateChanged(AbsListView view, int scrollState) {

}

И теперь, и когда вы решаете, что больше нет данных для извлечения из вашего сервера (чтобы заполнить больше элементов в вашем списке), и вы хотите избежать просмотра списка, чтобы продолжать/активировать ваш (при загрузке анимации), вы Может просто установить значение preLast для значения lastItem.