首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android Jetpack架构组件(九)之Paging

Android Jetpack架构组件(九)之Paging

原创
作者头像
xiangzhihong
修改于 2021-01-13 10:08:26
修改于 2021-01-13 10:08:26
3.9K0
举报
文章被收录于专栏:向治洪向治洪

一、Paging简介

Android应用开发中,我们经常需要以列表的方式来展示大量的数据,这些数据可能来自网路,也可以来自本地的数据库。为了避免一次性加载大量的数据,对数据进行分页就显得很有必要。分页加载可以根据需要对数据进行按需加载,在不影响用户体验的前提下,提升应用的性能。

为了方便开发者进行分页处理,Google为开发者提供了分页组件(Paging),借助Paging组件开发者可以轻松的加载和呈现大型数据集,同时在 RecyclerView 中进行快速、无限滚动。并且,它可以从本地存储和/或网络加载分页数据,并让开发者能够定义内容的加载方式,同时它还支持与Room、LiveData 和 RxJava组合使用。

1.1 支持的架构类型

目前,Paging可以支持3种架构类型,分别是网路、数据、网路和数据库,架构的示意图如下所示。

在这里插入图片描述
在这里插入图片描述
网路

在Android应用开发中,对网路数据进行分页加载是一种比较常见的场景,也是我们平时开发中遇到得最多的。不同公司对分页机制所涉及的API接口通常会不一样,但总体而言,可以分为3中。针对这些不同的分类,Paging提供了PageKeyedDataSource、PositionalDataSource和ItemKeyedDataSource。

  • PageKeyedDataSource:根据传入的页面num获取某一页的数据,比如获取第2页的数据。
  • PositionalDataSource:分页时默认显示的第几页。
  • ItemKeyedDataSource:请求下一页的关键字。
数据库

除了网路外,数据源来源于数据库的场景也非常多,如果已经掌握了对网路数据的分页,那么对数据库的数据进行分页自然十分简单,只不过数据源的读取方式不同而已。

网路+数据库

在这种场景中,我们会对网路的数据进行缓存,而数据库就是比较场景的一种数据持久化方式,比如聊天应用中。首先,我们会利用数据库对网路数据进行缓存,不过在这种场景下,我们需要同时处理数据库和网路两个数据源,因此需要约定好网路和数据库的数据处理逻辑。

1.2 工作流程

在正式使用Paging之前,我们需要对Paging的工作流程有一个大致的了解。如下图所示,是使用Paging需要经历的几个步骤。

在这里插入图片描述
在这里插入图片描述

如上图所示,主要的步骤如下:

  1. 使用DataSource从服务器获取或者从本地数据库获取数据。
  2. 将数据保存到PageList中。
  3. 将PageList的数据交给PageListAdapter。
  4. PageListAdapter在后台线程对比原来的PageList和新的PageList,生成新的PageList。
  5. PageListAdapter通知RecyclerView进行数据的更新。

1.3 核心概念

使用Paging库进行分页加载时,需要用到几个核心的类,分别是PagedListAdapter、PageList和DataSource。

PagedListAdapter

众所周知,在Android列表开发中需要使用RecyclerView,并且需要配合自定义Adapter。PagedListAdapter继承于RecyclerView.Adapter,这表明它也是一个RecyclerView.Adapter,并且扩展了RecyclerView.Adapter的支持异步差分更新功能。

PageList

PageList是用于通知DataSource何时获取数据,以及如何获取数据。比如,何时获取第一页数据,以及何时开始加载数据等待。并且,DataSource数据源都将通过PageList设置给PagedListAdapter。

DataSource

DataSource主要用于执行数据的加载操作,并且数据的载入需要在子线程中进行,否则会造成主线程的阻塞。DataSource的来源可以是网路,也可以是本地的数据库,如Room。根据分页机制的不同,DataSource可以有3种来源,分别是PageKeyedDataSource、PositionalDataSource和ItemKeyedDataSource。

  • PageKeyedDataSource:根据传入的页面num获取某一页的数据,比如获取第2页的数据。
  • PositionalDataSource:分页时默认显示的第几页。
  • ItemKeyedDataSource:请求下一页的关键字。

二、基本使用

2.1 添加依赖

首先,在app的build.gradle文件中添加Paging组件库的依赖,如下所示。

