Paging3实现下拉刷新、上拉加载更多(Java向)

1、Paging库介绍

        这里就不详细介绍、官网写的非常详细清楚:

        传送门:库优势 库架构 基本原理 

        本文是针对Java向的开发,想学习Kotlin的可以参考郭霖这篇博文:

       Jetpack新成员,Paging3从吐槽到真香

2、Paging库基本用法(结合RxJava)

     1)导入依赖:

    implementation "androidx.paging:paging-runtime:3.0.1"
    //使用RxJava,需要引入这个依赖 (RxJava2、RxJava3均可,这里以RxJava2为例)
    implementation "androidx.paging:paging-rxjava2:3.0.1"

        这里的版本号对自己项目minSdkVersion是有要求的,可以根据实际情况进行修改。

        使用RxJava 还需要如下依赖:

   implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'

   implementation 'io.reactivex.rxjava2:rxjava:2.2.9'

    2)构建RxPagingSource(kotlin是直接构建PagingSource)

            RxPagingSource<Integer, Bean> :

Integer页数数据类型
Bean列表Item项的实体类型

          实现loadSingle方法:

@Override
    public @NotNull Single<LoadResult<Integer, Bean>> loadSingle(@NotNull LoadParams<Integer> loadParams) {
        Integer nextPageNumber = loadParams.getKey();
        if (nextPageNumber == null) {
            nextPageNumber = 1;
        }
        Integer finalPage = nextPageNumber;
        int loadSize = loadParams.getLoadSize();
        return Single.fromCallable(() -> repository
                .getNeedPagingList(type, loadSize, loadSize * (finalPage - 1)))
                .subscribeOn(Schedulers.io()).map(new Function<List<Bean>, LoadResult<Integer, Bean>>() {
                    @Override
                    public LoadResult<Integer, Bean> apply(@NonNull List<Bean> mBeans) throws Exception {
                        return toLoadResult(mBeans, finalPage);
                    }
                }).onErrorReturn(new Function<Throwable, LoadResult<Integer, Bean>>() {
                    @Override
                    public LoadResult<Integer, Bean> apply(@NonNull Throwable throwable) throws Exception {
                        return new LoadResult.Error<>(throwable);
                    }
                });
    }

    /**
     * 功能描述 将获取的集合对象转化为需加载的结果对象
     *
     * @param mBeans 待加载的实体
     * @param page  对应的页数
     * @return: androidx.paging.PagingSource.LoadResult<java.lang.Integer, com.xxx.xxx.Bean>
     * @since 1.0
     */
    private LoadResult<Integer, Bean> toLoadResult(@NonNull List<Bean> mBeans, Integer page) {
        Integer prevKey = page == 1 ? null : page - 1;
        Integer nextKey = mBeans.isEmpty() ? null : page + 1;
        return new LoadResult.Page<>(mBeans, prevKey, nextKey, LoadResult.Page.COUNT_UNDEFINED,
                LoadResult.Page.COUNT_UNDEFINED);
    }

loadSingle

RxJava对应的方法,Kotlin是对应load方法

loadParams

getkey:代表当前页数,可为null.
loadSize:一页显示的条目数(可随PagingConfig 调整)

LoadResult.Page()

prevKey:当前页的上一页页数 (如当前第一页,没有上一页,所以上一页为null)
nexttKey:当前页的下一页页数 (如当前页是最后一页,没有下一页,所以下一页为null)

这里有个getRefreshKey方法 涉及高级用法,可实现后放着,返回null就行。

     3)设置 PagingData 流

    我这里是在ViewModel里实现的:

 /**
     *
     *功能描述 获取PagingData流
     * @param type 类型,(假如有搜索功能的话,可作为列表一个过滤条件)
     * @return: io.reactivex.Flowable<androidx.paging.PagingData < com.xxx.xxx.Bean>> 
     *@since 1.0
     */
    public Flowable<PagingData<Bean>> getPagingData(String type) {
        CoroutineScope viewmodelScope = ViewModelKt.getViewModelScope(this);
        Pager<Integer, Bean> pager = new Pager<Integer, Bean>(new PagingConfig(60, 10, true, 60),
                () -> new MyPagingSource(type,repository));
        Flowable<PagingData<Bean>> flowable = PagingRx.getFlowable(pager);
        return PagingRx.cachedIn(flowable, viewmodelScope);
    }

