无尽的RecyclerView与ProgressBar分页

我正在使用一个RecyclerView并从API中以十个批次的方式获取对象。 对于分页,我使用EndlessRecyclerOnScrollListener

这一切正常工作。 现在剩下的就是在列表底部添加一个进度微调器,而下一批对象由API提取。 以下是Google Play商店应用程序的屏幕截图,其中显示了ProgressBar中肯定是RecyclerView

在这里输入图像说明

问题是, RecyclerViewEndlessRecyclerOnScrollListener都没有内置的支持,在下一批对象被提取时在底部显示ProgressBar

我已经看到了以下答案:

1. 将不确定的ProgressBar作为页脚放置在RecyclerView网格中

2. 将项目添加到底部的ProgressBar Endless Scroll RecyclerView

我不满意这些答案(由同一个人)。 这涉及在用户正在滚动时在数据集的中途阻塞一个null对象,然后在下一批次交付之后将其取出。 它看起来像一个黑客,回避可能或可能无法正常工作的主要问题。 这会导致列表中的一些干扰和失真

使用SwipeRefreshLayout不是一个解决scheme。 SwipeRefreshLayout涉及到从顶端拉取新的项目,它并不显示进度视图。

有人可以提供一个很好的解决scheme吗? 我有兴趣了解Google如何为自己的应用程序实现这个function(Gmail应用程序也有这个function)。 有没有什么文章详细显示? 所有的答案和评论将不胜感激。 谢谢。

其他一些参考

1. RecyclerView分页 。 (非常棒的概述…)

2. RecyclerView页眉和页脚 。 (更多相同…)

3. 无限的RecyclerViewProgressBar在底部

我在我的旧项目中实现了这一点,我做了如下…

我已经创build了一个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数据,但它也应该工作 )也是这篇文章对我有帮助endless-recyclerview -with-进度条

另外我不知道你是否命名它,但我也发现这个postinfinite_scrolling_recyclerview ,也许它也可以帮助你。

如果这不是你正在寻找的,告诉我这个代码有什么问题,我会尽力修改它。

希望能帮助到你。

编辑

既然你不想删除一个项目…我发现我猜一个人只删除这个post的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
  • position >= listItems.size() getItemViewType()返回VIEW_TYPE_LOADING 。 这样,装载程序将只显示在回收站视图列表的末尾。 这个解决scheme的唯一问题是,即使在到达最后一页之后,加载程序也会显示出来,所以为了解决这个问题,你需要把x-pagination-total-count在适配器中,
  • 那么你改变条件返回视图types

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

现在我想出了这个想法,你觉得怎么样?

我喜欢将一个进度视图持有者添加到适配器的想法,但它往往会导致一些丑陋的逻辑操纵来获得你想要的。 视图持有者的方法迫使你通过getItemCount()getItemViewType()getItemId(position)和任何一种你可能想要包含的getItem(position)方法的返回值来处理额外的页脚项。

另一种方法是在加载开始和结束时,通过在RecyclerView下方显示或隐藏ProgressBar来pipe理ProgressBarFragmentActivity级别的可见性。 这可以通过将ProgressBar直接包含在视图布局中或将其添加到自定义的RecyclerView ViewGroup类来实现。 这个解决scheme通常会导致更less的维护和更less的错误。

更新:当您在加载内容时向上滚动视图时,我的build议会出现问题。 ProgressBar将坚持视图布局的底部。 这可能不是你想要的行为。 出于这个原因,向您的适配器添加进度视图保持器可能是最好的function解决scheme。 只是不要忘记保护你的物品存取方法。 🙂

这里是简单和清洁的方法。

从这个代码path指南实现无尽的滚动,然后按照以下步骤。

1.在RecyclerView下添加进度条。

  <android.support.v7.widget.RecyclerView android:id="@+id/rv_movie_grid" android:layout_width="0dp" android:layout_height="0dp" android:paddingBottom="50dp" android:clipToPadding="false" android:background="@android:color/black" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> </android.support.v7.widget.RecyclerView> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="invisible" android:background="@android:color/transparent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> 

这里android:paddingBottom =“50dp”android:clipToPadding =“false”是非常重要的。

2.获取进度条的参考。

 progressBar = findViewById(R.id.progressBar); 

3.定义显示和隐藏进度条的方法。

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

不同的方法是在onBindViewHolder中启动API调用,并初始化放置到项目中查看一些进度指示器。 通话结束后,您更新视图(隐藏进度并显示接收的数据)。 例如用毕加索图片加载,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张照片,但是按需下载 – >我之前在毕加索展示的情况)
  2. 在那里你正在使用真正的懒惰加载,你要求服务器给你额外的数据块。 在这种情况下,第一个先决条件是从服务器获得足够的响应和必要的信息。 前面的例子{“offset”:0,“total”:100,“items”:[{items}]}

有响应意味着您收到了总共100个数据的第一部分。 我的做法是这样的:

查看获得第一大块数据(如10)后,将它们添加到适配器。

RecyclerView.Adapter.getItemCount只要当前可用项目的数量低于总数量(例如可用10;总数100),在getItemCount方法中,您将返回items.size()+ 1

RecyclerView.Adapter.getItemViewType如果数据总量大于适配器中可用项目的数量,并且position = items.size()(即,您已经在getItemCount方法中虚构地添加项目),则视图types返回一些进度指示器。 否则,你将返回正常的布局types

RecyclerView.Adapter.onCreateViewHolder当您被要求使用进度指示器视图types时,您只需要让演示者获得更多的项目并更新适配器

所以基本上,这是方法,你不必添加/删除列表中的项目,你可以控制的情况下,当延迟加载将被触发。

这是代码示例:

 public class ForecastListAdapter extends RecyclerView.Adapter<ForecastListAdapter.ForecastVH> { private final Context context; private List<Forecast> 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<Forecast> items, Context context, ILazyLoading lazyLoadingListener) { this.items = items; this.context = context; this.lazyLoadingListener = lazyLoadingListener; } public void addItems(List<Forecast> 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(); }} 

也许这会激励你做出适合你需要的东西