代码语言:txt
AI代码解释
复制
dependencies {
   //网路请求库
   implementation 'com.squareup.retrofit2:retrofit:2.9.0'
   implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
   implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1'
   implementation 'com.google.code.gson:gson:2.8.6'
   implementation 'com.squareup.okhttp3:okhttp:4.8.0'
   implementation 'com.squareup.okio:okio:2.7.0'
   //Paging
   def paging_version = "2.1.0"
   implementation "androidx.paging:paging-runtime:$paging_version"
 }

2.2 定义网路请求

在Android开发中,数据通常来源于网路,我们可以使用retrofit完成网络数据的请求。在获取数据之前,我们需要先新建一个数据实体类,主要用来存储获取的数据,如下所示是使用干货集中营的开源 Api 的数据的实体类。

代码语言:txt
AI代码解释
复制
public class DataBean {

    private int count;
    private boolean error;
    private List<ResultsBean> results;
    
    ...//省略get和set

    public static class ResultsBean {
        private String desc;
        private String ganhuo_id;
        private String publishedAt;
        private String readability;
        private String type;
        private String url;
        private String who;
       
        ... //省略get和set
    }
}

然后,为了完成网路请求,我们需要按照Retrofit的使用方式新建一个Api,用于统一管理请求接口,如下所示。

代码语言:txt
AI代码解释
复制
public interface Api {

    //开源API:http://gank.io/api/search/query/listview/category/Android/count/10/page/1
    @GET("api/search/query/listview/category/Android/count/10/page/{page}")
    Call<List<DataBean.ResultsBean>> getArticleList1(@Path("page") int page);

}

然后,我们对Retrofit进行一个简单的封装,然后用它完成网路请求,如下所示。

代码语言:txt
AI代码解释
复制
public class RetrofitClient {

    private static RetrofitClient instance;
    private Retrofit mRetrofit;
    private OkHttpClient getClient(){
        OkHttpClient.Builder builder = new OkHttpClient().newBuilder()
                .connectTimeout(30, TimeUnit.SECONDS)//设置超时时间
                .readTimeout(10, TimeUnit.SECONDS)//设置读取超时时间
                .writeTimeout(10, TimeUnit.SECONDS);//设置写入超时时间
        return builder.build();
    }

    public RetrofitClient() {
        mRetrofit=new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .client(getClient())
                .build();

    }

    public static RetrofitClient getInstance() {
        if (instance==null){
            instance=new RetrofitClient();
        }
        return instance;
    }
    
    public <T> T createApi(Class<T> cls){
        T t=mRetrofit.create(cls);
        return t;
    }
}

2.3 创建数据源

如果使用的是网路数据,使用Paging进行分页加载时需要自定义DataSource。前面说过,DataSource有3个抽象类,分别是PageKeyedDataSource、PositionalDataSource和ItemKeyedDataSource,因此实际使用时需要继承他们。

由于此处加载的是网络数据,所以使用PageKeyedDataSource更合适,我们新建一个继承自PageKeyedDataSource的自定义DataSource,如下所示。

代码语言:txt
AI代码解释
复制
public class PagingDataSource extends PageKeyedDataSource<String, DataBean.ResultsBean> {

    private static final String TAG = PagingDataSource.class.getSimpleName();
    private int mPage = 1;

    @Override
    public void loadInitial(@NonNull LoadInitialParams<String> params, @NonNull final LoadInitialCallback<String, DataBean.ResultsBean> callback) {
        Api api = RetrofitClient.getInstance().createApi(Api.class);
        Call<List<DataBean.ResultsBean>> call = api.getArticleList1(mPage);
        call.enqueue(new Callback<List<DataBean.ResultsBean>>() {
            @Override
            public void onResponse(Call<List<DataBean.ResultsBean>> call, Response<List<DataBean.ResultsBean>> response) {
                if (response.isSuccessful() && response.code() == 200) {
                    List<DataBean.ResultsBean> data  = response.body();
                    callback.onResult(data, "before", "after");
                }

            }

            @Override
            public void onFailure(Call<List<DataBean.ResultsBean>> call, Throwable t) {
                Log.e(TAG, "--onFailure-->" + t.getMessage());
            }
        });
    }