PagingConfig:

PageSize一页加载的项目数
prefetchDistance预取数,意思提前多少项目数拉取下一屏数据,例如设置为10的话,当前加载了60条数据,当用户滑倒第50条时候,就会提前加载下一屏数据,注意这个值必须大于0。(Paging列表丝滑的原因之一)
enablePlaceholders是否启用占位符,启用的话,item会先填充默认的布局,待数据获取加载后,再刷新item,(Paging列表丝滑的原因之一)
initlalLoadSize初始化第一次加载item数。

cachedIn():

作用是在viewModelScope这个作用域内进行缓存,假如手机横竖屏发生了旋转导致了Activity重新创建,Paging3就可以直接读取缓存中的数据,而不会重新去发起网络请求了(节省带宽资源)。

     4)定义 RecyclerView 适配器

       自定义PagingDataAdapter,其基本用法和RecyclerView.Adapter一致,都需要实现onCreateViewHolder,ViewHolder,onBindViewHolder,getItemCount,下面重点说需要注意的地方:

 public PagingListAdapter(MyComparator myComparator, Context mContext) {
        super(myComparator);
        this.mContext = mContext;
    }

    public static class MyComparator extends DiffUtil.ItemCallback<Bean> {

        @Override
        public boolean areItemsTheSame(@NonNull Bean oldItem, @NonNull Bean newItem) {
            return oldItem.getId() == newItem.getId();
        }

        @Override
        public boolean areContentsTheSame(@NonNull Bean oldItem, @NonNull Bean newItem) {
            return oldItem.getInfo().equals(newItem.getInfo());
        }
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        
        Bean mBean =getItem(position);
        if (mBean == null) {
            return;
        }
        holder.tvInfo.setText(mBean.getInfo());
        ...
    }


    @Override
    public int getItemCount() {
        return super.getItemCount();
    }

首先是需要实现DiffUtil.ItemCallback<?> 这个方法是也是Paging能够实现去重功能的关键,从而能够更高效的利用网络带宽和系统资源,大家也不想每次刷新、拉取都重新去加载旧数据是吧。

然后说下onBindViewHolder里获取item项的方法getItem(position);这个方法是固定的,不要误把列表数据集合又传到这个apdater里面了,咱们的数据流是通过上述PagingData形式传递给Paging了,Paging会通过其内部机制自主的管理数据如何分页、加载等。

getItemCount()也是一样,固定写法。

   5)显示列表。

PagingListAdapter adapter = new PagingListAdapter(new PagingListAdapter.MyComparator(), getContext());

 mViewModel.getPagingData(type).as(RxLife.asOnMain(getViewLifecycleOwner())).subscribe(mBeanPagingData -> {
            adapter.submitData(getLifecycle(), mBeanPagingData);
        });

就这么简单,当执行submitData后,Paging的整个机制就运转起来了,等着界面显示即可。

通过以上步骤,基本实现了一个简单Paging列表。但是这个列表会有个问题,就是不管是开始加载还是上拉加载更多,列表都没加载状态的显示效果,这样在数据量较大或网络不太稳定情况下,用户体验感会很差,那么接下来内容就是给Paging列表添加状态效果,包括加载完成后,在列表尾部显示加载完毕等文字效果。

3、Paging3列表显示加载状态。

1)启动列表显示加载状态。

实现addLoadStateListener监听:

