美文网首页
JetPack--Paging3

JetPack--Paging3

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

    前面我们使用过Paging,最新版本Paging3和以前对比,有所改动

    Paging2->Paging3三个模块改为:

    1.DataSource->PagingSource : 数据从该模块中获取,数据可以来源于网络、本地数据库等
    2.PagedList->Pager : 负责具体获取数据的逻辑,何时获取、加载下一页、预加载等
    3.PagedListAdapter->PagingDataAdapter : RecyclerView的adapter需要继承它,内部做了一系列处理

    一、paging3上手

    效果:


    1.首先配置gradle

    使用kapt插件

    plugins {
    
        id 'kotlin-kapt'
    }
    

    DataBinding支持

        defaultConfig {
    
            dataBinding{
                enabled = true
            }
        }
    

    添加依赖

        implementation 'androidx.legacy:legacy-support-v4:1.0.0'
        //依赖协程核心库 ,提供Android UI调度器
        implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
        //依赖当前平台所对应的平台库 (必须)
        implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
        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.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01'
        implementation 'androidx.paging:paging-runtime-ktx:3.0.0-beta03'
        implementation "androidx.activity:activity-ktx:1.1.0"
        implementation "androidx.fragment:fragment-ktx:1.2.5"
    
    2.根据服务器返回数据创建实体类

    服务器数据:

    {
        "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"
            }
        ]
    }
    
    
    package com.aruba.paging3application.entity
    
    /**
     * Created by aruba on 2021/9/22.
     */
    data class Movie(
        val id: Int,
        val title: String,
        val cover: String,
        val rate: String
    )
    
    package com.aruba.paging3application.entity
    
    import com.google.gson.annotations.SerializedName
    
    /**
     * Created by aruba on 2021/9/22.
     */
    data class Movies(
        @SerializedName("subjects")
        val movies: List<Movie>,
        @SerializedName("has_more")
        val hasMore: Boolean
    )
    
    3.定义网络相关

    Api:

    package com.aruba.flowapplyapplication.net
    
    import com.aruba.paging3application.entity.Movies
    import retrofit2.http.GET
    import retrofit2.http.Query
    
    /**
     * Created by aruba on 2021/9/21.
     */
    interface Api {
    
        @GET("pkds.do")
        suspend fun getMovies(
            @Query("page") page: Int,
            @Query("pagesize") pagesize: Int
        ): Movies
    
    }
    

    retrofit工具类:

    package com.aruba.flowapplyapplication.net
    
    import okhttp3.OkHttpClient
    import retrofit2.Retrofit
    import retrofit2.converter.gson.GsonConverterFactory
    
    /**
     * Created by aruba on 2021/9/21.
     */
    object RetrofitClient {
        private val instance: Retrofit by lazy {
            Retrofit.Builder()
                .baseUrl("http://192.168.17.114:8080/pagingserver_war/")
                .client(OkHttpClient.Builder().build())
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        }
    
        fun <T> getApi(clazz: Class<T>): T {
            return instance.create(clazz) as T
        }
    }
    
    4.定义RecyclerView的Adapter相关

    布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <layout 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">
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingVertical="10dip">
    
    
            <ImageView
                android:id="@+id/imageView"
                android:layout_width="100dip"
                android:layout_height="100dip"
                app:image="@{movie.cover}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/guideline2"
                app:layout_constraintHorizontal_bias="0.432"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_bias="0.054"
                tools:srcCompat="@tools:sample/avatars" />
    
            <TextView
                android:id="@+id/textViewTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{movie.title}"
                android:textSize="16sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.0"
                app:layout_constraintStart_toStartOf="@+id/guideline"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_bias="0.255"
                tools:text="泰坦尼克号" />
    
            <TextView
                android:id="@+id/textViewRate"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="24dp"
                android:text="@{movie.rate}"
                android:textSize="16sp"
                app:layout_constraintStart_toStartOf="@+id/guideline"
                app:layout_constraintTop_toBottomOf="@+id/textViewTitle"
                tools:text="评分:8.9分" />
    
            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_percent="0.4" />
    
            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_percent="0.5" />
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    
        <data>
    
            <variable
                name="movie"
                type="com.aruba.paging3application.entity.Movie" />
        </data>
    </layout>
    

    BingingAdapter,自定义标签image

    package com.aruba.paging3application.bindingAdapter
    
    import android.widget.ImageView
    import androidx.databinding.BindingAdapter
    import com.aruba.paging3application.R
    import com.squareup.picasso.Picasso
    
    /**
     * Created by aruba on 2021/9/22.
     */
    @BindingAdapter("image")
    fun setImage(imageView: ImageView, imageUrl: String) {
        Picasso.get().load(imageUrl).placeholder(R.drawable.ic_launcher_foreground)
            .error(R.drawable.ic_launcher_foreground)
            .into(imageView);
    }
    

    Adapter,继承于PagingDataAdapter,和paging2一样需要DiffUtil.ItemCallback

    package com.aruba.paging3application.adapter
    
    import android.view.LayoutInflater
    import android.view.ViewGroup
    import androidx.paging.DifferCallback
    import androidx.paging.PagingDataAdapter
    import androidx.recyclerview.widget.DiffUtil
    import com.aruba.paging3application.databinding.ItemBinding
    import com.aruba.paging3application.entity.Movie
    
    /**
     * Created by aruba on 2021/9/22.
     */
    class MoviePagingAdapter : PagingDataAdapter<Movie, BindingViewHolder>(
        object : DiffUtil.ItemCallback<Movie>() {
            override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
                return oldItem.id == newItem.id
            }
    
            override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
                return oldItem == newItem
            }
        }
    ) {
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
            val binding = ItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return BindingViewHolder(binding)
        }
    
        override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
            (holder.binding as ItemBinding).movie = getItem(position)
        }
    
    }
    
    5.定义PagingSource

    继承PagingSource,实现load函数,返回值为LoadResult,可以使用LoadResult.Page实例化,入参为继承时定义的第二个泛型,和上一页和下一页的两个Key,Key对应的第一个泛型

    package com.aruba.paging3application.paging
    
    import android.util.Log
    import androidx.paging.PagingSource
    import androidx.paging.PagingState
    import com.aruba.flowapplyapplication.net.Api
    import com.aruba.flowapplyapplication.net.RetrofitClient
    import com.aruba.paging3application.entity.Movie
    import com.aruba.paging3application.entity.Movies
    
    /**
     * Pagind获取数据层
     * Created by aruba on 2021/9/22.
     */
    class MoviePagingSource : PagingSource<Int, Movie>() {
        companion object {
            const val pageSize = 10
        }
    
        //该办法只在初始加载成功且加载页面的列表不为空的情况下被调用。
        override fun getRefreshKey(state: PagingState<Int, Movie>): Int? {
            return null
        }
    
        override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> {
            val currentPage = params.key ?: 1
            val movies = RetrofitClient.getApi(Api::class.java).getMovies(currentPage, pageSize)
    
            //上一页的key
            val prevKey: Int? = (currentPage - 1).run {
                if (this == 0) {//说明当前是第一页数据
                    null
                } else {
                    this
                }
            }
    
            //下一页的key
            val nextKey: Int? = when {
                params.loadSize > pageSize -> {//第一次加载的会多一些
                    // public val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER, 
                    // 默认PagingConfig为pager分配初始获取数据的大小为pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER
                    // 所以Pager配置时,如果initialLoadSize不指定,那么第一次加载数据并不是我们定义的pageSize
                    val count = params.loadSize / pageSize
                    count + 1
                }
                movies.hasMore -> {
                    currentPage + 1
                }
                else -> {
                    null
                }
            }
    
            return LoadResult.Page(
                data = movies.movies,
                prevKey = prevKey,
                nextKey = nextKey
            )
        }
    }
    
    6.定义Pager

    Pager可以返回一个Flow,使用一个ViewModel获取Pager的Flow,下流就可以收集

    package com.aruba.paging3application.viewmodel
    
    import androidx.lifecycle.ViewModel
    import androidx.paging.Pager
    import androidx.paging.PagingConfig
    import com.aruba.paging3application.paging.MoviePagingSource
    
    /**
     * Created by aruba on 2021/9/22.
     */
    class MovieViewModel : ViewModel() {
    
        //绑定page的flow,返回是一个flow对象
        fun bindPage() = Pager(config = PagingConfig(
            initialLoadSize = MoviePagingSource.pageSize,//初次加载的数据量大小
            pageSize = MoviePagingSource.pageSize,
            enablePlaceholders = false
        ),
            pagingSourceFactory = {
                MoviePagingSource()
            }
        ).flow
    }
    
    7.Activity中实例化ViewModel、配置RecyclerView等
    package com.aruba.paging3application
    
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import androidx.activity.viewModels
    import androidx.lifecycle.lifecycleScope
    import androidx.recyclerview.widget.LinearLayoutManager
    import com.aruba.paging3application.adapter.MoviePagingAdapter
    import com.aruba.paging3application.databinding.ActivityMainBinding
    import com.aruba.paging3application.viewmodel.MovieViewModel
    import kotlinx.coroutines.flow.collect
    
    class MainActivity : AppCompatActivity() {
        private val movieViewModel by viewModels<MovieViewModel>()
        private val binding by lazy {
            ActivityMainBinding.inflate(layoutInflater)
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(binding.root)
    
            val adapter = MoviePagingAdapter()
            binding.recyclerView.adapter = adapter
            binding.recyclerView.layoutManager = LinearLayoutManager(this)
            
            lifecycleScope.launchWhenCreated {
                movieViewModel.bindPage().collect {
                    adapter.submitData(it)
                }
            }
        }
    }
    

    二、加载更多

    效果:


    LoadStateAdapter

    PagingDataAdapter支持设置一个LoadStateAdapter,来显示加载更多
    定义Adapter继承于LoadStateAdapter:

    package com.aruba.paging3application.adapter
    
    import android.view.LayoutInflater
    import android.view.ViewGroup
    import androidx.paging.LoadState
    import androidx.paging.LoadStateAdapter
    import com.aruba.paging3application.databinding.MovieLoadmoreBinding
    
    /**
     * Created by aruba on 2021/9/22.
     */
    class LoadMoreAdapter : LoadStateAdapter<BindingViewHolder>() {
        override fun onBindViewHolder(holder: BindingViewHolder, loadState: LoadState) {
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): BindingViewHolder {
            val binding = MovieLoadmoreBinding.inflate(
                LayoutInflater.from(parent.context),
                parent, false
            )
            return BindingViewHolder(binding)
        }
    }
    

    布局为:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <data>
    
        </data>
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:padding="10dp">
    
            <ProgressBar
                android:id="@+id/progressBar"
                android:layout_width="20dp"
                android:layout_height="20dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/tv_loading" />
    
            <TextView
                android:id="@+id/tv_loading"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="正在加载数据... ..."
                android:textSize="18sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent" />
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    </layout>
    

    PagingDataAdapter设置下就可以了

        val adapter = MoviePagingAdapter()
        binding.recyclerView.adapter = adapter.withLoadStateFooter(LoadMoreAdapter())
    

    三、下拉刷新

    效果:


    在布局中为RecyclerView套一层SwipeRefreshLayout后,在Activity中设置刷新监听
            binding.apply {
                recyclerView.adapter = adapter.withLoadStateFooter(LoadMoreAdapter())
                recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
    
                //开始刷新
                refreshLayout.setOnRefreshListener {
                    adapter.refresh()
                }
            }
    

    Adapter的状态进行监听,来更新SwipeRefreshLayout的刷新状态

        //监听adapter状态
        adapter.loadStateFlow.collect {
            //根据刷新状态来通知swiprefreshLayout是否刷新完毕
            binding.refreshLayout.isRefreshing = it.refresh is LoadState.Loading
        }
    

    完整代码:

    package com.aruba.paging3application
    
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import androidx.activity.viewModels
    import androidx.lifecycle.lifecycleScope
    import androidx.paging.LoadState
    import androidx.recyclerview.widget.LinearLayoutManager
    import com.aruba.paging3application.adapter.LoadMoreAdapter
    import com.aruba.paging3application.adapter.MoviePagingAdapter
    import com.aruba.paging3application.databinding.ActivityMainBinding
    import com.aruba.paging3application.viewmodel.MovieViewModel
    import kotlinx.coroutines.flow.collect
    import kotlinx.coroutines.launch
    
    class MainActivity : AppCompatActivity() {
        private val movieViewModel by viewModels<MovieViewModel>()
        private val binding by lazy {
            ActivityMainBinding.inflate(layoutInflater)
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(binding.root)
    
            val adapter = MoviePagingAdapter()
            binding.apply {
                recyclerView.adapter = adapter.withLoadStateFooter(LoadMoreAdapter())
                recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
    
                //开始刷新
                refreshLayout.setOnRefreshListener {
                    adapter.refresh()
                }
            }
    
            lifecycleScope.launchWhenCreated {
                launch {
                    movieViewModel.bindPage().collect {
                        adapter.submitData(it)
                    }   
                }
                
                launch {
                    //监听adapter状态
                    adapter.loadStateFlow.collect {
                        //根据刷新状态来通知swiprefreshLayout是否刷新完毕
                        binding.refreshLayout.isRefreshing = it.refresh is LoadState.Loading
                    }
                }
            }
        }
    }
    

    四、瞬态数据缓存

    目前我们写的代码是不带瞬态数据缓存的:


    想要实现瞬态数据缓存,则要将Flow变为ViewModel的属性,并为它设置作用域
    package com.aruba.paging3application.viewmodel
    
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import androidx.paging.Pager
    import androidx.paging.PagingConfig
    import androidx.paging.cachedIn
    import com.aruba.paging3application.paging.MoviePagingSource
    
    /**
     * Created by aruba on 2021/9/22.
     */
    class MovieViewModel : ViewModel() {
        private val pagerFlow by lazy {
            Pager(config = PagingConfig(
                initialLoadSize = MoviePagingSource.pageSize * 2,//初次加载的数据量大小
                pageSize = MoviePagingSource.pageSize,
                enablePlaceholders = false
            ),
                pagingSourceFactory = {
                    MoviePagingSource()
                }
            ).flow.cachedIn(viewModelScope)
        }
    
        //绑定page的flow,返回是一个flow对象
        fun bindPage() = pagerFlow
    }
    

    效果:


    Demo地址:https://gitee.com/aruba/paging3-application.git

    相关文章

      网友评论

          本文标题:JetPack--Paging3

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