    //加载上一页
    @Override
    public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, DataBean.ResultsBean> callback) {
        Log.i(TAG, "--loadBefore-->" + params.key);
    }

    //加载下一页
    @Override
    public void loadAfter(@NonNull final LoadParams<String> params, @NonNull final LoadCallback<String, DataBean.ResultsBean> callback) {
        mPage++;
        Api api = RetrofitClient.getInstance().createApi(Api.class);
        Call<List<DataBean.ResultsBean>> call = api.getArticleList1(mPage);
        call.enqueue(new Callback<List<DataBean.ResultsBean>>() {
            @Override
            public void onResponse(Call<List<DataBean.ResultsBean>> call, Response<List<DataBean.ResultsBean>> response) {
                if (response.isSuccessful() && response.code() == 200) {
                    List<DataBean.ResultsBean> data  = response.body();
                    callback.onResult(data, params.key);
                }
            }

            @Override
            public void onFailure(Call<List<DataBean.ResultsBean>> call, Throwable t) {
                Log.i(TAG, "--onFailure-->" + t.getMessage());
            }
        });
    }
}

在上面的代码中,PageKeyedDataSource需要重写三个方法。

  • loadInitial():第一次请求数据,即初始化状态时请求数据。
  • loadBefore():请求上一页数据,基本没有用处。
  • loadAfter(): 请求下一页数据。

DataSource创建好了,再创建一个DataSource.Factory,返回对应的DataSource实例,如下所示。

代码语言:txt
AI代码解释
复制
public class PagingDataSourceFactory extends DataSource.Factory<String, DataBean.ResultsBean> {

    @NonNull
    @Override
    public DataSource<String, DataBean.ResultsBean> create() {
        PagingDataSource dataSource = new PagingDataSource();
        return dataSource;
    }
}

2.4 配置分页参数

在Jetpack的架构里面,官方推荐每个页面持有一个ViewModel对象,以保证数据的正确性以及避免其他的问题产生。

在在 ViewModel 中创建 PagedList.Config 并进行分页参数配置,创建 DataSource 工厂对象,最终生成支持分页的 LiveData 数据。要想创建LiveData,需要先创建一个LivePagedListBuilder,LivePagedListbuilder有设分页数量和配置参数两种方法,如下所示。

代码语言:txt
AI代码解释
复制
public class PagingViewModel extends ViewModel {

    private int pageSize = 20;
    //PagedList配置
    private PagedList.Config config = new PagedList.Config.Builder()
            .setInitialLoadSizeHint(pageSize)//设置首次加载的数量
            .setPageSize(pageSize)//设置每页加载的数量
            .setPrefetchDistance(2)//设置距离每页最后数据项来时预加载下一页数据
            .setEnablePlaceholders(false)//设置是否启用UI占用符
            .build();

    //DataSource.Factory
    private DataSource.Factory<String,DataBean.ResultsBean> factory = new PagingDataSourceFactory();

    //LiveData
    private LiveData<PagedList<DataBean.ResultsBean>> mPagedList = new LivePagedListBuilder<>(factory, config)
            .build();

    public LiveData<PagedList<DataBean.ResultsBean>> getPagedList() {
        return mPagedList;
    }

}

上面代码中,我们提到了占位符,占位符的作用是在数据完成渲染之前,向用户显示的默认视图效果。占位符具有以下优点:

  • 支持滚动条:PagedList 可向 PagedListAdapter 提供列表项数量。此信息允许适配器绘制滚动条来传达整个列表大小。有新页面载入时,滚动条不会跳到指定位置,因为列表不会改变大小。
  • 无需加载旋转图标:由于列表大小已知,因此无需提醒用户正在加载更多项。

不过,在添加对占位符的支持之前,请注意以下前提条件:

  • 需要可计数的数据集:Room 持久性库 中的 DataSource 实例可以有效地计算项的数量。但如果您使用的是自定义本地存储解决方案或网络专用数据架构,确定数据集包含多少项可能会开销极大,甚至根本无法确定。
  • 适配器必须考虑未加载的项:为准备列表以应对增长而使用的适配器或呈现机制需要处理 Null 列表项。例如,将数据绑定到 ViewHolder 时,您需要提供默认值来表示未加载数据。
  • 需要同样大小的项视图:如果列表项大小会随着内容而变(例如社交网络更新),则项之间的交叉渐变效果并不理想。在这种情况下,我们强烈建议停用占位符。

2.5 创建PagedListAdapter

PagedListAdapter是一个特殊的RecyclerView的RecyclerAdapter,使用方法也和RecyclerAdapter的使用方式类似,如下所示。

代码语言:txt
AI代码解释
复制
public class PagingAdapter extends PagedListAdapter<DataBean.ResultsBean, PagingAdapter.ViewHolder> {

    public PagingAdapter() {
        super(itemCallback);
    }

