当前位置:   article > 正文

Android架构之Paging组件(一)_android paging

android paging

Jetpack组件系列文章
Android架构之LifeCycle组件
Android架构之Navigation组件(一)
Android架构之Navigation组件(二)
Android架构之Navigation组件(三)
Android架构之Navigation组件(四)
Android架构之ViewModel组件
Android架构之LiveData组件
Android架构之Room组件(一)
Android架构之Room组件(二)
Android架构之WorkManager组件
Android架构之DataBinding(一)
Android架构之DataBinding(二)
Android架构之Paging组件(一)
Android架构之Paging组件(二)
Jetpack与MVVM架构

Paging组件的意义

分页加载是在应用程序开发过程中十分常见的需求。我们经常需要以列表的形式加载大量的数据,一次性加载所有的数据,必然会消耗大量的时间和数据流量。然而用户只需要部分数据。这时候就有了分页加载。分页加载是对数据进行按需加载。 分页加载有两种模式:

  • 传统的上拉加载更多
  • 无限滚动分页效果

Paging组件就是基于无限滚动模式而设计的。

在这里插入图片描述
比如说:京东的App就是就是基于无限滚动模式进行分页加载的。当我们滑动到一定量的数据的时候,会自动请求加载下一页的数据。这让用户会感觉到,所有的数据都是一次性而加载成的。

Paging支持的架构模型

在这里插入图片描述

网络

对网络数据进行分页加载,是最常见的一种分页需求。不同的公司针对分页机制所设计的API接口通常也不太一样,但总体而言可以归纳为3种。为此,Paging组件提供了3种不同的方法,以应对不同的分页机制。

  1. PositionalDataSource
  2. PageKeyedDataSource
  3. ItemKeyedDataSource

数据库

数据库与网络的分页加载大同小异,无非就是数据源的替换。

网络+数据库

出于用户体验的考虑,通常我们会对网络数据进行缓存,以便下次用户打开应用程序,应用程序可以先展示缓存数据。我们通常会利用数据库对网络数据进行缓存。所以,我们采用单一数据源作为解决方法。即从网络获取的数据,直接缓存进数据库,列表只从数据库这个唯一的数据源获取数据。

Paging的工作原理

在这里插入图片描述

1.在RecyclerView的滑动过程中,会触发PagedListAdapter类中的onBindViewHolder()方法。数据与RecycleView中Item布局的UI控件正是在该方法中进行绑定的。

2.当RecyclerView滑动到底部时,在onBindViewHolder()方法中所调用的getItem()方法会通知PagedList,当前需要载入更多数据。

3.接着,PagedList会根据PageList.Config中的配置通知DataSource执行具体的数据获取工作。

4.DataSource从网络/本地数据库取得数据后,交给PagedList,PagedList将持有这些数据。

5.PagedList将数据交给PagedListAdapter中的DiffUtil进行比对和处理。

6.数据在经过处理后,交由RecyclerView进行展示.

Paging的3个核心类

1.PagedListAdapter:

  • 适配器,RecyclerView的适配器,通过分析数据是否发生了变化,负责处理UI展示的逻辑(增加/删除/替换等)

2.PageList:

  • 负责通知DataSource何时获取数据,以及如何获取数据。例如,何时加载第一页/下一页、第一页加载的数量、提前多少条数据开始执行预加载等。需要注意的是,从DataSource获取的数据将存储在PagedList中

3.DataSource

  • 数据源,执行具体的数据载入工作。注意:数据的载入需要在工作线程中执行。数据可以来自网络,也可以来自本地数据库,如Room.根据分页机制的不同,Paging为我们提供了3种DataSource。

3种DataSource

1.PositionalDataSource

  • 适用于可通过任意位置加载数据,且目标数据源数量固定的情况。例如,若请求时携带的参数为start=2&count=5,则表示向服务端请求从第2条数据开始往后的5条数据。
    在这里插入图片描述

2.PageKeyedDataSource

  • 适用于数据源以"页"的方式进行请求的情况。例如,若请求时携带的参数为page=2&pageSize=5,则表示数据源以5条数据为一页,当前返回第二页的5条数据。
    在这里插入图片描述

