JetPack--Paging2

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继承于ItemKeyedDataSourcegetKey方法中返回实体类的唯一值,内部会使用最后一个数据的唯一值作为下次查询的参数

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);
    }
}

定义DaoDatabase相关:

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);
            }
        });
    }

}

无网络效果:

Demo地址:https://gitee.com/aruba/my-jetpack-application.git
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值