    @NonNull
    @Override
    public PagingAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycleview, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull PagingAdapter.ViewHolder holder, int position) {
        DataBean.ResultsBean bean = getItem(position);
        if (bean != null) {
            holder.desc.setText(bean.getDesc());
            holder.time.setText(bean.getPublishedAt());
            holder.type.setText(bean.getType());
            holder.auth.setText(bean.getWho());
        }
    }


    public class ViewHolder extends RecyclerView.ViewHolder{
        TextView desc;
        TextView time;
        TextView type;
        TextView auth;

        public ViewHolder(View itemView) {
            super(itemView);
            desc = itemView.findViewById(R.id.desc);
            time = itemView.findViewById(R.id.time);
            type = itemView.findViewById(R.id.type);
            auth = itemView.findViewById(R.id.auth);
        }
    }


    private static DiffUtil.ItemCallback<DataBean.ResultsBean> itemCallback = new DiffUtil.ItemCallback<DataBean.ResultsBean>() {
        @Override
        public boolean areItemsTheSame(@NonNull DataBean.ResultsBean oldItem, @NonNull DataBean.ResultsBean newItem) {
            return oldItem.getGanhuo_id().equals(newItem.getGanhuo_id());
        }

        @SuppressLint("DiffUtilEquals")
        @Override
        public boolean areContentsTheSame(@NonNull DataBean.ResultsBean oldItem, @NonNull DataBean.ResultsBean newItem) {
            return oldItem.equals(newItem);
        }
    };

}

在使用PagedListAdapter时,PagedListAdapter内部默认实现DiffUtil来进行数据的差量计算,所以我们在构造方法里面传递一个DiffUtil.ItemCallback。

2.6 加载分页数据

经过上面的处理后,接下来只需要在Activity中进行数据的请求和绑定即可,如下所示。

代码语言:txt
AI代码解释
复制
public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";
    private ActivityMainBinding activityMainBinding;
    private PagingViewModel mViewModel;
    private PagingAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        initRecyclerView();
//        getData();
    }

    private void initRecyclerView() {
        adapter = new PagingAdapter();

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        activityMainBinding.recycle.setLayoutManager(layoutManager);
        activityMainBinding.recycle.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
        activityMainBinding.recycle.setAdapter(adapter);

        ViewModelProvider mViewModelProvider = new ViewModelProvider(this,
                new ViewModelProvider.AndroidViewModelFactory(getApplication()));
        mViewModel = mViewModelProvider.get(PagingViewModel.class);

    }

    public void getData() {
        mViewModel.getPagedList().observe(this, new Observer<PagedList<DataBean.ResultsBean>>() {
            @Override
            public void onChanged(PagedList<DataBean.ResultsBean> dataBeans) {
                adapter.submitList(dataBeans);
            }
        });
    }
}

我们使用 LiveData 监听加载的数据,然后使用 sumbitList 将数据提交给 PagedListAdapter,PagedListAdapter会在后台线程中对比新旧数据的差异,最后更新 RecyclerView。

三、Paging3

3.1 概述

Paging是JetPack框架提供的一个分页库,它可以帮助开发者从本地存储或通过网络加载显示数据,不过由于历史原因,早期的Paging存在各种使用上的问题,因此Android在后面提供了Paging3用来替换早期的Paging2。相比Paging2,Paging3有如下一些优点。

  • 在内存中缓存分页数据,确保 App 在使用分页数据时有效地使用系统资源。
  • 内置删除重复数据的请求,确保 App 有效地使用网络带宽和系统资源。
  • 可配置 RecyclerView 的 Adapters,当用户滚动到加载数据的末尾时自动请求数据。
  • 支持 Kotlin 协程和 Flow, 以及 LiveData 和 RxJava。
  • 内置的错误处理支持,包括刷新和重试等功能。

3.1.1 主要变化

在 Paging3 之前,Paging提供了 ItemKeyedDataSource、PageKeyedDataSource、PositionalDataSource 这三个类来进行数据获取的操作。

  • PositionalDataSource:用于加载数据有限的数据,比如加载本地数据库。
  • PageKeyedDataSource:用来请求网络数据,适用于通过页码分页来请求数据。
  • ItemKeyedDataSource:用来请求网络数据,它适用于通过当前页面最后一条数据的 id作为下一页的数据的开始的位置的场景。

在 Paging3 之后,ItemKeyedDataSource、PageKeyedDataSource、PositionalDataSource 合并为一个 PagingSource,所有旧 API 加载方法被合并到 PagingSource 中的单个 load() 方法中,如下所示。

代码语言:txt
AI代码解释
复制
abstract suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value>