3.ItemKeyedDataSource

  • 适用于当目标数据的下一页需要依赖于上一页数据中最后一个对象中的某个字段作为key的情况。此类分页形式常见于评论功能的实现。例如,若上一页数据中最后一个对象的key为9527,那么在请求下一页时,需要携带参数since=9527&pageSize=5,则服务器会返回key=9527之后的5条数据
    在这里插入图片描述

PositionalDataSource的使用

Api接口

比如说豆瓣的Api接口

api.douban.com/v2/movie/in_theaters?apikey=XXXXX&start=0&count=8
  • 1

参数start表示可以从任意位置开始获取数据;参数count表示从start位置往后的count条数据。 但是由于豆瓣Api接口不可以使用,你们测试的时候,只需要找到类似于这种的接口,修改下参数和接口返回数据对应的Model类即可使用。这里以豆瓣返回的数据演示(上网查找的).

接口返回的字段

在接口返回的字段中,去掉了我们不需要的字段
在这里插入图片描述

项目架构

类似于如下架构, 这里等下我们所讲PageKeyedDataSource的用法类和方法。
在这里插入图片描述

准备工作

1.添加依赖

这里我们使用Retrofit作为网络请求库,Glide作为图片加载库。项目中还用到了LiveData和ViewModel,因此还需要添加LifeCycle的依赖,还需要Paging(分页加载数据)和RecyclerView(显示数据)的依赖

//paging的依赖
def paging_version = "2.1.2"
    implementation "androidx.paging:paging-runtime:$paging_version"
    testImplementation "androidx.paging:paging-common:$paging_version"
    //recyclerView的依赖
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    //viewModel的依赖
    implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
    //retrofit的依赖
    implementation "com.squareup.retrofit2:retrofit:2.6.2"
    implementation "com.squareup.retrofit2:converter-gson:2.6.2"
    //glide的依赖
    implementation 'com.github.bumptech.glide:glide:4.9.0'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
2.添加网络权限
 <uses-permission android:name="android.permission.INTERNET"/>
  • 1

构造网络请求框架

就是将我们retrofit给进行了一层封装,也就是我们api文件里面的类
这里没学过retrofit的朋友,可以去自行了解以下(这里就不多说了)。

Api接口
public interface Api {
    @GET("movie/in_theaters")
    Call<Movies> getMovies(
            @Query("start") int since,
            @Query("count") int perPage,
       
    );
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
RetrofitClient类
public class RetrofitClient {
    private  static  final  String BASE_URL = "https://api.douban.com/v2/";
    private  static  RetrofitClient retrofitClient;
    private Retrofit retrofit;

