Endless RecyclerView with ProgressBar for pagination

前端 未结 9 2145
庸人自扰
庸人自扰 2020-11-27 12:51

I am using a RecyclerView and fetching objects from an API in batches of ten. For pagination, I use EndlessRecyclerOnScrollListener.

It\'s all working properly. Now

相关标签:
9条回答
  • 2020-11-27 13:12

    Different approach would be to start the API call inside onBindViewHolder and initialy place into items view some progress indicator. After call is finished, you update the view (hide progress and showing received data). For example with Picasso for image loading, onBindViewHolder method would look like this

    @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() {
    
                    }
                });
    }
    

    As I see it, there are two cases which can appear:

    1. where you download all items in light version with one call (e.g. the adapter knows immediately that he’ll have to deal with 40 pictures, but downloads it on demand —> case which I showed previously with Picasso)
    2. where you are working with real lazy loading and you are asking server to give you additional chunk of data. In this case, first prerequisite is to have adequate response from server with necessary information. Fore example { "offset": 0, "total": 100, "items": [{items}] }

    There response means that you received first chunk of total 100 data. My approach would be something like this:

    View After getting first chunk of data (e.g. 10) add them into adapter.

    RecyclerView.Adapter.getItemCount As long as the current amount of available items is lower than total amount (e.g. available 10; total 100), in getItemCount method you will return items.size() + 1

    RecyclerView.Adapter.getItemViewType if total amount of data is greater than amount of available items in adapter and the position = items.size() (i.e. you’ve fictively added item in getItemCount method), as view type you return some progress-indicator. Otherwise you’ll return normal layout type

    RecyclerView.Adapter.onCreateViewHolder When you are asked to use progress-indicator view type, all you need to do is to ask your presenter to get additional chunk of items and update the adapter

    So basically, this is approach where you don’t have to add/remove items from the list and where you have control over situation when lazy loading will be triggered.

    Here is the code example:

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

    Maybe this'll inspire you to make something that will suit your needs

    0 讨论(0)
  • 2020-11-27 13:16

    This solution is inspired by Akshar Patels solution on this page. I modified it a bit.

    1. When loading the first items it looks nice to have the ProgressBar centered.

    2. I didn't like the remaining empty padding at the bottom when there existed no more items to load. That has been removed with this solution.

    First the 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"/>
    

    Then I added the following programmatically.

    When first results been loaded, add this to your onScrollListener. It moves the ProgressBar from center to the bottom:

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

    When no more items exist, remove the padding at the bottom like this:

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

    Hide and show your ProgressBar as usual.

    0 讨论(0)
  • 2020-11-27 13:20

    There is another way to do this.

    • First your adapter's getItemCount returns listItems.size() + 1

    • return VIEW_TYPE_LOADING in getItemViewType() for position >= listItems.size(). This way the loader will only be shown at the end of the recycler view list. The only problem with this solution is even after reaching the last page, the loader will be shown, so in order to fix that you store the x-pagination-total-count in the adapter, and

    • then you change the condition to return view type to

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

    I just came up with this idea now what do you think?

    0 讨论(0)
提交回复
热议问题