除此之外,变化的内容还包括:

  • LivePagedListBuilder 和 RxPagedListBuilder 合并为了 Pager。
  • 使用 PagedList.Config 替换 PagingConfig。
  • 使用 RemoteMediator 替换了 PagedList.BoundaryCallback 去加载网络和本地数据库的数据。

3.1.2 重要概念

在正式学习Paging3之前,我们需要弄清楚几个重要的概念:

  • PagingSource:单一的数据源,可以表示数据库请求得到的数据,也可以是网络请求得到的数据。
  • RemoteMediator:单一的数据源,它会在 PagingSource 没有数据的时候,再使用 RemoteMediator 提供的数据,如果既存在数据库请求又存在网络请求,通常 PagingSource 用于进行数据库请求,RemoteMediator 进行网络请求。
  • PagingData:单次分页数据的容器
  • Pager:用来构建 Flow<PagingData> 的类,实现数据加载完成的回调。
  • PagingDataAdapter:分页加载数据的 RecyclerView 的适配器。

总的来说,使用Paging3加载网络数据的流程是:PagingSource 和 RemoteMediator 充当数据源的角色,ViewModel 使用 Pager 中提供的 Flow<PagingData> 监听数据刷新,每当 RecyclerView 即将滚动到底部的时候,就会加载新的数据,最后再使用PagingAdapter 展示数据。

3.1.3 Paging3应用架构

下面是Android官方推荐的接入 Paging3的应用架构图。

在这里插入图片描述
在这里插入图片描述

可以发现,使用Paging3实现数据分页时主要包含3个对象:

数据仓库层Repository

Repository层主要使用PagingSource这个分页组件来实现,每个PagingSource对象都对应一个数据源,以及该如何从该数据源中查找数据,PagingSource可以从任何单个数据源比如网络或者数据库中查找数据。

Repository层还有另一个分页组件可以使用RemoteMediator,它是一个分层数据源,比如有本地数据库缓存的网络数据源。

ViewModel层

Repository最终返回一个异步流包裹的PagingDataFlow<PagingData<Value>>,PagingData存储了数据结果,最终可以使用它将数据跟UI界面关联起来。ViewModel一般都使用LiveData来跟UI层交互,Flow的扩展函数可以直接转换成一个LiveData可观察对象。

UI层

UI层其实就是Activity/Fragment等视图层,主要的作用是给RecycleView设置Adapter,给Adater设置数据。

3.2 基本使用

3.2.1, 添加依赖

首先,在app的build.gradle文件中添加Paging3组件库的依赖,如下所示。

代码语言:txt
AI代码解释
复制
dependencies {
   ...
   //Paging3
   def paging_version = "3.0.0-alpha11"
   implementation "androidx.paging:paging-runtime:$paging_version"
 }

3.2.2 定义数据源

Paging 2提供了三种类型的 PageSource,开发者需要根据使用场景去进行选择。而Paging 3对数据源进行了统一处理,开发时只需要继承 PagingSource 即可。

Paging 3的数据源可以是PagingSource,也可以是RemoteMediator,它们的区别如下。

  • PagingSource:单一数据源以及如何从该数据源中查找数据,数据源的变动会直接映射到 UI 上。
  • RemoteMediator:实现加载网络分页数据并更新到数据库中,但是数据源的变动不能直接映射到 UI 上。

那实际使用时,如何进行选择呢?PagingSource主要用于加载有限的数据集,而RemoteMediator则主要用来加载网络分页数据,实际使用时需要结合 PagingSource 实现保存更多数据操作并映射到 UI 上。

下面以WanAndroid的接口为例,接口地址为:https://www.wanandroid.com/article/list/1/json,数据源的代码如下。

代码语言:txt
AI代码解释
复制
public class Paging3DataSource extends PagingSource<Integer, ArticleBean.DataBean.DatasBean>  {


    @Nullable
    @Override
    public Object load(@NotNull LoadParams<Integer> params, @NotNull Continuation<? super LoadResult<Integer, ArticleBean.DataBean.DatasBean>> continuation) {
        int page = 0;
        if(params.getKey()!=null){
            page=params.getKey();
        }
        //获取网络数据
        ArticleBean result = (ArticleBean) RetrofitClient.getInstance().getApi().getArticleList(page);
        //需要加载的数据
        List<ArticleBean.DataBean.DatasBean> datas= result.getData().getDatas();
        //如果可以往上加载更多就设置该参数,否则不设置
        String prevKey=null;
        //加载下一页的key 如果传null就说明到底了
        String nextKey=null;
        if(result.getData().getCurPage() == result.getData().getPageCount()){
            nextKey=null;
        }else {
            nextKey=String.valueOf(page+1);
        }
        return new LoadResult.Page<>(datas,prevKey,nextKey);
    }


}

