带有ProgressBar的Endless RecyclerView用于分页

时间:2015-12-23 08:47:10

标签: android android-listview android-recyclerview android-5.0-lollipop android-progressbar

我正在使用RecyclerView并以十个批量从API中提取对象。对于分页,我使用EndlessRecyclerOnScrollListener

一切正常。现在剩下的就是在列表底部添加一个进度微调器,同时由API提取下一批对象。以下是Google Play商店应用的屏幕截图,显示了ProgressBar的{​​{1}}:

enter image description here

问题是,RecyclerViewRecyclerView都没有内置支持在底部显示EndlessRecyclerOnScrollListener,而下一批对象正在被提取。

我已经看到了以下答案:

1。 Put an indeterminate ProgressBar as footer in a RecyclerView grid

2。 Adding items to Endless Scroll RecyclerView with ProgressBar at bottom

我对这些答案都不满意(两个人都是同一个人)。这涉及在用户滚动时将ProgressBar对象拖入数据集中,然后在下一批交付后将其取出。它看起来像是一个黑客,可以回避主要问题,可能会或可能不会正常工作。它会在列表中引起一些刺耳和失真

使用SwipeRefreshLayout不是解决方案。 null涉及从顶部提取以获取最新项目,并且无论如何都不会显示进度视图。

有人可以为此提供一个好的解决方案吗?我有兴趣知道Google是如何为自己的应用程序实现此功能的(Gmail应用程序也有此功能)。是否有任何文章详细显示?所有答案&评论将不胜感激。谢谢。

其他一些参考资料

1。 Pagination with RecyclerView。 (精湛的概述......)

2。 RecyclerView header and footer。 (更多相同......)

3。 Endless RecyclerView with ProgressBar at bottom

11 个答案:

答案 0 :(得分:16)

我在我的旧项目上实现了这个,我按照以下方式实现了这个......

我已经创建了一个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;
}

在我MainActivityLoadItems()添加其他项目的地方是:

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也许它有用作我的答案,因为我手动完成而不是来自{{{ 1}}但它也可以正常工作 )此帖也对我有用endless-recyclerview-with-progress-bar

此外,我不知道你是否给它命名,但我也发现了这篇文章infinite_scrolling_recyclerview,也许它对你也有帮助。

如果它不是您要找的,请告诉我这段代码有什么问题,我会尝试修改它。

希望它有所帮助。

修改

由于你不想删除某个项目...我发现我猜一个人只在这篇文章中移除了APIdiseño-android-endless-recyclerview

这适用于footer,但我知道您可以将其改编为ListView他不会删除他刚刚放置的任何项目RecyclerView Visible/Invisible看看:{{3} }}

另请查看:此问题detecting-end-of-listview

答案 1 :(得分:12)

这里是简单明了的方法。

this Codepath Guide实施Endless Scrolling,然后按照以下步骤操作。

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

答案 2 :(得分:5)

还有另一种方法可以做到这一点。

  • 首先,您的适配器的getItemCount返回listItems.size() + 1
  • VIEW_TYPE_LOADING中为getItemViewType()返回position >= listItems.size()。这样,加载程序将仅显示在回收器视图列表的末尾。此解决方案的唯一问题是,即使在到达最后一页后,也会显示加载程序,因此为了解决您将x-pagination-total-count存储在适配器中的问题,
  • 然后更改条件以将视图类型返回到

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

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

答案 3 :(得分:3)

我喜欢将进度视图持有者添加到适配器的想法,但它往往导致一些丑陋的逻辑操作来获得你想要的东西。视图持有者方法会强制您通过使用getItemCount()getItemViewType()getItemId(position)以及您可能使用的任何getItem(position)方法的返回值进行操作来防范其他页脚项目想要包括。

另一种方法是通过在加载开始时显示或隐藏ProgressBar下的Fragment来管理ActivityProgressBar级别的RecyclerView可见性分别结束。这可以通过直接在视图布局中包含ProgressBar或将其添加到自定义RecyclerView ViewGroup类来实现。该解决方案通常可以减少维护并减少错误。

更新:我的建议在内容加载时向上滚动视图时出现问题。 ProgressBar将粘贴到视图布局的底部。这可能不是您想要的行为。因此,向适配器添加进度视图持有者可能是最好的功能解决方案。只是不要忘记保护您的物品存取方法。 :)

答案 4 :(得分:2)

这是我的解决方法,没有添加假项目(在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()。 API调用完成后hideProgress()

答案 5 :(得分:1)

尝试以下简单代码:

<?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>

当列表项已经滚动时,使进度条可见;当从服务中获取数据时,则隐藏进度条。

答案 6 :(得分:1)

另一种可能的解决方案是使用 RecyclerView 1.2.0 提供的 ConcatAdapter。缺点是这个库版本还处于 alpha 阶段。

使用这种方法,单独的适配器用于进度指示器,与主适配器连接。

 val concatAdapter = ConcatAdapter(dataAdapter, progressAdapter)

progressAdapter 应该从 getItemCount() 方法返回 0 或 1,具体取决于加载状态。

更多信息:https://medium.com/androiddevelopers/merge-adapters-sequentially-with-mergeadapter-294d2942127a

并检查recyclerview库的当前稳定版本,阅读时可能已经是稳定版本:https://developer.android.com/jetpack/androidx/releases/recyclerview


另一种可行的方法是使用回收器视图项目装饰。使用这种方法还可以避免修改 ViewHolders。 动画装饰器也是可能的,例如参见:https://medium.com/mobile-app-development-publication/animating-recycler-view-decorator-9b15fa4b2c23

基本上,当加载指示器应该与 recyclerView.addItemDecoration() 函数一起出现时添加项装饰器,然后使用 recyclerView.removeItemDecoration() 删除。不断使 recyclerView 上的 invalidateItemDecorations() 无效,同时显示项目装饰以使动画运行。


第三种可能性是使用 Google 的 Paging 库(Jetpack 的一部分),v3 提供的页眉和页脚适配器(仍处于测试阶段) https://www.youtube.com/watch?v=1cwqGOku2a4

答案 7 :(得分:0)

此解决方案的灵感来自此页面上的Akshar Patels解决方案。我做了一点修改。

  1. 在加载第一个项目时,将ProgressBar居中看起来不错。

  2. 当不存在要加载的项目时,我不喜欢底部的其余空白填充。该解决方案已将其删除。

首先是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。

答案 8 :(得分:0)

您可以像这样在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>

答案 9 :(得分:0)

添加适配器

在预定义位置向 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时从活动调用 apiSaveJobsAdapter.updateList(joblist);

答案 10 :(得分:-2)

不同的方法是在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() {

                }
            });
}

在我看来,有两种情况可以出现:

  1. 您可以通过一次通话下载轻型版本的所有项目(例如,适配器立即知道他将需要处理40张图片,但需要按需下载 - &gt;我之前用Picasso展示过的情况)
  2. 您正在使用真正的延迟加载,并且您要求服务器为您提供额外的数据块。在这种情况下,第一个先决条件是从服务器获得足够的响应和必要的信息。例如 { &#34;偏移&#34;:0, &#34;总计&#34;:100, &#34; items&#34;:[{items}] }
  3. 响应意味着您收到了总共100个数据的第一块。我的方法是这样的:

    查看 获取第一块数据(例如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<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();
    }}
    

    也许这会激励你做出符合你需求的东西