Endless RecyclerView с ProgressBar для разбивки на страницы

Я использую RecyclerView и извлекаю объекты из API пакетами по десять. Для разбивки на страницы я использую EndlessRecyclerOnScrollListener .

Все работает нормально. Теперь все, что осталось, это добавить прокрутку прогресса в нижней части списка, в то время как следующая партия объектов извлекается API. Вот скриншот приложения Google Play Store, показывающего ProgressBar в том, что, безусловно, является RecyclerView :

введите описание изображения здесь

Проблема заключается в том, что ни RecyclerView ни EndlessRecyclerOnScrollListener имеют встроенной поддержки для отображения ProgressBar внизу, а следующая партия объектов извлекается.

Я уже видел следующие ответы:

1. Поместите неопределенный ProgressBar как нижний колонтитул в сетку RecyclerView .

2. Добавление элементов в Endless Scroll RecyclerView с помощью ProgressBar внизу .

Я не удовлетворен этими ответами (и тем же человеком). Это включает в себя shoehorning null объект в dataset на полпути, пока пользователь прокручивается, а затем вынимает его после того, как будет отправлена ​​следующая партия. Это похоже на хак, который обходит основную проблему, которая может или не может работать должным образом. И это вызывает немного резкости и искажения в списке

Использование SwipeRefreshLayout здесь не является решением. SwipeRefreshLayout включает в себя вытягивание сверху, чтобы извлечь новейшие элементы, и в любом случае он не отображает представление прогресса.

Может ли кто-нибудь предложить хорошее решение для этого? Мне интересно узнать, как Google внедрил это для своих приложений (в приложении Gmail тоже есть). Существуют ли какие-либо статьи, где это подробно показано? Все ответы и комментарии будут оценены. Спасибо.

Некоторые другие ссылки :

1. Разбиение страницы с помощью RecyclerView . (Превосходный обзор …)

2. Заголовок и нижний колонтитул RecyclerView . (Больше того же …)

3. Endless RecyclerView с ProgressBar внизу .

Я реализовал это в своем старом проекте, я сделал это следующим образом …

Я создал interface как ребята из ваших примеров

 public interface LoadMoreItems { void LoadItems(); } 