在上面的代码中,自定义的PagingSource需要继承自PagingSource,需要传入两个泛型,第一个表示下一页数据的加载方式,另一个是返回数据的实体类。同时,自定义的PagingSource还需要重写load方法来触发异步加载,可以看到它是一个用suspend修饰的挂起函数,可以很方便的使用协程异步加载。

而load方法的参数LoadParams中有一个key值,可以在加载下一页数据时使用。返回值是一个LoadResult,出现异常调用LoadResult.Error(e),正常强开情况下调用LoadResult.Page方法来设置从网络或者数据库获取到的数据。

3.2.3 网络请求

在实际应用开发中,我们需要从网络中获取数据,然后再进行其他业务操作。网络请求一般会借助Retrofit来实现,下面是使用Retrofit完成WanAndroid接口请求的简单的封装,代码如下。

代码语言:txt
AI代码解释
复制
public class RetrofitClient {

    private static String BASE_URL="https://www.wanandroid.com/";
    private static RetrofitClient instance;
    private Retrofit mRetrofit;

    private OkHttpClient getClient(){
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                Log.i("RetrofitClient: ", message);
            }
        });
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(interceptor)
                .connectTimeout(60, TimeUnit.SECONDS)
                .build();
        return client;
    }

    public RetrofitClient() {
        mRetrofit=new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(getClient())
                .build();
    }

    public static RetrofitClient getInstance() {
        if (instance==null){
            instance=new RetrofitClient();
        }
        return instance;
    }

    public <T> T createApi(Class<T> cls){
        T t=mRetrofit.create(cls);
        return t;
    }

}

然后,在创建一个WanAndroidApi来管理所有的Api接口,如下所示。

代码语言:txt
AI代码解释
复制
public interface WanAndroidApi {

    @GET("article/list/{pageNum}/json")
    Call<ArticleBean> getArticleList(@Path("page") int page);
}

3.2.4 构建ViewModel

分页数据的容器被称为 PagingData,每次刷新数据时,都会创建一个 PagingData 的实例。如果要创建 PagingData 数据流,那么需要创建一个 Pager 实例,并提供一个 PagingConfig 配置对象和一个可以告诉 Pager 如何获取您实现的 PagerSource 的实例的函数,以供 Pager 使用。

实际开发中,Repository返回的是一个异步流包裹的PagingDataFlow<PagingData<Value>>,PagingData存储了数据结果。而在MVVM中,我们需要构建ViewModel来实现是LiveData和UI层交互,而ViewModel的Flow的扩展函数可以将直接将PagingSource转换成一个LiveData可观察对象,代码如下。

代码语言:txt
AI代码解释
复制
public class Paging3ViewModel extends ViewModel {

    PagingConfig pagingConfig = new PagingConfig(20, 3);

    public LiveData<PagingData<ArticleBean.DataBean.DatasBean>> getArticleData() {
        CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(this);
        Pager<Integer, ArticleBean.DataBean.DatasBean> pager = new Pager<>(pagingConfig, () -> new Paging3DataSource());
        LiveData<PagingData<ArticleBean.DataBean.DatasBean>> cachedResult=PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), viewModelScope);
        return cachedResult;
    }

}

3.2.5 创建Adapter

和Paging2的Adapter使用步骤类似,在Paging3中创建Adapter需要继承 PagingDataAdapter,然后提供 DiffUtil.ItemCallback<T>,如下所示。

代码语言:txt
AI代码解释
复制
public class Paging3Adapter extends PagingDataAdapter<ArticleBean.DataBean.DatasBean, Paging3Adapter.ViewHolder> {


