Paging是一个用于分页加载的组件,对于一些列表数据,以前大家都使用过的一种方式是定义一个page,当达到加载更多条件时,page加一再去请求数据,为此我们要写很多重复的代码,Paging就对分页进行了一个封装
Paging由分为三个模块
1.DataSource:数据从该模块中获取,数据可以来源于网络、本地数据库等
2.PagedList:负责具体获取数据的逻辑,何时获取、加载下一页、预加载等
3.PagedListAdapter:RecyclerView的adapter需要继承它,内部做了一系列处理
一、Paging上手
1.PositionalDataSource
PositionalDataSource适合于从任意位置获取数据的情况,入参为开始点和数据量大小
首先我们要获取网络数据、使用LiveData、Paging等,需要添加依赖:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.paging:paging-runtime:2.1.2'
根据服务器返回数据,创建实体类
服务器数据:
{
"count":5,
"start":0,
"total":100,
"subjects":[
{
"id":35076714,
"title":"扎克·施奈德版正义联盟",
"cover":"https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2634360594.webp",
"rate":"8.9"
},
{
"id":26935283,
"title":"侍神令",
"cover":"https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2629260713.webp",
"rate":"5.8"
},
{
"id":35145068,
"title":"双层肉排",
"cover":"https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2633977758.webp",
"rate":"6.7"
},
{
"id":33433405,
"title":"大地",
"cover":"https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2628845704.webp",
"rate":"6.6"
},
{
"id":35167535,
"title":"租来的朋友",
"cover":"https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2616903233.webp",
"rate":"6.1"
}
]
}
实体类:
package com.aruba.paging.entity;
import java.util.Objects;
/**
* Created by aruba on 2021/9/17.
*/
public class Movie {
//唯一id
public int id;
//标题
public String title;
//图片地址
public String cover;
//评分
public String rate;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Movie movie = (Movie) o;
return id == movie.id && title.equals(movie.title) && cover.equals(movie.cover) && rate.equals(movie.rate);
}
@Override
public int hashCode() {
return Objects.hash(id, title, cover, rate);
}
@Override
public String toString() {
return "Movie{" +
"id=" + id +
", title='" + title + '\'' +
", cover='" + cover + '\'' +
", rate='" + rate + '\'' +
'}';
}
}
package com.aruba.paging.entity;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import java.util.Objects;
/**
* Created by aruba on 2021/9/17.
*/
public class Movies {
//当前有多少条数据
public int count;
//从哪条开始的
public int start;
//一共多少条
public int total;
@SerializedName("subjects")
public List<Movie> movies;
@Override
public String toString() {
return "Movies{" +
"count=" + count +
", start=" + start +
", total=" + total +
", movies=" + movies +
'}';
}
}
定义接口相关:
package com.aruba.paging.api;
import android.annotation.TargetApi;
import com.aruba.paging.entity.Movies;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
* Created by aruba on 2021/9/17.
*/
public interface Api {
@GET("pds.do")
Call<Movies> getMovies(@Query("start") int start, @Query("count") int count);
}
package com.aruba.paging.api;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* Created by aruba on 2021/9/17.
*/
public class RetrofitClient {
private static final String BASE_URL = "http://192.168.17.114:8080/pagingserver_war/";
private static RetrofitClient instance = new RetrofitClient();
private Retrofit retrofit;
static RetrofitClient getInstance() {
if (instance == null) {
instance = new RetrofitClient();
}
return instance;
}
private RetrofitClient() {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(getOkhttpClient())
.build();
}
private OkHttpClient getOkhttpClient() {
return new OkHttpClient.Builder().build();
}
public Api getApi() {
return retrofit.create(Api.class);
}
}
定义DataSource,继承至PositionalDataSource:
package com.aruba.paging.paging.model;
import androidx.annotation.NonNull;
import androidx.paging.PositionalDataSource;
import com.aruba.paging.api.RetrofitClient;
import com.aruba.paging.entity.Movie;
import com.aruba.paging.entity.Movies;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Created by aruba on 2021/9/17.
*/
public class MovieDataSource extends PositionalDataSource<Movie> {
//一次取8条数据
public static final int PER_PAGE = 8;
//第一次加载
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Movie> callback) {
int startPosition = 0;
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) {
//数据回调给pagedlist
callback.onResult(response.body().movies,
response.body().start,
response.body().total);
}
}
@Override
public void onFailure(Call<Movies> call, Throwable t) {
}
});
}
//加载更多
@Override
public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Movie> callback) {
RetrofitClient.getInstance()
.getApi()
//入参:开始点,一次获取的数据量大小
//加载更多时,params中的startPosition参数会自动加PER_PAGE
.getMovies(params.startPosition, PER_PAGE)
.enqueue(new Callback<Movies>() {
@Override
public void onResponse(Call<Movies> call, Response<Movies> response) {
if (response.body() != null) {
//数据回调给pagedlist
callback.onResult(response.body().movies);
}
}
@Override
public void onFailure(Call<Movies> call, Throwable t) {
}
});
}
}
接下来在定义PagedList之前,需要定义一个DataSource的Factory,PagedList需要通过这个Factory来拿到DataSource对象
package com.aruba.paging.paging.factory;
import androidx.annotation.NonNull;
import androidx.paging.DataSource;
import com.aruba.paging.entity.Movie;
import com.aruba.paging.paging.model.MovieDataSource;
/**
* Created by aruba on 2021/9/17.
*/
public class MovieDataSourceFactory extends DataSource.Factory<Integer, Movie> {
@NonNull
@Override
public DataSource<Integer, Movie> create() {
return new MovieDataSource();
}
}
定义一个ViewModel,把PagedList作为LiveData对象
package com.aruba.paging.paging.viewmodel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import androidx.paging.LivePagedListBuilder;
import androidx.paging.PagedList;
import com.aruba.paging.entity.Movie;
import com.aruba.paging.paging.factory.MovieDataSourceFactory;
import com.aruba.paging.paging.model.MovieDataSource;
/**
* Created by aruba on 2021/9/17.
*/
public class MovieViewModel extends ViewModel {
public LiveData<PagedList<Movie>> pagedListLiveData;
public MovieViewModel() {
//配置项
PagedList.Config config = new PagedList.Config.Builder()
//最大加载多少条数据
.setMaxSize(1000 * MovieDataSource.PER_PAGE)
//首次加载多少条数据
.setInitialLoadSizeHint(MovieDataSource.PER_PAGE * 2)
//设置距离底部还有多少条数据时开始加载下一页的数据
.setPrefetchDistance(2)
//一页显示多少条
.setPageSize(MovieDataSource.PER_PAGE)
//设置控件占位
.setEnablePlaceholders(false)
.build();
//通过Factory和Config配置
pagedListLiveData = new LivePagedListBuilder<>(new MovieDataSourceFactory(), config)
.build();
}
}
接下来就是UI展示了,定义RecyclerView的Adapter,继承于PagedListAdapter,内部还需要需要一个diffCallback,用来刷新数据用:
package com.aruba.paging.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.paging.PagedListAdapter;
import androidx.recyclerview.widget.AsyncDifferConfig;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.aruba.paging.R;
import com.aruba.paging.entity.Movie;
import com.squareup.picasso.Picasso;
/**
* Created by aruba on 2021/9/17.
*/
public class MovieListAdapter extends PagedListAdapter<Movie, MovieListAdapter.ViewHolder> {
//数据源更新时,只会更新不一样的,而不是整个adapter刷新
private static final DiffUtil.ItemCallback<Movie> diffCallback = new DiffUtil.ItemCallback<Movie>() {
@Override
public boolean areItemsTheSame(@NonNull Movie oldItem, @NonNull Movie newItem) {
return oldItem == newItem;
}
@Override
public boolean areContentsTheSame(@NonNull Movie oldItem, @NonNull Movie newItem) {
return oldItem.equals(newItem);
}
};
public MovieListAdapter() {
super(diffCallback);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View item = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new ViewHolder(item);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Movie movie = getItem(position);
holder.textViewTitle.setText(movie.title);
holder.textViewRate.setText(movie.rate);
Picasso.get()
.load(movie.cover)
.placeholder(R.drawable.ic_launcher_foreground)
.error(R.drawable.ic_launcher_foreground)
.into(holder.imageView);
}
public class ViewHolder extends RecyclerView.ViewHolder {
private ImageView imageView;
private TextView textViewTitle;
private TextView textViewRate;
public ViewHolder(@NonNull View itemView) {
super(itemView);
this.imageView = itemView.findViewById(R.id.imageView);
this.textViewTitle = itemView.findViewById(R.id.textViewTitle);
this.textViewRate = itemView.findViewById(R.id.textViewRate);
}
}
}
Activity中配置RecyclerView并实例化ViewModel进行数据观测:
package com.aruba.paging;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.paging.PagedList;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.Context;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.KeyboardView;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.text.method.BaseKeyListener;
import android.text.method.DateKeyListener;
import android.text.method.KeyListener;
import android.text.method.NumberKeyListener;
import android.text.method.TextKeyListener;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import com.aruba.paging.adapter.MovieListAdapter;
import com.aruba.paging.entity.Movie;
import com.aruba.paging.paging.viewmodel.MovieViewModel;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private MovieListAdapter movieListAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
movieListAdapter = new MovieListAdapter();
recyclerView.setAdapter(movieListAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
//实例化ViewModel
MovieViewModel movieViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MovieViewModel.class);
//设置pagedList的数据变化监听
movieViewModel.pagedListLiveData.observe(this, new Observer<PagedList<Movie>>() {
@Override
public void onChanged(PagedList<Movie> movies) {
//将数据放入adapter
movieListAdapter.submitList(movies);
}
});
}
}
效果:
2.PageKeyedDataSource
PageKeyedDataSource适合于按页分页的情况,需要一个页数和一页数据量大小
服务器返回数据:
{
"has_more":true,
"subjects":[
{
"id":35076714,
"title":"扎克·施奈德版正义联盟",
"cover":"https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2634360594.webp",
"rate":"8.9"
},
{
"id":26935283,
"title":"侍神令",
"cover":"https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2629260713.webp",
"rate":"5.8"
},
{
"id":35145068,
"title":"双层肉排",
"cover":"https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2633977758.webp",
"rate":"6.7"
},
{
"id":33433405,
"title":"大地",
"cover":"https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2628845704.webp",
"rate":"6.6"
},
{
"id":35167535,
"title":"租来的朋友",
"cover":"https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2616903233.webp",
"rate":"6.1"
}
]
}
有了上面的基础,我们再修改下返回的Movies实体类:
package com.aruba.paging2.entity;
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* Created by aruba on 2021/9/17.
*/
public class Movies {
//是否还有下一页
@SerializedName("has_more")
public boolean hasMore;
@SerializedName("subjects")
public List<Movie> movies;
}
修改下api
package com.aruba.paging2.api;
import com.aruba.paging2.entity.Movies;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
* Created by aruba on 2021/9/17.
*/
public interface Api {
@GET("pkds.do")
Call<Movies> getMovies(
@Query("page") int page,
@Query("pagesize") int pagesize
);
}
定义DataSource继承于PageKeyedDataSource,在初次加载和下一页加载中调用网络请求
package com.aruba.paging2.paging.model;
import androidx.annotation.NonNull;
import androidx.paging.PageKeyedDataSource;
import androidx.paging.PositionalDataSource;
import com.aruba.paging2.api.RetrofitClient;
import com.aruba.paging2.entity.Movie;
import com.aruba.paging2.entity.Movies;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Created by aruba on 2021/9/17.
*/
public class MovieDataSource extends PageKeyedDataSource<Integer, Movie> {
public static final int PER_PAGE = 8;
public static final int FIRST_PAGE = 1;
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Movie> callback) {
RetrofitClient.getInstance().getApi()
.getMovies(FIRST_PAGE, PER_PAGE)
.enqueue(new Callback<Movies>() {
@Override
public void onResponse(Call<Movies> call, Response<Movies> response) {
//第一次加载,上一页page为null,下一页传入page+1
callback.onResult(response.body().movies, null, FIRST_PAGE + 1);
}
@Override
public void onFailure(Call<Movies> call, Throwable t) {
}
});
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Movie> callback) {
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Movie> callback) {
RetrofitClient.getInstance().getApi()
.getMovies(params.key, PER_PAGE)
.enqueue(new Callback<Movies>() {
@Override
public void onResponse(Call<Movies> call, Response<Movies> response) {
if(response.body() == null){
return;
}
//把数据传递给PagedList
Integer nextKey = response.body().hasMore ? params.key + 1 : null;
callback.onResult(response.body().movies, nextKey);
}
@Override
public void onFailure(Call<Movies> call, Throwable t) {
}
});
}
}
其他的就不用改动了,效果和上面是一样的
3.ItemKeyedDataSource
ItemKeyedDataSource使用于不固定的数据列表,如帖子,因为新增帖子比较频繁,使用上面两种可能会出现重复数据,需要参数为实体类唯一值和数据量大小
服务器返回数据:
[
{
"id":35076714,
"title":"扎克·施奈德版正义联盟",
"cover":"https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2634360594.webp",
"rate":"8.9"
},
{
"id":26935283,
"title":"侍神令",
"cover":"https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2629260713.webp",
"rate":"5.8"
},
{
"id":35145068,
"title":"双层肉排",
"cover":"https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2633977758.webp",
"rate":"6.7"
},
{
"id":33433405,
"title":"大地",
"cover":"https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2628845704.webp",
"rate":"6.6"
},
{
"id":35167535,
"title":"租来的朋友",
"cover":"https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2616903233.webp",
"rate":"6.1"
}
]
直接返回一个列表,我们就不需要Movies类了,修改下api:
package com.aruba.paging3.api;
import com.aruba.paging3.entity.Movie;
import com.aruba.paging3.entity.Movies;
import java.util.List;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
* Created by aruba on 2021/9/17.
*/
public interface Api {
@GET("ikds.do")
Call<List<Movie>> getMovies(
@Query("since") int since,
@Query("pagesize") int pagesize
);
}
定义DataSource继承于ItemKeyedDataSource,getKey方法中返回实体类的唯一值,内部会使用最后一个数据的唯一值作为下次查询的参数
package com.aruba.paging2.paging.model;
import androidx.annotation.NonNull;
import androidx.paging.ItemKeyedDataSource;
import com.aruba.paging2.api.RetrofitClient;
import com.aruba.paging2.entity.Movie;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Created by aruba on 2021/9/17.
*/
public class MovieDataSource extends ItemKeyedDataSource<Integer, Movie> {
public static final int PER_PAGE = 8;
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Movie> callback) {
RetrofitClient.getInstance().getApi()
.getMovies(0, PER_PAGE)
.enqueue(new Callback<List<Movie>>() {
@Override
public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) {
if (response.body() != null)
callback.onResult(response.body());
}
@Override
public void onFailure(Call<List<Movie>> call, Throwable t) {
}
});
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Movie> callback) {
RetrofitClient.getInstance().getApi()
.getMovies(params.key, PER_PAGE)
.enqueue(new Callback<List<Movie>>() {
@Override
public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) {
if (response.body() != null)
callback.onResult(response.body());
}
@Override
public void onFailure(Call<List<Movie>> call, Throwable t) {
}
});
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Movie> callback) {
}
@NonNull
@Override
public Integer getKey(@NonNull Movie item) {
return item.id;
}
}
效果和上面也是一样的
二、本地数据缓存
BoundaryCallback
有时我们想要把数据缓存到本地,然后无网络时就可以加载本地数据,那么可以用BoundaryCallback
依赖Room数据库和刷新控件:
implementation 'androidx.room:room-runtime:2.3.0-rc01'
annotationProcessor 'androidx.room:room-compiler:2.3.0-rc01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01'
把Movie类改造成Entity,由于服务器id为随机的,我们内置一个自增长的主键
package com.aruba.paging4.entity;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import java.util.Objects;
/**
* Created by aruba on 2021/9/17.
*/
@Entity
public class Movie {
//作为本地表的主键
@PrimaryKey(autoGenerate = true)
public int key;
//唯一id
public int id;
//标题
public String title;
//图片地址
public String cover;
//评分
public String rate;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Movie movie = (Movie) o;
return id == movie.id && title.equals(movie.title) && cover.equals(movie.cover) && rate.equals(movie.rate);
}
@Override
public int hashCode() {
return Objects.hash(key, id, title, cover, rate);
}
}
定义Dao和Database相关:
package com.aruba.paging4.database.dao;
import androidx.paging.DataSource;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import com.aruba.paging4.entity.Movie;
import java.util.List;
/**
* Created by aruba on 2021/9/18.
*/
@Dao
public interface MovieDao {
@Insert
void insertMovies(List<Movie> movies);
@Query("DELETE FROM movie")
void clear();
@Query("SELECT * FROM movie")
DataSource.Factory<Integer, Movie> getMovieList();
}
package com.aruba.paging4.database;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.DatabaseConfiguration;
import androidx.room.InvalidationTracker;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import com.aruba.paging4.database.dao.MovieDao;
import com.aruba.paging4.entity.Movie;
/**
* Created by aruba on 2021/9/18.
*/
@Database(entities = {Movie.class}, version = 1, exportSchema = true)
public abstract class MyDataBase extends RoomDatabase {
private static final String DBNAME = "my.db";
private static MyDataBase instance;
public static MyDataBase getInstance() {
if (instance == null) throw new NullPointerException("database not init!!");
return instance;
}
public static synchronized MyDataBase init(Context context) {
if (instance == null)
instance = Room.databaseBuilder(context.getApplicationContext()
, MyDataBase.class, DBNAME)
.fallbackToDestructiveMigration()
.build();
return instance;
}
public abstract MovieDao getMovieDao();
}
定义BoundaryCallback:
package com.aruba.paging4.paging.boundarycallback;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.paging.PagedList;
import com.aruba.paging4.api.RetrofitClient;
import com.aruba.paging4.database.MyDataBase;
import com.aruba.paging4.entity.Movie;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Created by aruba on 2021/9/18.
*/
public class MovieBoundaryCallback extends PagedList.BoundaryCallback<Movie> {
public static final int PER_PAGE = 10;
@Override
public void onZeroItemsLoaded() {
super.onZeroItemsLoaded();
//加载第一页
getTopData();
}
private void getTopData() {
RetrofitClient.getInstance().getApi()
.getMovies(0, PER_PAGE)
.enqueue(new Callback<List<Movie>>() {
@Override
public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) {
if (response.body() != null)
insertMovies(response.body());
}
@Override
public void onFailure(Call<List<Movie>> call, Throwable t) {
}
});
}
private void insertMovies(List<Movie> body) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
MyDataBase.getInstance().getMovieDao().insertMovies(body);
return null;
}
}.execute();
}
@Override
public void onItemAtEndLoaded(@NonNull Movie itemAtEnd) {
super.onItemAtEndLoaded(itemAtEnd);
//加载之后的页
getAfterData(itemAtEnd);
}
private void getAfterData(Movie movie) {
RetrofitClient.getInstance().getApi()
.getMovies(movie.id, PER_PAGE)
.enqueue(new Callback<List<Movie>>() {
@Override
public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) {
if (response.body() != null)
insertMovies(response.body());
}
@Override
public void onFailure(Call<List<Movie>> call, Throwable t) {
}
});
}
}
修改ViewModel,这时我们不需要DataSource和Factory了
package com.aruba.paging4.paging.viewmodel;
import android.os.AsyncTask;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import androidx.paging.LivePagedListBuilder;
import androidx.paging.PagedList;
import com.aruba.paging4.database.MyDataBase;
import com.aruba.paging4.entity.Movie;
import com.aruba.paging4.paging.boundarycallback.MovieBoundaryCallback;
/**
* Created by aruba on 2021/9/17.
*/
public class MovieViewModel extends ViewModel {
public LiveData<PagedList<Movie>> pagedListLiveData;
public MovieViewModel() {
//通过Dao获取Factory
pagedListLiveData = new LivePagedListBuilder<>(
MyDataBase.getInstance().getMovieDao().getMovieList(),
MovieBoundaryCallback.PER_PAGE)
.setBoundaryCallback(new MovieBoundaryCallback())
.build();
}
//刷新数据
public void refresh() {
//数据库清空后,会自动重新加载
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
MyDataBase.getInstance().getMovieDao().clear();
return null;
}
}.execute();
}
}
Activity中做一个下拉刷新,数据清空后,BoundaryCallback会自动加载第一次数据
package com.aruba.paging4;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.paging.PagedList;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.aruba.paging4.adapter.MovieListAdapter;
import com.aruba.paging4.database.MyDataBase;
import com.aruba.paging4.entity.Movie;
import com.aruba.paging4.paging.viewmodel.MovieViewModel;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private MovieListAdapter movieListAdapter;
private MovieViewModel movieViewModel;
private SwipeRefreshLayout swipeRefreshLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyDataBase.init(this);
swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout);
recyclerView = findViewById(R.id.recyclerView);
movieListAdapter = new MovieListAdapter();
recyclerView.setAdapter(movieListAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
//实例化ViewModel
movieViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MovieViewModel.class);
//设置pagedList的数据变化监听
movieViewModel.pagedListLiveData.observe(this, new Observer<PagedList<Movie>>() {
@Override
public void onChanged(PagedList<Movie> movies) {
//将数据放入adapter
movieListAdapter.submitList(movies);
}
});
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
movieViewModel.refresh();
swipeRefreshLayout.setRefreshing(false);
}
});
}
}
无网络效果:
网友评论