Android Jetpack(七)Paging

一、Paging 简介

        Paging 是 Jetpack 组件库的一个组件,是 Google 官方提供的分页加载数据框架,主要用于数据的分页加载

        Paging 主要由三个部分组成:

  • DataSource
  • PagedList
  • PagedListAdapter

二、Paging 核心构成

1. DataSource

        DataSource<Key, Value>是一个数据源,其中Key对应加载数据的条件信息,Value对应加载数据的实体类。           

        DataSource是一个抽象类,但是我们不能直接继承它实现它的子类。但是Paging库里提供了它的三个子类供我们继承用于不同场景的实现:

  • PageKeyedDataSource<Key, Value>:适用于目标数据根据页信息请求数据的场景,即Key字段是页相关的信息。比如请求的数据的参数中包含类似next / pervious页数的信息。
  • ItemKeyedDataSource<Key, Value>:适用于目标数据的加载依赖特定item的信息, 即Key字段包含的是Item中的信息,比如需要根据第N项的信息加载第N+1项的数据,传参中需要传入第N项的ID时,该场景多出现于论坛类应用评论信息的请求。
  • PositionalDataSource<T>:适用于目标数据总数固定,通过特定的位置加载数据,这里Key是Integer类型的位置信息,T即Value。 比如从数据库中的1200条开始加在20条数据。

2. PageList

        PageList是一个List的子类,支持所有List的操作,除此之外它主要有五个成员:

(1)MainThreadExecutor:一个主线程的Excutor, 用于将结果post到主线程。

(2)BackgroundThreadExecutor:后台线程的Excutor。

(3)BoundaryCallback:加载Datasource中的数据加载到边界时的回调。

(4)Config:配置PagedList从Datasource加载数据的方式, 其中包含以下属性:

  • pageSize:设置每页加载的数量
  • prefetchDistance:预加载的数量,默认为pagesize
  • initialLoadSizeHint:初始化数据时加载的数量,默认为pagesize*3
  • enablePlaceholders:当item为null是否使用PlaceHolder展示

(5)PagedStorage<T>:用于存储加载到的数据,它是真正的蓄水池所在,它包含一个ArrayList<List> 对象mPages,按页存储数据。

        PagedList会从Datasource中加载数据,更准确的说是通过Datasource加载数据, 通过Config的配置,可以设置一次加载的数量以及预加载的数量。 除此之外,PagedList还可以向RecyclerView.Adapter发送更新的信号,驱动UI的刷新。

3. PagedListAdapte

        PagedListAdapte是RecyclerView.Adapter的实现,用于展示PagedList的数据。它本身实现的更多是Adapter的功能,但是它有一个小伙伴PagedListAdapterHelper<T>,PagedListAdapterHelper会负责监听PagedList的更新Item数量的统计等功能。这样当PagedList中新一页的数据加载完成时,PagedAdapte就会发出加载完成的信号,通知RecyclerView刷新,这样就省略了每次Loading后手动调一次notifyDataChanged()。

        除此之外,当数据源变动产生新的PagedList,PagedAdapter会在后台线程中比较前后两个PagedList的差异,然后调用notifyItem…()方法更新RecyclerView.这一过程依赖它的另一个小伙伴ListAdapterConfig, ListAdapterConfig负责主线程和后台线程的调度以及DiffCallback的管理,DiffCallback的接口实现中定义比较的规则,比较的工作则是由PagedStorageDiffHelper来完成。

        三者之间的关系以及数据加载到数据展示的流程如下:

        

        当一条新的item插入到数据库,DataSource会被初始化,LiveData后台线程就会创建一个新的PagedList。这个新的PagedList会被发送到UI线程的PagedListAdapter中,PagedListAdapter使用DiffUtil在对比现在的Item和新建Item的差异。当对比结束,PagedListAdapter通过调用RecycleView.Adapter.notifyItemInserted()将新的item插入到适当的位置。
 

三、Paging 使用

1. 导入依赖库

implementation 'androidx.paging:paging-runtime:2.1.1'

2. 创建 DataRepository

        创建 DataRepository 用于数据的加载,这一步不是必须的,但一般为了获取数据的逻辑更加清晰,通常会创建对应的 Repository 类。

public class DataRepository {
    private List<Data> dataList = new ArrayList<>();