    public Paging3Adapter(@NotNull DiffUtil.ItemCallback<ArticleBean.DataBean.DatasBean> diffCallback) {
        super(itemCallback);
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycleview, parent, false);
        return new Paging3Adapter.ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        ArticleBean.DataBean.DatasBean bean = getItem(position);
        if (bean != null) {
            holder.desc.setText(bean.getDesc());
            holder.time.setText(String.valueOf(bean.getPublishTime()));
            holder.type.setText(bean.getType());
            holder.auth.setText(bean.getAuthor());
        }
    }

    public static class ViewHolder extends RecyclerView.ViewHolder{
        TextView desc;
        TextView time;
        TextView type;
        TextView auth;

        public ViewHolder(View itemView) {
            super(itemView);
            desc = itemView.findViewById(R.id.desc);
            time = itemView.findViewById(R.id.time);
            type = itemView.findViewById(R.id.type);
            auth = itemView.findViewById(R.id.auth);
        }
    }


    public static DiffUtil.ItemCallback<ArticleBean.DataBean.DatasBean> itemCallback = new DiffUtil.ItemCallback<ArticleBean.DataBean.DatasBean>() {
        @Override
        public boolean areItemsTheSame(@NonNull ArticleBean.DataBean.DatasBean oldItem, @NonNull ArticleBean.DataBean.DatasBean newItem) {
            return oldItem.getId()==newItem.getId();
        }

        @SuppressLint("DiffUtilEquals")
        @Override
        public boolean areContentsTheSame(@NonNull ArticleBean.DataBean.DatasBean oldItem, @NonNull ArticleBean.DataBean.DatasBean newItem) {
            return oldItem.equals(newItem);
        }
    };
}

可以发现,Paging3Adapter的代码和普通的Adapter的代码是差不多的,也是需要重写onCreateViewHolder和onBindViewHolder两个方法。

3.2.6 在UI 中展示数据

最后,我们在Activity中使用RecyclerView展示获取的数据即可,如下所示。

代码语言:txt
AI代码解释
复制
public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";
    private Paging3Adapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView( R.layout.activity_main);
        initRecyclerView();
    }

    private void initRecyclerView() {
        adapter = new Paging3Adapter();
        RecyclerView  recyclerView = findViewById(R.id.recycle);
        recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
        recyclerView.setAdapter(adapter);

        Paging3ViewModel viewModel=new ViewModelProvider(this).get(Paging3ViewModel.class);

        viewModel.getArticleData().observe(this, pagingData -> adapter.submitData(getLifecycle(),pagingData));
    }
}

除此之外,Paging3还支持添加 Header 和 Footer来实现上拉刷新和下拉加载更多的功能。

