带有用于分页的 ProgressBar 的无尽 RecyclerView
Endless RecyclerView with ProgressBar for pagination
我正在使用 RecyclerView
and fetching objects from an API in batches of ten. For pagination, I use EndlessRecyclerOnScrollListener
。
一切正常。现在剩下的就是在列表底部添加一个进度微调器,而下一批对象由 API 获取。这是 Google Play 商店应用程序的屏幕截图,显示 ProgressBar
肯定是 RecyclerView
:
问题是,RecyclerView
和 EndlessRecyclerOnScrollListener
都不 built-in 支持在获取下一批对象时在底部显示 ProgressBar
.
我已经看过以下答案:
1. Put an indeterminate ProgressBar
as footer in a RecyclerView
grid.
2. .
我对这些答案不满意(都是同一个人)。这涉及在用户滚动时将 null
对象塞进 data-set 中途,然后在下一批交付后将其取出。它看起来像是回避了可能会或可能无法正常工作的主要问题的黑客攻击。它会在列表中造成一些不和谐和失真
使用 SwipeRefreshLayout
不是这里的解决方案。 SwipeRefreshLayout
涉及从顶部拉动以获取 最新的 项,并且它不显示进度视图。
有人可以为此提供一个好的解决方案吗?我很想知道 Google 如何为他们自己的应用程序实现这一点(Gmail 应用程序也有)。是否有任何文章详细说明了这一点?所有答案和评论将不胜感激。谢谢。
一些其他参考文献:
1. Pagination with RecyclerView
。 (精湛的概述......)
2. RecyclerView
header and footer。 (更多相同...)
3. .
我在我的旧项目中实现了这个,我是这样做的...
我已经创建了一个 interface
就像你的例子中的那些人所做的那样
public interface LoadMoreItems {
void LoadItems();
}
然后我在 Adapter
上添加了一个 addOnScrollListener()
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
(Note: this is using AsyncTask
maybe it's useful as my answer, since I did it manually not with data from API
but it should work as well) also this post was helpful to me endless-recyclerview-with-progress-bar
另外不知道你有没有给它起名字,我也找到了这个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 returns listItems.size() + 1
return VIEW_TYPE_LOADING
在 getItemViewType()
中 position >= listItems.size()
。这样加载器只会显示在回收器视图列表的末尾。此解决方案的唯一问题是即使在到达最后一页后,加载程序也会显示,因此为了修复您将 x-pagination-total-count
存储在适配器中,并且
然后你把条件return改成
(position >= listItem.size())&&(listItem.size <= xPaginationTotalCount)
.
我现在才想到这个主意,你觉得呢?
我喜欢向适配器添加进度视图持有者的想法,但它往往会导致一些丑陋的逻辑操作来获得你想要的东西。 view holder 方法迫使您通过摆弄 getItemCount()
、getItemViewType()
、getItemId(position)
和任何类型的 getItem(position)
的 return 值来防止额外的页脚项您可能想要包含的方法。
另一种方法是通过在 RecyclerView
下方显示或隐藏 ProgressBar
来管理 Fragment
或 Activity
级别的 ProgressBar
可见性加载分别开始和结束。这可以通过直接在视图布局中包含 ProgressBar
或将其添加到自定义 RecyclerView
ViewGroup
class 来实现。此解决方案通常会导致更少的维护和更少的错误。
更新:当您在加载内容时向上滚动视图时,我的建议会带来问题。 ProgressBar
将固定在视图布局的底部。这可能不是您想要的行为。出于这个原因,向您的适配器添加一个进度视图持有者可能是最好的、实用的解决方案。只是不要忘记保护您的项目访问器方法。 :)
不同的方法是在 onBindViewHolder 中启动 API 调用,并在项目视图中初始放置一些进度指示器。通话结束后,您更新视图(隐藏进度并显示接收到的数据)。例如使用 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() {
}
});
}
在我看来,有两种情况会出现:
- 您可以通过一个电话下载精简版的所有项目(例如,适配器立即知道他必须处理 40 张图片,但会按需下载 —> 我之前用 Picasso 展示过的案例)
- 您正在使用真正的延迟加载并要求服务器为您提供额外的数据块。在这种情况下,第一个先决条件是从服务器获得具有必要信息的充分响应。例如
{
"offset": 0,
"total": 100,
"items": [{items}]
}
有响应意味着您收到了总共 100 条数据中的第一块。我的方法是这样的:
查看
在获得第一块数据(例如 10)后将它们添加到适配器中。
RecyclerView.Adapter.getItemCount
只要当前可用项目数量低于总数量(例如可用 10;总计 100),在 getItemCount 方法中,您将 return items.size() + 1
RecyclerView.Adapter.getItemViewType
如果数据总量大于适配器中可用项目的数量并且位置 = items.size() (即您在 getItemCount 方法中虚构地添加了项目),作为视图类型您 return 一些进展-指标。否则你会 return 正常布局类型
RecyclerView.Adapter.onCreateViewHolder
当您被要求使用进度指示器视图类型时,您需要做的就是要求您的演示者获取额外的项目块并更新适配器
所以基本上,这是一种方法,您不必 add/remove 列表中的项目,并且您可以控制触发延迟加载的情况。
代码示例如下:
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();
}}
也许这会激发您做出满足您需求的东西
这是更简单、更清晰的方法。
从 this Codepath Guide 实施无限滚动,然后按照以下步骤操作。
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);
}
这是我在不添加假项目的情况下的解决方法(在 Kotlin 中但很简单):
在您的适配器中添加:
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
}
现在您可以在 API 调用之前轻松调用 showProgress()
。 hideProgress()
在 API 调用完成后。
此解决方案的灵感来自此页面上的 Akshar Patels 解决方案。我稍微修改了一下。
加载第一个项目时,ProgressBar 居中看起来不错。
我不喜欢底部没有更多要加载的项目时剩余的空白填充。此解决方案已将其删除。
首先是XML:
<android.support.v7.widget.RecyclerView
android:id="@+id/video_list"
android:paddingBottom="60dp"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
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/progressBar2"
style="?android:attr/progressBarStyle"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_centerHorizontal="true"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
然后我以编程方式添加了以下内容。
加载第一个结果后,将其添加到您的 onScrollListener。它将 ProgressBar 从中心移动到底部:
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) loadingVideos.getLayoutParams();
layoutParams.topToTop = ConstraintLayout.LayoutParams.UNSET;
loadingVideos.setLayoutParams(layoutParams);
当没有更多项目存在时,像这样删除底部的填充:
recyclerView.setPadding(0,0,0,0);
照常隐藏和显示 ProgressBar。
您可以像这样在 recycleView 中使用 layout_above 标签:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_below="@+id/tv2"
android:layout_width="match_parent"
android:focusable="false"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_above="@+id/pb_pagination"/>
<ProgressBar
android:id="@+id/pb_pagination"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="30dp"
android:layout_height="30dp"
android:indeterminate="true"
android:visibility="gone"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
试试这个简单的代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview_cities"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/preogressbar"
/>
<ProgressBar
android:id="@+id/preogressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
/>
</RelativeLayout>
当您的列表项已经滚动时使进度条可见,当您从您的服务获取数据时隐藏。
添加适配器
在预定义位置向 RecyclerView 插入新项目
public void insert(int position, JobModel data) {
joblist.add(position, data);
notifyItemInserted(position);
}.
public void updateList( ArrayList<JobModel> data) {
try {
for (int i = joblist.size(); i < data.size(); i++)
insert(i, data.get(i));
}catch (Exception e){} }.
当页面 ==2 时从 activity 调用
apiSaveJobsAdapter.updateList(工作列表);
另一种可能的解决方案是使用 RecyclerView 1.2.0 提供的 ConcatAdapter。缺点是这个库版本还处于 alpha 阶段。
使用这种方法,单独的适配器用于进度指示器,与主适配器连接。
val concatAdapter = ConcatAdapter(dataAdapter, progressAdapter)
progressAdapter 应该 return 来自 getItemCount() 方法的 0 或 1,具体取决于加载状态。
更多信息:https://medium.com/androiddevelopers/merge-adapters-sequentially-with-mergeadapter-294d2942127a
并查看当前recyclerview库的稳定版本,阅读时可能已经是稳定版本:https://developer.android.com/jetpack/androidx/releases/recyclerview
另一种可行的方法是使用回收器视图项目装饰。使用这种方法还可以避免修改 ViewHolder。
装饰器的动画也是可能的,例如:https://medium.com/mobile-app-development-publication/animating-recycler-view-decorator-9b15fa4b2c23
基本上,项目装饰器是在应使用 recyclerView.addItemDecoration() 函数显示加载指示器时添加的,然后使用 recyclerView.removeItemDecoration() 删除的。在显示项目装饰以制作动画时,在 recyclerView 上不断使 ItemDecorations() 无效 运行。
第三种可能性是使用 Google(Jetpack 的一部分)中的 Paging 库,v3 中可用的页眉和页脚适配器(仍处于测试阶段)
https://www.youtube.com/watch?v=1cwqGOku2a4
我正在使用 RecyclerView
and fetching objects from an API in batches of ten. For pagination, I use EndlessRecyclerOnScrollListener
。
一切正常。现在剩下的就是在列表底部添加一个进度微调器,而下一批对象由 API 获取。这是 Google Play 商店应用程序的屏幕截图,显示 ProgressBar
肯定是 RecyclerView
:
问题是,RecyclerView
和 EndlessRecyclerOnScrollListener
都不 built-in 支持在获取下一批对象时在底部显示 ProgressBar
.
我已经看过以下答案:
1. Put an indeterminate ProgressBar
as footer in a RecyclerView
grid.
2.
我对这些答案不满意(都是同一个人)。这涉及在用户滚动时将 null
对象塞进 data-set 中途,然后在下一批交付后将其取出。它看起来像是回避了可能会或可能无法正常工作的主要问题的黑客攻击。它会在列表中造成一些不和谐和失真
使用 SwipeRefreshLayout
不是这里的解决方案。 SwipeRefreshLayout
涉及从顶部拉动以获取 最新的 项,并且它不显示进度视图。
有人可以为此提供一个好的解决方案吗?我很想知道 Google 如何为他们自己的应用程序实现这一点(Gmail 应用程序也有)。是否有任何文章详细说明了这一点?所有答案和评论将不胜感激。谢谢。
一些其他参考文献:
1. Pagination with RecyclerView
。 (精湛的概述......)
2. RecyclerView
header and footer。 (更多相同...)
3.
我在我的旧项目中实现了这个,我是这样做的...
我已经创建了一个 interface
就像你的例子中的那些人所做的那样
public interface LoadMoreItems {
void LoadItems();
}
然后我在 Adapter
addOnScrollListener()
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
(Note: this is using AsyncTask
maybe it's useful as my answer, since I did it manually not with data from API
but it should work as well) also this post was helpful to me endless-recyclerview-with-progress-bar
另外不知道你有没有给它起名字,我也找到了这个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 returns
listItems.size() + 1
return
VIEW_TYPE_LOADING
在getItemViewType()
中position >= listItems.size()
。这样加载器只会显示在回收器视图列表的末尾。此解决方案的唯一问题是即使在到达最后一页后,加载程序也会显示,因此为了修复您将x-pagination-total-count
存储在适配器中,并且然后你把条件return改成
(position >= listItem.size())&&(listItem.size <= xPaginationTotalCount)
.
我现在才想到这个主意,你觉得呢?
我喜欢向适配器添加进度视图持有者的想法,但它往往会导致一些丑陋的逻辑操作来获得你想要的东西。 view holder 方法迫使您通过摆弄 getItemCount()
、getItemViewType()
、getItemId(position)
和任何类型的 getItem(position)
的 return 值来防止额外的页脚项您可能想要包含的方法。
另一种方法是通过在 RecyclerView
下方显示或隐藏 ProgressBar
来管理 Fragment
或 Activity
级别的 ProgressBar
可见性加载分别开始和结束。这可以通过直接在视图布局中包含 ProgressBar
或将其添加到自定义 RecyclerView
ViewGroup
class 来实现。此解决方案通常会导致更少的维护和更少的错误。
更新:当您在加载内容时向上滚动视图时,我的建议会带来问题。 ProgressBar
将固定在视图布局的底部。这可能不是您想要的行为。出于这个原因,向您的适配器添加一个进度视图持有者可能是最好的、实用的解决方案。只是不要忘记保护您的项目访问器方法。 :)
不同的方法是在 onBindViewHolder 中启动 API 调用,并在项目视图中初始放置一些进度指示器。通话结束后,您更新视图(隐藏进度并显示接收到的数据)。例如使用 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() {
}
});
}
在我看来,有两种情况会出现:
- 您可以通过一个电话下载精简版的所有项目(例如,适配器立即知道他必须处理 40 张图片,但会按需下载 —> 我之前用 Picasso 展示过的案例)
- 您正在使用真正的延迟加载并要求服务器为您提供额外的数据块。在这种情况下,第一个先决条件是从服务器获得具有必要信息的充分响应。例如 { "offset": 0, "total": 100, "items": [{items}] }
有响应意味着您收到了总共 100 条数据中的第一块。我的方法是这样的:
查看 在获得第一块数据(例如 10)后将它们添加到适配器中。
RecyclerView.Adapter.getItemCount 只要当前可用项目数量低于总数量(例如可用 10;总计 100),在 getItemCount 方法中,您将 return items.size() + 1
RecyclerView.Adapter.getItemViewType 如果数据总量大于适配器中可用项目的数量并且位置 = items.size() (即您在 getItemCount 方法中虚构地添加了项目),作为视图类型您 return 一些进展-指标。否则你会 return 正常布局类型
RecyclerView.Adapter.onCreateViewHolder 当您被要求使用进度指示器视图类型时,您需要做的就是要求您的演示者获取额外的项目块并更新适配器
所以基本上,这是一种方法,您不必 add/remove 列表中的项目,并且您可以控制触发延迟加载的情况。
代码示例如下:
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();
}}
也许这会激发您做出满足您需求的东西
这是更简单、更清晰的方法。
从 this Codepath Guide 实施无限滚动,然后按照以下步骤操作。
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);
}
这是我在不添加假项目的情况下的解决方法(在 Kotlin 中但很简单):
在您的适配器中添加:
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
}
现在您可以在 API 调用之前轻松调用 showProgress()
。 hideProgress()
在 API 调用完成后。
此解决方案的灵感来自此页面上的 Akshar Patels 解决方案。我稍微修改了一下。
加载第一个项目时,ProgressBar 居中看起来不错。
我不喜欢底部没有更多要加载的项目时剩余的空白填充。此解决方案已将其删除。
首先是XML:
<android.support.v7.widget.RecyclerView android:id="@+id/video_list" android:paddingBottom="60dp" android:clipToPadding="false" android:layout_width="match_parent" android:layout_height="match_parent" 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/progressBar2" style="?android:attr/progressBarStyle" android:layout_marginTop="10dp" android:layout_width="wrap_content" android:layout_centerHorizontal="true" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/>
然后我以编程方式添加了以下内容。
加载第一个结果后,将其添加到您的 onScrollListener。它将 ProgressBar 从中心移动到底部:
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) loadingVideos.getLayoutParams();
layoutParams.topToTop = ConstraintLayout.LayoutParams.UNSET;
loadingVideos.setLayoutParams(layoutParams);
当没有更多项目存在时,像这样删除底部的填充:
recyclerView.setPadding(0,0,0,0);
照常隐藏和显示 ProgressBar。
您可以像这样在 recycleView 中使用 layout_above 标签:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_below="@+id/tv2"
android:layout_width="match_parent"
android:focusable="false"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_above="@+id/pb_pagination"/>
<ProgressBar
android:id="@+id/pb_pagination"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="30dp"
android:layout_height="30dp"
android:indeterminate="true"
android:visibility="gone"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
试试这个简单的代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview_cities"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/preogressbar"
/>
<ProgressBar
android:id="@+id/preogressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
/>
</RelativeLayout>
当您的列表项已经滚动时使进度条可见,当您从您的服务获取数据时隐藏。
添加适配器
在预定义位置向 RecyclerView 插入新项目
public void insert(int position, JobModel data) {
joblist.add(position, data);
notifyItemInserted(position);
}.
public void updateList( ArrayList<JobModel> data) {
try {
for (int i = joblist.size(); i < data.size(); i++)
insert(i, data.get(i));
}catch (Exception e){} }.
当页面 ==2 时从 activity 调用 apiSaveJobsAdapter.updateList(工作列表);
另一种可能的解决方案是使用 RecyclerView 1.2.0 提供的 ConcatAdapter。缺点是这个库版本还处于 alpha 阶段。
使用这种方法,单独的适配器用于进度指示器,与主适配器连接。
val concatAdapter = ConcatAdapter(dataAdapter, progressAdapter)
progressAdapter 应该 return 来自 getItemCount() 方法的 0 或 1,具体取决于加载状态。
更多信息:https://medium.com/androiddevelopers/merge-adapters-sequentially-with-mergeadapter-294d2942127a
并查看当前recyclerview库的稳定版本,阅读时可能已经是稳定版本:https://developer.android.com/jetpack/androidx/releases/recyclerview
另一种可行的方法是使用回收器视图项目装饰。使用这种方法还可以避免修改 ViewHolder。 装饰器的动画也是可能的,例如:https://medium.com/mobile-app-development-publication/animating-recycler-view-decorator-9b15fa4b2c23
基本上,项目装饰器是在应使用 recyclerView.addItemDecoration() 函数显示加载指示器时添加的,然后使用 recyclerView.removeItemDecoration() 删除的。在显示项目装饰以制作动画时,在 recyclerView 上不断使 ItemDecorations() 无效 运行。
第三种可能性是使用 Google(Jetpack 的一部分)中的 Paging 库,v3 中可用的页眉和页脚适配器(仍处于测试阶段) https://www.youtube.com/watch?v=1cwqGOku2a4