adapter.addLoadStateListener(new Function1<CombinedLoadStates, Unit>() {
            @Override
            public Unit invoke(CombinedLoadStates combinedLoadStates) {
                LoadState loadState = combinedLoadStates.getRefresh();
                if (loadState instanceof LoadState.NotLoading) {
					//关闭进度条
                    Alert.progressClose();
                } else if (loadState instanceof LoadState.Loading) {
					//显示进度条
                    Alert.progresShow(getContext(), getContext().getString(R.string.review_progress_text));
                } else if (loadState instanceof LoadState.Error) {
                    LoadState.Error error = (LoadState.Error) loadState;
					//关闭进度条,并弹出错误原因吐司
                    Alert.progressClose();
                    Toast.makeText(getContext(), "Error:" + error.getError().getMessage(), Toast.LENGTH_SHORT).show();
                }

                return null;
            }
        });
getRefresh

获取初始化刷新时加载状态

getAppend在加载更多的时候的加载状态
getPrepend在当前列表头部添加数据时的加载状态
NotLoading

没加载,无错误

Loading

数据加载中
Error

加载出错

2)加载更多时状态加载

实现LoadStateAdapter:

public class PagingLoadStateAdapter extends LoadStateAdapter<PagingLoadStateAdapter.MyViewHolder> {

    private final Context context;
    private final PagingListAdapter adapter;

    public PagingLoadStateAdapter(Context context,PagingListAdapter adapter) {
        this.context = context;
        this.adapter=adapter;
    }

    @Override
    public void onBindViewHolder(@NotNull PagingLoadStateAdapter.MyViewHolder myViewHolder,
                                 @NotNull LoadState loadState) {
        myViewHolder.bind(loadState,adapter);
    }

    @NotNull
    @Override
    public PagingLoadStateAdapter.MyViewHolder onCreateViewHolder(@NotNull ViewGroup viewGroup,
                                                                  @NotNull LoadState loadState) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.load_state_item, viewGroup, false);
        return new MyViewHolder(view);
    }

    public static class MyViewHolder extends RecyclerView.ViewHolder {
        private final TextView mErrorMsg;
        private final TextView mStatus;
        private final Button mRetry;
        private final ProgressBar progressBar;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            mErrorMsg = itemView.findViewById(R.id.errorMsg);
            mRetry = itemView.findViewById(R.id.retryButton);
            progressBar = itemView.findViewById(R.id.progressBar);
            mStatus = itemView.findViewById(R.id.tv_stats);
        }
		 /**
         * 功能描述 加载状态显示
         *
         * @param loadState 加载状态
         * @since 1.0
         */
        public void bind(LoadState loadState) {
            if (loadState instanceof LoadState.NotLoading) {
                if (loadState.getEndOfPaginationReached()) {
                    progressBar.setVisibility(View.GONE);
                    mStatus.setVisibility(View.VISIBLE);
                    mStatus.setText("数据加载完毕");
                    mRetry.setVisibility(View.GONE);
                    mErrorMsg.setVisibility(View.GONE);
                }
            }

            if (loadState instanceof LoadState.Loading) {
                progressBar.setVisibility(View.VISIBLE);
            } else {
                progressBar.setVisibility(View.GONE);
            }
            if (loadState instanceof LoadState.Error) {
                mErrorMsg.setVisibility(View.VISIBLE);
                mRetry.setVisibility(View.VISIBLE);
                mRetry.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        adapter.retry();
                    }
                });
                mStatus.setVisibility(View.GONE);
                LoadState.Error loadStateError = (LoadState.Error) loadState;
                mErrorMsg.setText(loadStateError.getError().getLocalizedMessage());
            } else {
                mErrorMsg.setVisibility(View.GONE);
                mRetry.setVisibility(View.GONE);
            }

        }

    }

    @Override
    public boolean displayLoadStateAsItem(@NotNull LoadState loadState) {
        return (loadState instanceof LoadState.Loading || loadState instanceof LoadState.Error || loadState instanceof LoadState.NotLoading && loadState
                .getEndOfPaginationReached());
    }

}

设置LoadStateAdapter:

PagingLoadStateAdapter pagingLoadStateAdapter = new PagingLoadStateAdapter(getActivity(),adapter);
ConcatAdapter concatAdapter = adapter.withLoadStateFooter(pagingLoadStateAdapter);