Затем я добавляю addOnScrollListener() на моем Adapter

  recyclerView.addOnScrollListener(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 (!loading && totalItemCount <= (lastVisibleItem + visibleThreshold)) { //End of the items if (onLoadMoreListener != null) { onLoadMoreListener.LoadItems(); } loading = true; } } }); 

onCreateViewHolder() - это то место, где я помещаю ProgressBar или нет.

 @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder vh; if (viewType == VIEW_ITEM) { View v = LayoutInflater.from(parent.getContext()).inflate( R.layout.list_row, parent, false); vh = new StudentViewHolder(v); } else { View v = LayoutInflater.from(parent.getContext()).inflate( R.layout.progressbar_item, parent, false); vh = new ProgressViewHolder(v); } return vh; } 

В моей MainActivity , где я LoadItems() для добавления других элементов:

 mAdapter.setOnLoadMoreListener(new LoadMoreItems() { @Override public void LoadItems() { DataItemsList.add(null); mAdapter.notifyItemInserted(DataItemsList.size() - 1); handler.postDelayed(new Runnable() { @Override public void run() { // remove progress item DataItemsList.remove(DataItemsList.size() - 1); mAdapter.notifyItemRemoved(DataItemsList.size()); //add items one by one //When you've added the items call the setLoaded() mAdapter.setLoaded(); //if you put all of the items at once call // mAdapter.notifyDataSetChanged(); } }, 2000); //time 2 seconds } }); 

Для получения дополнительной информации я просто последовал за этим Github repository ( Примечание: это использование AsyncTask может быть, оно полезно в качестве моего ответа, поскольку я сделал это вручную не с данными из API но он должен работать также ), и этот пост был полезен для меня бесконечно-recyclerview -с-прогресс-бар

Кроме того, я не знаю, назвали ли вы его, но я также нашел этот пост infin_scrolling_recyclerview , возможно, он также может помочь вам.

Если это не то, что вы ищете, дайте мне знать и скажите мне, что не так с этим кодом, и я попытаюсь изменить его по своему вкусу.

Надеюсь, поможет.

РЕДАКТИРОВАТЬ

Поскольку вы не хотите удалять элемент ... Я обнаружил, что я думаю, что один парень, который удаляет footer только на этом посту: diseño-android-endless-recyclerview .

Это для ListView но я знаю, что вы можете приспособить его к RecyclerView он не удаляет какой-либо элемент, который он просто помещает Visible/Invisible ProgressBar выглядит так: detecting-end-of-listview

Также взгляните на этот вопрос: android-implementing-progressbar-and-loading-for-endless-list-like-android

Есть и другой способ сделать это.

  • Сначала getItemCount вашего адаптера возвращает listItems.size() + 1
  • return VIEW_TYPE_LOADING в getItemViewType() для position >= listItems.size() . Таким образом, загрузчик будет отображаться только в конце списка просмотра ресайклеров. Единственная проблема с этим решением – даже после достижения последней страницы, будет показан загрузчик, поэтому, чтобы исправить то, что вы храните x-pagination-total-count в адаптере, и
  • то вы измените условие, чтобы вернуться к типу вида

    (position >= listItem.size())&&(listItem.size <= xPaginationTotalCount) .

Я только что придумал эту идею, что вы думаете?

ЗДЕСЬ ПРОСТО И ЧИСТЯЩИЙ ПОДХОД.

Внесите Endless Scrolling из этого руководства Codepath Guide, а затем выполните следующие шаги.

1. Добавьте индикатор выполнения в RecyclerView.

     

Здесь очень важны андроид: paddingBottom = “50dp” и android: clipToPadding = “false” .

2. Получить ссылку на индикатор выполнения.

 progressBar = findViewById(R.id.progressBar); 

3. Определите методы для отображения и скрытия индикатора выполнения.

 void showProgressView() { progressBar.setVisibility(View.VISIBLE); } void hideProgressView() { progressBar.setVisibility(View.INVISIBLE); } 

Мне нравится идея добавления держателя обзора прогресса к адаптеру, но он, как правило, приводит к некоторым уродливым логическим манипуляциям, чтобы получить то, что вы хотите. Подход держателя вида заставляет вас getItemCount() за дополнительным элементом getItemCount() колонтитула, возвращая значения возвращаемого значения getItemCount() , getItemViewType() , getItemId(position) и любого типа метода getItem(position) который вы можете включить.

Альтернативный подход – управлять видимостью ProgressBar на уровне Fragment или Activity , показывая или скрывая ProgressBar ниже RecyclerView когда загрузка начинается и заканчивается соответственно. Этого можно достичь, включив ProgressBar непосредственно в макет представления или добавив его в собственный class RecyclerView ViewGroup . Это решение, как правило, приведет к меньшему обслуживанию и уменьшению количества ошибок.

ОБНОВЛЕНИЕ. Мое предложение создает проблему, когда вы просматриваете резервную копию представления во время загрузки содержимого. ProgressBar будет придерживаться нижней части макета представления. Это, вероятно, не то поведение, которое вы хотите. По этой причине добавление держателя обзора прогресса в ваш адаптер, вероятно, лучшее, функциональное решение. Просто не забывайте охранять методы доступа к вашему элементу. 🙂

вот мое обходное решение без добавления поддельного предмета (в Котлине, но просто):

в вашем адаптере добавьте:

 private var isLoading = false private val VIEWTYPE_FORECAST = 1 private val VIEWTYPE_PROGRESS = 2 override fun getItemCount(): Int { if (isLoading) return items.size + 1 else return items.size } override fun getItemViewType(position: Int): Int { if (position == items.size - 1 && isLoading) return VIEWTYPE_PROGRESS else return VIEWTYPE_FORECAST } override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder { if (viewType == VIEWTYPE_FORECAST) return ForecastHolder(LayoutInflater.from(context).inflate(R.layout.item_forecast, parent, false)) else return ProgressHolder(LayoutInflater.from(context).inflate(R.layout.item_progress, parent, false)) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) { if (holder is ForecastHolder) { //init your item } } public fun showProgress() { isLoading = true } public fun hideProgress() { isLoading = false } 

теперь вы можете легко вызвать showProgress() перед вызовом API. и hideProgress() после вызова API.

Это решение вдохновлено решением Akshar Patels на этой странице. Я немного изменил его.

  1. При загрузке первых элементов вам будет приятно иметь центр ProgressBar.

  2. Мне не понравилось оставшееся пустое заполнение внизу, когда больше не было загружаемых предметов. Это решение было удалено с помощью этого решения.

Сначала XML:

    

Затем я добавил следующее программно.

Когда были загружены первые результаты, добавьте это в свой onScrollListener. Он перемещает ProgressBar от центра к основанию:

 ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) loadingVideos.getLayoutParams(); layoutParams.topToTop = ConstraintLayout.LayoutParams.UNSET; loadingVideos.setLayoutParams(layoutParams); 

Когда объектов больше нет, удалите прокладку внизу так:

 recyclerView.setPadding(0,0,0,0); 

Скройте и покажите свой ProgressBar, как обычно.

Другим подходом было бы запустить вызов API внутри onBindViewHolder и начальное место в элементах, чтобы просмотреть индикатор прогресса. После завершения вызова вы обновите представление (скройте ход и покажите полученные данные). Например, с Picasso для загрузки изображений, метод onBindViewHolder будет выглядеть следующим образом:

 @Override public void onBindViewHolder(final MovieViewHolder holder, final int position) { final Movie movie = items.get(position); holder.imageProgress.setVisibility(View.VISIBLE); Picasso.with(context) .load(NetworkingUtils.getMovieImageUrl(movie.getPosterPath())) .into(holder.movieThumbImage, new Callback() { @Override public void onSuccess() { holder.imageProgress.setVisibility(View.GONE); } @Override public void onError() { } }); } 

Как я вижу, есть два случая, которые могут появиться:

  1. где вы загружаете все предметы в светлой версии одним звонком (например, адаптер сразу знает, что ему придется иметь дело с 40 фотографиями, но загружает его по запросу -> случай, который я показал ранее с Picasso)
  2. где вы работаете с реальной ленивой загрузкой, и вы просите сервер предоставить вам дополнительный fragment данных. В этом случае первым предварительным условием является получение адекватного ответа от сервера с необходимой информацией. Например: {«offset»: 0, «total»: 100, «items»: [{items}]}

Там ответ означает, что вы получили первый кусок из 100 данных. Мой подход будет примерно таким:

Просмотр После получения первого fragmentа данных (например, 10) добавьте их в адаптер.

RecyclerView.Adapter.getItemCount Пока текущее количество доступных элементов ниже общей суммы (например, доступно 10, всего 100), в методе getItemCount вы возвращаете items.size () + 1

RecyclerView.Adapter.getItemViewType, если общий объем данных больше количества доступных элементов в адаптере и position = items.size () (т. Е. Вы фиктивно добавили элемент в метод getItemCount), в качестве вида типа вы возвращаете некоторый индикатор прогресса , В противном случае вы вернете нормальный тип макета

RecyclerView.Adapter.onCreateViewHolder Когда вас попросят использовать тип представления индикатора прогресса, все, что вам нужно сделать, – попросить своего докладчика получить дополнительный кусок элементов и обновить адаптер

Таким образом, в основном это подход, когда вам не нужно добавлять / удалять элементы из списка и где у вас есть контроль над ситуацией при запуске ленивой загрузки.

Вот пример кода:

 public class ForecastListAdapter extends RecyclerView.Adapter { private final Context context; private List items; private ILazyLoading lazyLoadingListener; public static final int VIEW_TYPE_FIRST = 0; public static final int VIEW_TYPE_REST = 1; public static final int VIEW_TYPE_PROGRESS = 2; public static final int totalItemsCount = 14; public ForecastListAdapter(List items, Context context, ILazyLoading lazyLoadingListener) { this.items = items; this.context = context; this.lazyLoadingListener = lazyLoadingListener; } public void addItems(List additionalItems){ this.items.addAll(additionalItems); notifyDataSetChanged(); } @Override public int getItemViewType(int position) { if(totalItemsCount > items.size() && position == items.size()){ return VIEW_TYPE_PROGRESS; } switch (position){ case VIEW_TYPE_FIRST: return VIEW_TYPE_FIRST; default: return VIEW_TYPE_REST; } } @Override public ForecastVH onCreateViewHolder(ViewGroup parent, int viewType) { View v; switch (viewType){ case VIEW_TYPE_PROGRESS: v = LayoutInflater.from(parent.getContext()).inflate(R.layout.forecast_list_item_progress, parent, false); if (lazyLoadingListener != null) { lazyLoadingListener.getAdditionalItems(); } break; case VIEW_TYPE_FIRST: v = LayoutInflater.from(parent.getContext()).inflate(R.layout.forecast_list_item_first, parent, false); break; default: v = LayoutInflater.from(parent.getContext()).inflate(R.layout.forecast_list_item_rest, parent, false); break; } return new ForecastVH(v); } @Override public void onBindViewHolder(ForecastVH holder, int position) { if(position < items.size()){ Forecast item = items.get(position); holder.date.setText(FormattingUtils.formatTimeStamp(item.getDt())); holder.minTemperature.setText(FormattingUtils.getRoundedTemperature(item.getTemp().getMin())); holder.maxTemperature.setText(FormattingUtils.getRoundedTemperature(item.getTemp().getMax())); } } @Override public long getItemId(int position) { long i = super.getItemId(position); return i; } @Override public int getItemCount() { if (items == null) { return 0; } if(items.size() < totalItemsCount){ return items.size() + 1; }else{ return items.size(); } } public class ForecastVH extends RecyclerView.ViewHolder{ @BindView(R.id.forecast_date)TextView date; @BindView(R.id.min_temperature)TextView minTemperature; @BindView(R.id.max_temperature) TextView maxTemperature; public ForecastVH(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } } public interface ILazyLoading{ public void getAdditionalItems(); }} 

Возможно, это вдохновит вас сделать что-то, что подойдет вашим потребностям

  • CardView не показывает Shadow в Android L
  • Автоматический вызов checkbox onCheckedChange при прокрутке списка?
  • Внедрение onScrollListener для обнаружения конца прокрутки в ListView
  • Просмотр списка фильтров Android
  • Как я могу заполнить ListView в JavaFX с помощью пользовательских объектов?
  • Маска ImageView с круглым фоном угла
  • VirtualizationStackPanel + MVVM + множественный выбор
  • как отображать извлеченные данные json в listview с использованием baseadapter
  • Изменение цвета полосы прокрутки в Android
  • EditText теряет содержимое при прокрутке в ListView
  • Как сделать элементы WPF ListView повторяющимися по горизонтали, например, горизонтальной полосой прокрутки?
  • Interesting Posts

    Получить текущий путь к папке

    Как «отлаживать» клавиатуру в Linux? Как нажатие клавиши и просмотр кода в терминале

    Android ADB прекратит применение команды, например, «force-stop» для ненарушенных устройств

    .NET Events – Что такое отправитель объекта и EventArgs?

    Запретить автоматическое удаление Outlook сообщений электронной почты

    Загрузка нескольких файлов с помощью Symfony2

    Бутстрап-grid с фиксированной оболочкой. Предотrotation стеков столбцов

    В Excel (или Numbers) какая формула может рассчитать количество дней в определенном месяце за 2 даты

    Предотrotation загрузки и печати файла PDF

    VirtualBox: разрешение 1366×768

    C # «Параметр недействителен». Создание нового растрового изображения

    В чем разница между jQuery: text () и html ()?

    Должен ли я использовать .done () и .fail () для нового кода jQuery AJAX вместо успеха и ошибки

    Android: Могу ли я включить / отключить фильтр намерений активности программно?

    Как отформатировать TimeSpan в XAML

    Давайте будем гением компьютера.