    public DataRepository() {
        for (int i = 0; i < 1000; i++) {
            Data data = new Data(i, "name " + i);
            dataList.add(data);
        }
    }

    public List<Data> initData(int size) {
        return dataList.subList(0, size);
    }

    public List<Data> loadPageData(int page, int size) {
        int totalPage;
        if (dataList.size() % size == 0) {
            totalPage = dataList.size() / size;
        } else {
            totalPage = dataList.size() / size + 1;
        }

        if (page > totalPage || page < 1) {
            return null;
        }
        if (page == totalPage) {
            return dataList.subList((page - 1) * size, dataList.size());
        }
        return dataList.subList((page - 1) * size, page * size);
    }
}

3. 自定义 DataSource

public class CustomPageDataSource extends PageKeyedDataSource<Integer, Data> {
    private DataRepository dataRepository;

    private CustomPageDataSource(DataRepository dataRepository) {
        this.dataRepository = dataRepository;
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Data> callback) {
        List<Data> dataList = dataRepository.initData(params.requestedLoadSize);
        callback.onResult(dataList, null, 2);
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Data> callback) {
        List<Data> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
        if (dataList != null) {
            callback.onResult(dataList, params.key - 1);
        }
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Data> callback) {
        List<Data> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
        if (dataList != null) {
            callback.onResult(dataList, params.key + 1);
        }
    }

    public static class CustomPageDataSourceFactory extends DataSource.Factory<Integer, Data> {
        private DataRepository repository;

        public CustomPageDataSourceFactory(DataRepository repository) {
            this.repository = repository;
        }

        @NonNull
        @Override
        public DataSource<Integer, Data> create() {
            return new CustomPageDataSource(repository);
        }
    }
}

    CustomItemDataSource继承自PageKeyedDataSource,实现了loadInitialloadAfterloadBefore方法,其中

  • loadInitial 初始加载数据
  • loadAfter 向后分页加载数据
  • loadBefore 向前分页加载数据

        其中params包装了分页加载的参数。

   loadInitial中的params为LoadInitialParams包含了requestedLoadSizeplaceholdersEnabled两个属性:

  • requestedLoadSize为加载的数据量
  • placeholdersEnabled是是否显示占位及当数据为null时显示占位的view

   loadBeforeloadAfter中的params为LoadParams包含了keyrequestedLoadSize,key即为DataSource<Key, Value>中的key,在这里即为页数。

   callback为数据加载完成的回调,loadInitial中调用调用DataRepository加载数据,然后调用callback.onResult告诉调用者数据加载完成。

4. 创建 PageAdapter

public class CustomAdapter extends PagedListAdapter<Data, CustomAdapter.CustomViewHolder> {

    public CustomAdapter() {
        super(new DiffUtil.ItemCallback<Data>() {
            @Override
            public boolean areItemsTheSame(@NonNull Data oldItem, @NonNull Data newItem) {
                return oldItem.getId() == newItem.getId();
            }

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

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

    @Override
    public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
        Data data = getItem(position);
        if (data == null) {
            holder.textView.setText("loading...");
        } else {
            holder.textView.setText(String.valueOf(data.getName()));
        }
    }

    static class CustomViewHolder extends RecyclerView.ViewHolder {
        TextView textView;

        public CustomViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.textView);
        }
    }
}

        DiffUtil.ItemCallback 的作用是判断两个item的数据是否相等。

5. 创建PagedList,并使用

        recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
        customAdapter = new CustomAdapter();
        recyclerView.setAdapter(customAdapter);

        PagedList.Config config = new PagedList.Config.Builder()
                .setPageSize(20)
                .setEnablePlaceholders(true)
                .setInitialLoadSizeHint(20)
                .build();
        dataRepository = new DataRepository();
        customPageDataSourceFactory = new CustomPageDataSource.CustomPageDataSourceFactory(dataRepository);
        dataList = new LivePagedListBuilder(customPageDataSourceFactory, config).build();

        dataList.observe(this, new Observer<PagedList<Data>>() {
            @Override
            public void onChanged(PagedList<Data> dataPagedList) {
                customAdapter.submitList(dataPagedList);
            }
        });

        通过以上步骤,便实现了数据的分页加载。

发布了67 篇原创文章 · 获赞 69 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_34519487/article/details/104340109