withLoadStateFooter

当前列表尾部添加

withLoadStateHeader

当前列表头部添加

withLoadStateHeaderAndFooter

当前列表头部和尾部分别添加

这里loadState状态可以参考启动列表的加载状态,含义是一样的,这里就不多赘述。

这里需要注意的是:

PagingDataAdapter一定要先设置withLoadStateFooter,然后返回一个新的concatAdapter对象,再把这个对象赋值给RecyclerView进行setAdapter,才会生效。

重写displayLoadStateAsItem方法,重写的原因就是为了在用户列表滑到底部时候显示数据加载完毕这个文字。查看源码可以看到displayLoadStateAsItem默认只是显示LoadState.Loading 和LoadState.Error,所以假如不重写就会发现滑到底部后,文字仅仅是一闪而逝,而不会停留在底部。

重试的话,也很简单,直接调用PagingDataAdapter的retry()即可。

好的,到这里基本上实现了Paging的启动加载,状态显示,上拉加载更多,重试等,现在就剩下下拉刷新

4、Paging3结合swiperefreshlayout实现下拉刷新功能。

 看过paging库介绍,是个分页加载库,它封装了一套完整的分页加载流程,虽然其自带刷新功能,但是并没有自带的刷新界面的动画效果,因此需要结合swiperefreshlayout这个库配合Paging使用来实现下拉刷新功能。

1)swiperefreshlayout依赖导入

 implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"

2)将swiperefreshlayout嵌套在RecyclerView上

 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/swiperefreshlayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginStart="@dimen/fragment_margin_left"
        android:layout_marginEnd="@dimen/fragment_margin_right"
        app:layout_constraintBottom_toTopOf="@+id/guideline19"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/guideline18">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:fastScrollEnabled="true"
            app:fastScrollHorizontalThumbDrawable="@drawable/line_drawable"
            app:fastScrollHorizontalTrackDrawable="@drawable/track_drawable"
            app:fastScrollVerticalThumbDrawable="@drawable/line_drawable"
            app:fastScrollVerticalTrackDrawable="@drawable/track_drawable" />
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

3)实现swiperefreshlayout刷新监听调用Paging3的刷新逻辑。

swiperefreshlayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                adapter.refresh();
            }
        });

4)配合Paging3刷新时加载状态,显示关闭swiperefreshlayout。

adapter.addLoadStateListener(new Function1<CombinedLoadStates, Unit>() {
            @Override
            public Unit invoke(CombinedLoadStates combinedLoadStates) {
                LoadState loadState = combinedLoadStates.getRefresh();
                if (loadState instanceof LoadState.NotLoading) {
                    swiperefreshlayout.setRefreshing(false);
                } else if (loadState instanceof LoadState.Loading) {
                    swiperefreshlayout.setRefreshing(true);
        
                } else if (loadState instanceof LoadState.Error) {
                    swiperefreshlayout.setRefreshing(false);
                    LoadState.Error error = (LoadState.Error) loadState;
                    Toast.makeText(getContext(), "Error:" + error.getError().getMessage(), Toast.LENGTH_SHORT).show();
                }

                return null;
            }
        });

至此大功告成!

z-paging插件可以实现底部加载更多的功能。通过使用该插件,您可以在uniapp中实现多条数据滚动显示或者自定义下拉刷新,分页显示等功能。插件提供了一个Footer布局文件,您可以在其中添加加载更多的视图元素,例如加载中的动画和文本提示。当用户滑动到底部时,插件会自动触发加载更多的操作。如果加载失败,用户可以点击重试按钮重新加载请求失败的页的数据。您可以在uniapp的官方插件市场搜索z-paging插件,并参考其接口文档学习如何使用该插件。\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [JetPack知识点实战系列六:Paging实现加载更多下拉刷新,错误后重新请求](https://blog.csdn.net/lcl130/article/details/108597975)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [uniapp开发使用插件z-paging实现页面下拉刷新上拉加载,分页加载](https://blog.csdn.net/qq_43799179/article/details/126709149)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值