参考: 使用官方Paging3分页库实现RecyclerView加载更多

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
《Flutter个人资料界面应用》
本文我将向您展示如何在 flutter 中制作个人资料页面的 UI,您将学习如何制作圆形按钮以及如何在 flutter 中制作渐变色。
徐建国
2021/08/03
1.1K0
Flutter 构建 Facebook Clone UI
该项目将需要许多文件,一个用于应用程序,3 个用于某些自定义小部件,因此请确保为每个组件创建一个文件
徐建国
2021/08/02
6050
基于flutter3.x+material-design3仿微信App应用实战
flutter3-wchat一款基于flutter3+dart3+material-ui技术构建的跨多端仿微信聊天项目。
andy2018
2024/02/07
1.3K0
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Al
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心
卓伊凡
2025/02/04
1580
Flutter 创建一个很酷的 Booking App UI
这是一个非常酷的项目,您将在其中学习如何实现标签栏、具有水平滚动的列表视图以及如何创建底部Flutter 中的导航栏,所以不用多说,让我们开始吧。
徐建国
2021/08/02
6400
本文主要介绍flutter聊天应用程序
在本教程中,我将向您展示如何使用 Flutter 构建一个完整的聊天应用程序。对于这一部分,我们将创建应用程序的 UI 原型,然后我将向您展示如何使用 firebase 创建后端服务并创建聊天系统。
徐建国
2021/07/31
8050
在 Flutter 中进行简单的 仪表盘 UI 设计
本教程中,我将向您展示如何在 Flutter 中进行简单的 仪表盘 UI 设计。 对于本教程,我们需要添加一些图像资产,因此创建一个新文件夹并将其命名为assets并将资产路径添加到pubspec.yaml文件中。 现在我们将编码我们的布局 import 'package:flutter/material.dart'; void main() => runApp( MaterialApp( home:Dashboard() ) ); class Dashboard extends Sta
徐建国
2021/08/03
1K0
【Flutter 专题】124 日常问题小结 (三) 自定义 Dialog 二三事
针对日常不同的需求,我们时常需要自定义 Dialog,而和尚在尝试过程中遇到一些小问题,简单记录总结一下;
阿策小和尚
2021/06/08
1.3K0
【Flutter 专题】124 日常问题小结 (三) 自定义 Dialog 二三事
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面
卓伊凡
2025/01/24
3010
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍
卓伊凡
2025/02/07
1650
Flutter 黏贴卡动画效果
设计非常出色的动画会使UI感觉更直觉,应用程序具有光滑的外观和感觉,改善用户体验。Flutter的动画支持使实现各种动画类型变得容易。许多小部件,特别是“Material”小部件,都伴随着其设计规范中所描述的标准运动效果,但是与此同时,也可以自定义这些效果。
老孟Flutter
2021/04/22
2.5K0
Flutter 黏贴卡动画效果
Flutter 金融应用程序的 UI
在本教程中,我将向您展示如何使用 Flutter 和 android studio
徐建国
2021/08/03
8670
Flutter 意见输入框
在我们输入文本之后下面的输入字数会变,可能马上你会想到使用setState不就完了嘛!....可是Dialog 并没有setState方法
赵哥窟
2021/03/02
2.2K0
Flutter 意见输入框
【10】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时
【10】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍
卓伊凡
2025/02/05
3780
Flutter组件随笔练习
Container组件 import 'package:flutter/material.dart'; //快捷方式:fim void main() { runApp(MyApp()); } //自定义组件 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return MaterialApp(
明知山
2020/09/02
1K0
Flutter 入门指北之常用布局
为了让我们的界面更容易被扩展,通常会在最外层包裹一层 Container,其构造函数也不是很难理解
陈宇明
2020/12/16
7760
flutter3_window_chat仿微信桌面端聊天实战
年前有给大家分享一款flutter3.x+dart3手机端聊天App实例。春节期间就又捣鼓了flutter3桌面端开发实践项目。
andy2018
2024/03/03
9410
Flutter 实现登录 UI
本文,我将解析怎么前构建一个用户交互的登录页面。这里,我使用 TextField 挂件,这方便用户输入用户名和密码。还使用 FlatButton 挂件,来处理一些动作。当然,我还使用了 Image 挂件来设定登录页面的 logo。
Jimmy_is_jimmy
2022/10/05
9660
Flutter 实现登录 UI
自研flutter3.22+getx手机版os桌面管理系统
flutter3.x-weos:原创研发flutter3+dart3+getx+fl_chart构建手机桌面OS管理系统模板。
andy2018
2024/06/07
2390
【Flutter 专题】117 图解 Dismissible 滑动清除 Widget
和尚在尝试在项目中实现类似于 iOS 邮箱邮件左右滑动删除对应邮件时,参考到 Flutter 提供的 Dismissible,虽与理想的有差别,但还是值得研究一下。
阿策小和尚
2021/03/16
1.4K0
推荐阅读
《Flutter个人资料界面应用》
1.1K0
Flutter 构建 Facebook Clone UI
6050
基于flutter3.x+material-design3仿微信App应用实战
1.3K0
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Al
1580
Flutter 创建一个很酷的 Booking App UI
6400
本文主要介绍flutter聊天应用程序
8050
在 Flutter 中进行简单的 仪表盘 UI 设计
1K0
【Flutter 专题】124 日常问题小结 (三) 自定义 Dialog 二三事
1.3K0
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为
3010
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时
1650
Flutter 黏贴卡动画效果
2.5K0
Flutter 金融应用程序的 UI
8670
Flutter 意见输入框
2.2K0
【10】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时
3780
Flutter组件随笔练习
1K0
Flutter 入门指北之常用布局
7760
flutter3_window_chat仿微信桌面端聊天实战
9410
Flutter 实现登录 UI
9660
自研flutter3.22+getx手机版os桌面管理系统
2390
【Flutter 专题】117 图解 Dismissible 滑动清除 Widget
1.4K0
相关推荐
《Flutter个人资料界面应用》
更多 >
LV.4
GitCode深圳公司总经理
目录
  • 一、Paging简介
    • 1.1 支持的架构类型
    • 1.2 工作流程
    • 1.3 核心概念
  • 二、基本使用
    • 2.1 添加依赖
    • 2.2 定义网路请求
    • 2.3 创建数据源
    • 2.4 配置分页参数
    • 2.5 创建PagedListAdapter
    • 2.6 加载分页数据
  • 三、Paging3
    • 3.1 概述
      • 3.1.1 主要变化
      • 3.1.2 重要概念
      • 3.1.3 Paging3应用架构
  • 3.2 基本使用
    • 3.2.1, 添加依赖
    • 3.2.2 定义数据源
    • 3.2.3 网络请求
    • 3.2.4 构建ViewModel
    • 3.2.5 创建Adapter
    • 3.2.6 在UI 中展示数据
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档