    public RetrofitClient() {
        retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

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

    public  Api getApi(){
        return  retrofit.create(Api.class);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

到时候你们用的时候,需要修改Api接口和RetrofitClient类中的BASE_URL

创建Model类

用来保存每次服务端返回的数据

Movies

这个类中的数据是返回给PagedList中的 保存了所有的电影

public class Movies
{
	//当前返回的数列
	public int count;
	
	//起始位置
	public int start;
	
	//一共多少数据
	public int total;
	
	//返回的电影列表
	@SerializedName("subjects")
	public List<Movie> movieList;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
Movie

Movie保存每一部电影的信息

public class Movie
{
	public String id;
	public String title;
	public String year;
	public Images images;
	
	public class Images{
		public String small;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

使用Paging组件分页请求数据并显示

我们根据下图一步步来完成Paging组件分页请求数据并显示
在这里插入图片描述

MovieDataSource

MovieDataSource继承自PositionalDataSource,通过API Service得到网络数据

public class MovieDataSource extends PositionalDataSource<Movie>
{
	//每次加载的数据
	public static final int PER_PAGE = 8;
	
	//第一次请求会调用这个方法	
	@Override
	public void loadInitial(final LoadInitialParam params,final LoadInitialCallback<Movie> callback){
		//从第0条数据开始加载
		int startPosition = 0;
		//调用我们之前封装好的RetrofitClient请求网络数据
		RetrofitClient.getInstance()
		.getApi()
		.getMovies(startPosition,PER_PAGE)
		.enqueue(new Callback<Movies>(){
				@Override
				public void onResponse(Call<Movies> call,Response<Movies> response)
				{
					if(response.body()!=null){
						//如果请求到了数据,就将请求到的数据发送出去
						callback.onResult(response.body().movieList,
						response.body().start,
						response.body().total);
					
					}
				}
				@Override
                    public void onFailure(Call<Movies> call, Throwable t) {
                        Log.e("true",t.toString());
                    } 
			})
		)
	//接下来的每次请求都会调用该方法
	@Override
	public void loadInitial(final LoadInitialParam params,final LoadInitialCallback<Movie> callback){
		//调用我们之前封装好的RetrofitClient请求网络数据
		RetrofitClient.getInstance()
		.getApi()
		.getMovies(startPosition,PER_PAGE)
		.enqueue(new Callback<Movies>(){
				@Override
				public void onResponse(Call<Movies> call,Response<Movies> response)
				{
					if(response.body()!=null){
						//如果请求到了数据,就将请求到的数据发送出去
						callback.onResult(response.body().movieList);
					
					}
				}
				@Override
                    public void onFailure(Call<Movies> call, Throwable t) {
                        Log.e("true",t.toString());
                    } 
			})
		)
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
loadInitial()方法

当页面首次加载数据时会调用loadInitial()方法。在该方法内,我们调用API接口,并设置从第1条数据开始加载。加载成功后,需要通过callback.onResult()方法将数据返回给PagedList,否则数据不会呗展示。

totalCount()与setEnablePlaceholders()

在 callback.onResult()方法中,需要注意的是第3个参数 int totalCount.如果在PagedList.Config中设置了setEnablePlaceholders()方法的值为true,那么需要通过totalCount参数告知PagedList当前服务端数据的总数。
setEnablePlaceholders()方法的作用是,是否需要为那些"数量已知,但尚未加载处理的数据"预留位置。 例如,我们通过loadInitial()方法首次请求数据,获取了8部电影的数据,并获知一共有70部新电信在上映,如果设置setEnablePlaceholders()为true,并且通过callback.onResult()方法的totalCount将70告诉了PagedList.那么RecyclerView一共会为你预留70个Item的位置。此时,将网络关闭。也可以看到,另外68个Item的效果。

需要注意的是:setEnablePlaceholders()默认为true,如果数据量很大的话,请设置为false,不然会消耗不必要的性能

loadRange()

loadRange()是加载下一页的数据。加载成功后,也需要通过callback.onResult()方法将数据返回给PagedList. 需要注意的是:start参数并不需要我们手动进行管理,Paging组件内部已经替我们完成了这些工作。

MovieDataSourceFactory

MovieDataSourceFactory 负责创建MovieDataSource,并使用LiveData包装MovieDataSource,将其暴露给MoveiViewModel

public class MovieDataSourceFactory extends DataSource.Factory(Integer,Moive){
	private MutableLiveData<MovieDataSource> liveDataSource=new MutableLiveData<>();
	@Override
	public DataSource<Integer,Movie> create()
	{
		MovieDataSource dataSource = new MovieDataSource();
		liveDataSource.postValue(dataSource);
		return dataSource;
	}	
	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
MovieViewModel

在MovieViewModel中通过LivePagedListBuilder创建和配置PagedList,并使用LiveData包装PagedList,将其暴露给MainActivity

public class  MovieViewModel extends ViewModel
{
	public LiveData<PageList<Movie>> moviePagedList;
	
	public MovieViewModel()
	{
		PagedList.Config config = (new PagedList.Config.Builder())
		.setEnablePlaceholders(true)
        .setPageSize(MovieDataSource.PER_PAGE)
        .setPrefetchDistance(3)
        .setInitialLoadSizeHint(MovieDataSource.PER_PAGE*4)
        .setMaxSize(65536*MovieDataSource.PER_PAGE)
        .build();

        moviePagedList= (new LivePagedListBuilder<>(new MovieDataSourceFactory(),config)).build();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

PagedList.Config中的几个重要方法:

  1. setEnablePlaceholders:用于设置控件占位。
  2. setPageSize: 设置每页的大小,该值通常与DataSource中请求数据的参数值保持一致。
  3. setPrefetchDistance:设置当距离底部还有多少条数据时开始加载下一页数据
  4. setInitialLoadSizeHint:设置首次加载数据的数量。该值要求是PageSize的整数倍。若未设置,则默认是PageSize的3倍。
  5. setMaxSize:设置PagedList所能承受的最大数量,一般来说是PageSize的许多倍,超过该值可能会出现异常。

MoviePagedListAdapter

列表数据通过MoviePagedListAdapter进行展示

public class MoviePagedListAdapter extends PagedListAdapter<Movie,MoviePagedListAdapter.MovieViewHolder > {
    private Context context;

    public MoviePagedListAdapter (Context context) {
        super(DIFF_CALLBACK);
        this.context = context;
    }
    private  static DiffUtil.ItemCallback<Movie> DIFF_CALLBACK = new DiffUtil.ItemCallback<Movie>() {
        @Override
        public boolean areItemsTheSame(@NonNull MovieoldItem, @NonNull Movie newItem) {
            return oldItem.id.equals(newItem.id);
        }

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

    @NonNull
    @Override
    public MoviePagedListAdapter.MovieViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.user_item,parent,false);
        return new UserViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MoviePagedListAdapter .MovieViewHolder holder, int position) {
        Movie movie = getItem(position);
        Log.e("true",movie .name);
        if(movie !=null){
            Glide.with(context).load(movie .avatar).placeholder(R.drawable.ic_launcher_background).into(holder.imageView);
            holder.textView.setText(movie .name);
        }else{
            holder.imageView.setImageResource(R.drawable.ic_launcher_background);
            holder.textView.setText("小鑫好看錒");
        }
    }

    class MovieViewHolder extends RecyclerView.ViewHolder{
        TextView textView;
        ImageView imageView;
        public UserViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv_text);
            imageView = itemView.findViewById(R.id.iv_img);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

MoviePagedListAdapter 需要继承自PagedListAdapter。在onBindViewHolder()方法中调用getItem()方法。若当前有数据,则直接将数据与UI控件进行绑定;若没有数据,则getItem()会通知PagedList去获取下一页的数据,PagedList收到通知后,让DataSource执行具体的数据获取工作。

关于DiffUtil工具的介绍

DiffUtil工具用于计算两个数据列表之间的差异。在此之前,当我们更数据时,需要通过notifyDataSetChanged()方法对整个数据源进行刷新,这样的作法效率并不高,如果使用DiffUtil的话,它只会更新需要更新的数据,而不需要更新整个数据源。
DiffUtil中主要有两个方法,通过这两个方法,让更新数据变得更高效。

  • areItemsTheSame: 当DiffUtil想要检测两个对象是否代表同一个Item时,调用该方法进行判断。
  • areContentsTheSame: 当DiffUtil想要检测两个Item是否存在不一样的数据时,调用该方法进行判断。

MainActivity

在其中,我们将RecyclerView与PagedListAdapter进行绑定。当数据发生变化时,该变化通过LiveData传递过来,再通过PagedLIstAdapter.submitList()方法刷新数据。

public class MainActivity extends AppCompatActivity{

    private RecyclerView recyclerView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = findViewById(R.id.recycleView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        final MoviePagedListAdapter moviePagedListAdapter = new MoviePagedListAdapter (this);
        MovieViewModel movieViewModel = new ViewModelProvider(this).get(MovieViewModel .class);
        movieViewModel.moviePageList.observe(this, new Observer<PagedList<User>>() {
            @Override
            public void onChanged(PagedList<Movie> movies) {
                Log.e("true",movies.toString());
                moviePagedListAdapter .submitList(movies);
            }
        });
        recyclerView.setAdapter(moviePagedListAdapter );
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

好了,到这里PositionalDataSource的基本使用就结束了,你们需要更改下API接口,与API接口返回数据对应的Model即可使用了。

PageKeyedDataSource的基本使用

前面我们已经介绍了,PageKeyedDataSource适用于数据源以"页"的方式进行请求的情况。接下来,我们来看看PageKeyedDataSource是如何使用的。

1. API接口

https://api.stackexchange.com/2.2/users?page=1&pagesize=6&site=stackoverflow
  • 1

参数page表示从第一页开始获取数据。参数pagesize表示每页6天数据。site参数表示数据来源是StavkOverflow网站,该参数是API接口必须要携带的,对于分页并没有意义。

接口返回的数据

在这里插入图片描述
由于返回的数据过多,我们定义的Model只选取了我们 需要的数据。

项目架构

在这里插入图片描述

准备工作

1.导入依赖
	//导入viewModel
 	implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
 	//导入retrofit
    implementation "com.squareup.retrofit2:retrofit:2.6.2"
    implementation "com.squareup.retrofit2:converter-gson:2.6.2"
    //导入glide
    implementation 'com.github.bumptech.glide:glide:4.9.0'
    //导入paging
    def paging_version = "2.1.2"
    implementation "androidx.paging:paging-runtime:$paging_version"
    testImplementation "androidx.paging:paging-common:$paging_version"
    //导入recyclerView
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
2.添加权限

由于需要用到网络请求,我们需要添加网络权限

 <uses-permission android:name="android.permission.INTERNET"/>
  • 1

封装网络请求类

Api
public interface Api {
    @GET("users")
    Call<UserResponse> getUsers(
            @Query("page") int page,
            @Query("pagesize") int pageSize,
            @Query("site") String site
    );
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
RetrofitClient
public class RetrofitClient {
    private  static  final  String BASE_URL = "https://api.stackexchange.com/2.2/";
    private  static  RetrofitClient retrofitClient;
    private Retrofit retrofit;

    public RetrofitClient() {
        retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

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

    public  Api getApi(){
        return  retrofit.create(Api.class);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

Retrofit的知识就不多说了,直接就可以代码了,不懂的可以自己去百度百度。

Model类

public class User {
    @SerializedName("account_id")
    public  String id;

    @SerializedName("display_name")
    public  String name;

    @SerializedName("profile_image")
    public  String avatar;

    public User(String id, String name, String avatar) {
        this.id = id;
        this.name = name;
        this.avatar = avatar;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
public class UserResponse {
    @SerializedName("items")
    public List<User> users;

    @SerializedName("has_more")
    public  boolean hasMore;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

hasMore代表该接口是否还有更多的数据,这里我们用它进行判断,是否还需要加载下一页的数据。在这里插入图片描述

DataSource类

public class  UserDataSource  extends PageKeyedDataSource<Integer, User> {
    public  static  final  int FIRST_PAGE = 1;
    public  static  final  int PER_PAGE = 8;
    public  static  final  String SITE = "stackoverflow";
    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, User> callback) {
        RetrofitClient.getInstance()
                .getApi()
                .getUsers(FIRST_PAGE,PER_PAGE,SITE)
                .enqueue(new Callback<UserResponse>() {
                    @Override
                    public void onResponse(Call<UserResponse> call, Response<UserResponse> response) {
                        Log.e("true",response.body().users.toString());
                        if(response.body()!=null){
                            callback.onResult(response.body().users,null,FIRST_PAGE+1);
                        }
                    }

                    @Override
                    public void onFailure(Call<UserResponse> call, Throwable t) {
                        Log.e("true",t.toString());
                    }
                });
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, User> callback) {

    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, User> callback) {
        RetrofitClient.getInstance()
                .getApi()
                .getUsers(params.key,PER_PAGE,SITE)
                .enqueue(new Callback<UserResponse>() {
                    @Override
                    public void onResponse(Call<UserResponse> call, Response<UserResponse> response) {
                        if(response.body()!=null){
                            Log.e("true",params.key+"");
                            Integer nextKey = response.body().hasMore?params.key+1:null;
                            callback.onResult(response.body().users,nextKey);
                        }
                    }

                    @Override
                    public void onFailure(Call<UserResponse> call, Throwable t) {

                    }
                });
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

该类继承自PageKeyedDataSource.。我们主要实现两个方法,loadInitial()与loadAfter()方法

loadInitial()方法: 和PositionalDataSource中的loadInitial()是一样的作用,都是加载第一页的数据,但是加载成功后的,都需要callback.onResult()方法将数据返回给PagedList。 但是传递的参数却不同。

上面我们详细说了PositionalDataSource中的callback.onResult()方法,这里就只说PageKeyedDataSource中的callback.onResult()方法

 public abstract void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
                @Nullable Key nextPageKey);
  • 1
  • 2

第1个参数是加载得到的数据,将其交给PagedList。第2个参数是上一页的key。在此,由于当前加载的是第一页,不存在上一页,所以设置为null。第3个参数为下一页的key,即当前页key的值加上1,若不存在下一页,则返回null.

loadAfter()方法: 加载下一页的工作在该方法内进行。需要注意的是LoadParams params参数,我们在loadInitial()方法中设置的nextPageKey,正是通过LoadParams传递过来的。LoadParams.key得到的是下一页的key,通过这个key,我们就可以请求下一页。请求下一页成功后,同样也是通过callback.onResult()方法将数据返回给PagedList,同时再设置下一页的key。注意:在设置下一页之前,需要判断是否还有更多的数据,这时候的API接口返回的hasMore参数就起作用了,可以通过该参数,判断是否还有下一页数据,若没有数据,则将下一页的key设置为null.

 Log.e("true",params.key+"");
Integer nextKey = response.body().hasMore?params.key+1:null;                    
callback.onResult(response.body().users,nextKey);
  • 1
  • 2
  • 3

在这里插入图片描述
当我们不断滑动的时候,params.key是不断在变化,直到没有下一页数据的时候,会返回null。

UserDataSourceFactory

UserDataSourceFactory负责创建UserDataSource,并使用LiveData包装UserDataSource,将其暴露给UserViewModel

public class UsersDataSourceFactory  extends DataSource.Factory<Integer, User> {

    private MutableLiveData<UserDataSource> liveData = new MutableLiveData<>();
    @NonNull
    @Override
    public DataSource<Integer, User> create() {
        UserDataSource dataSource = new UserDataSource();
        liveData.postValue(dataSource);
        return dataSource;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

UserViewModel

在UserViewModel中通过LivePagedListBuilder创建和配置PagedList,并使用LiveData包装PagedList,将其暴露给MainActivity

public class UserViewModel  extends ViewModel {
    public LiveData<PagedList<User>> userPageList;

    public UserViewModel() {
        PagedList.Config config = (new PagedList.Config.Builder())
                .setEnablePlaceholders(true)
                .setPageSize(UserDataSource.PER_PAGE)
                .setPrefetchDistance(3)
                .setInitialLoadSizeHint(UserDataSource.PER_PAGE*4)
                .setMaxSize(65536*UserDataSource.PER_PAGE)
                .build();

        userPageList = (new LivePagedListBuilder<>(new UsersDataSourceFactory(),config)).build();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

上面详细说了config中各个参数的作用,这里就不再细说了。

UserPagedListAdapter

列表数据通过UserPagedListAdapter进行展示

public class UserPagedListAdapter  extends PagedListAdapter<User,UserPagedListAdapter.UserViewHolder> {
    private Context context;

    public UserPagedListAdapter(Context context) {
        super(DIFF_CALLBACK);
        this.context = context;
    }
    private  static DiffUtil.ItemCallback<User> DIFF_CALLBACK = new DiffUtil.ItemCallback<User>() {
        @Override
        public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) {
            return oldItem.id.equals(newItem.id);
        }

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

    @NonNull
    @Override
    public UserPagedListAdapter.UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.user_item,parent,false);
        return new UserViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull UserPagedListAdapter.UserViewHolder holder, int position) {
        User user = getItem(position);
//        Log.e("true",user.name);
        if(user!=null){
            Glide.with(context).load(user.avatar).placeholder(R.drawable.ic_launcher_background).into(holder.imageView);
            holder.textView.setText(user.name);
        }else{
            holder.imageView.setImageResource(R.drawable.ic_launcher_background);
            holder.textView.setText("小鑫好看錒");
        }
    }

    class UserViewHolder extends RecyclerView.ViewHolder{
        TextView textView;
        ImageView imageView;
        public UserViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv_text);
            imageView = itemView.findViewById(R.id.iv_img);
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

MainActivity

public class MainActivity extends AppCompatActivity{

    private RecyclerView recyclerView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = findViewById(R.id.recycleView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        final UserPagedListAdapter userPagedListAdapter = new UserPagedListAdapter(this);
        UserViewModel userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
        userViewModel.userPageList.observe(this, new Observer<PagedList<User>>() {
            @Override
            public void onChanged(PagedList<User> users) {
//                Log.e("true",users.toString());
                userPagedListAdapter.submitList(users);
            }
        });
        recyclerView.setAdapter(userPagedListAdapter);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

MainActivity布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycleView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

RecyclerView子布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <ImageView
        android:id="@+id/iv_img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher_background"
        />
    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="25sp"
        android:text="25"/>
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

运行程序,效果如下:

在这里插入图片描述
无限滚动的效果就出来了,是不是感觉就是一次加载出来的。

好了,这节课篇幅也比较长了,剩下的paging知识点就放在下一节唠叨把。不足之处,欢迎大家留言,大家下期再见。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/286506
推荐阅读
相关标签
  

闽ICP备14008679号