JetPack--Paging2

作者: aruba | 来源:发表于2021-09-18 16:43 被阅读0次

    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

    相关文章

      网友评论

        本文标题:JetPack--Paging2

        本文链接:https://www.haomeiwen.com/subject/fyprgltx.html