Android Jetpack Kotlin/Java pageing3的基础使用。

Posted 安果移不动

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Jetpack Kotlin/Java pageing3的基础使用。相关的知识,希望对你有一定的参考价值。

Java可以参考

2021年最全面的Jetpack系统学习课程,看他就够了,更新中_哔哩哔哩_bilibili

Kt可以参考

郭林大神的:

Jetpack新成员,Paging3从吐槽到真香_guolin的博客-CSDN博客_paging3

他里面提供了数据接口 我没有提供。

美中不足的地方我又找到了。

他这个地方用的是loadSize

第一个开始都是好好的

但是后面会变为前面的三倍,也就是

分页数据

第一次正常1

第二次正常2 

第三次正常3 

第四次 不正常 1= 第一次的数据

第五次 不正常2  = 第二次的数据 

第六次 不正常2  = 第三次的数据 

第七次 不正常  = 正常第四次的数据

第八次 不正常  = 正常第五次的数据

----以此类推。

这可能和我用的版本比较新有关。但这是需要大家注意的地方 当数据拉下来要自己检查检查对不对。合格不合格 再进行微调

我的粗略了看了下他的数据是上面这样分布的有问题的

我的数据是刚好没有问题的

所以我这个pageSize还是老实点写死就好啦。

但是。也可以参考我这篇哦。

为啥说起paging。好像闻所未闻?

因为他需要更多的jetpack知识才可以进行学习。否则还是难以理解

协程 的挂起 suspend 、Flow的流、MVVM 的viewModel 

下面是正式的kt教程

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'androidx.paging:paging-runtime-ktx:3.1.1'

 请注意 java依赖都不带 -ktx

和kt完全不一样,java的朋友真的需要学习kt才好用。当然如果不想学可以看java的那个教程

 java应该是这样

androidx.paging:paging-runtime:3.1.1

下面关于java不再多讲。主要是kt 

引入依赖后同步工程

这里我就不提供具体的分页请求接口数据了。

所以域名和路径我都打上马赛克。但是绝对是可以正常访问的数据按照这种格式绝对没有问题的哦

然后 编写网络类

package com.anguomob.jecpack.api

import android.util.Log
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

class RetrofitClient private constructor() 
    private val TAG = "RetrofitClient"

    companion object 
        val instance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) 
            RetrofitClient()
        
        val BASE_URL = "https://xxxxx.xxxxx.com/"
    


    private fun getClient(): OkHttpClient 
        return OkHttpClient.Builder().build()

    

    fun getApi(): Api 
        Log.e(TAG, "getApi:$BASE_URL ")


        val builder = RetrofitUrlManager.with(OkHttpClient.Builder())

        // 拦截器

        val client: OkHttpClient = builder
            .addNetworkInterceptor(OkHttpNetworkInterceptor())
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(60, TimeUnit.SECONDS)
            .connectTimeout(60, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .build()

        return Retrofit.Builder()
//            .client(client)
            .baseUrl(BASE_URL)
//            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .build().create(Api::class.java)
    

//            .client(client)

这个可以先不设置。还有 回调我们也不用RxJava了。而是使用协程的suspend

package com.anguomob.jecpack.api

import com.anguomob.jecpack.bean.FeedBack
import com.anguomob.jecpack.bean.FeedBackLists
import com.anguomob.jecpack.bean.FeedBackParent
import com.anguomob.jecpack.bean.Response
import io.reactivex.Observable
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query

interface Api 
    @GET("api/v1/open/app/xxxx/xxxx")
    suspend fun getFeedBack(
        @Query("page") page: Int,
        @Query("size") size: Int
    ): Response<FeedBackLists>

这里大家可以看到。有一个Response<FeedBackLists>

Response 是对最外层数据的封装。比如 后台一般要返回code、data、message 

package com.anguomob.jecpack.bean


import com.google.gson.annotations.SerializedName
import java.io.Serializable

/**
 *
 * 返回结果封装
 */
class Response<T> : Serializable 
    @SerializedName("code")
    var code // 返回的code
            = 0

    @SerializedName("data")
    var data // 具体的数据结果
            : T? = null
        private set

    @SerializedName("message")
    var message // message 可用来返回接口的说明
            : String? = null

    fun setData(data: T) 
        this.data = data
    

其中数据bean为

package com.anguomob.jecpack.bean

import com.google.gson.annotations.SerializedName





data class FeedBackLists(
    //起始位置
    val current_page: Int,
    val `data`: List<FeedBack>,
    val first_page_url: String,
    val from: Int,
    val last_page: Int,
    val last_page_url: String,
    val next_page_url: String,
    val path: String,
    val per_page: Int,
    val prev_page_url: Any,
    val to: Int,
    //一共多少条
    val total: Int,
    //当前页面的数量
    var count: Int = data.size,

)

data class FeedBack(
    val id: Int,
    val content: String,
    val create_time: String,

//    val android_version: String,
//    val app_name: String,
//    val app_version: String,
//    val contact: String,
//    val device_unique_id: String,
//    val model: String,
//    val package_name: String
)

稍微解释一下。

FeedBackLists  里面的 

data  是本次分页的数据

而 

FeedBackLists  包含了
last_page 这个代表 当前size下,总共分页多少页。

将数据挂起后 可以直接将数据返回出来

 我们编写第一个核心类

PagingSource

加载数据

package com.anguomob.jecpack.paging

import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.anguomob.jecpack.api.RetrofitClient
import com.anguomob.jecpack.bean.FeedBack
import com.anguomob.jecpack.bean.FeedBackLists

class FeedBackDataSource : PagingSource<Int, FeedBack>() 
    private val TAG = "FeedBackDataSource"


    override fun getRefreshKey(state: PagingState<Int, FeedBack>): Int? 
        return null
    


    override suspend fun load(params: LoadParams<Int>): PagingSource.LoadResult<Int, FeedBack> 

        return try 
            val page = params.key ?: 1 // set page 1 as default
            val pageSize = 1


            val repoResponse = RetrofitClient
                .instance
                .getApi()
                .getFeedBack(page, pageSize)

            val prevKey = if (page > 1) page - 1 else null
            val nextKey = if (page < (repoResponse.data?.last_page ?: 0)) page + 1 else null
            Log.e(TAG, "load:pageSize:$pageSize")
            Log.e(TAG, "load:prevKey:$prevKey")
            Log.e(TAG, "load:nextKey:$nextKey")
            Log.e(TAG, "load:repoResponse.data?.last_page:$repoResponse.data?.last_page")
            PagingSource.LoadResult.Page(repoResponse.data?.data!!, prevKey, nextKey)

         catch (e: Exception) 
            Log.e(TAG, "load:Error:$e.message")
            PagingSource.LoadResult.Error(e)
        


    



通过

PagingSource.LoadResult.Page

加载数据 并将prePage与nextPage的索引返回出去。还有data列表数据,如若没有数据则返回null

如何使用到DataSourece呢 再次编写一个类

package com.anguomob.jecpack.repository

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.anguomob.jecpack.bean.FeedBack
import com.anguomob.jecpack.paging.FeedBackDataSource
import kotlinx.coroutines.flow.Flow

object FeedBackRepository 

    private const val PAGE_SIZE = 1


    fun getPagingData(): Flow<PagingData<FeedBack>> 
        return Pager(
            config = PagingConfig(PAGE_SIZE),
            pagingSourceFactory =  FeedBackDataSource() 
        ).flow
    


可以配置页数大小。

如何使用到这个仓库数据

FeedBackRepository

viewModel

package com.anguomob.jecpack.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.anguomob.jecpack.bean.FeedBack
import com.anguomob.jecpack.repository.FeedBackRepository
import kotlinx.coroutines.flow.Flow

class FeedBackViewModel : ViewModel() 
    fun getPagingData(): Flow<PagingData<FeedBack>> 
        return FeedBackRepository.getPagingData().cachedIn(viewModelScope)
    

页面布局很简单。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.PagingActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

页面代码也很简单 因为核心代码都在其他的pageSource中

package com.anguomob.jecpack.activity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.anguomob.jecpack.R
import com.anguomob.jecpack.adapter.FeedBackListAdapter
import com.anguomob.jecpack.databinding.ActivityPagingBinding
import com.anguomob.jecpack.databinding.ItemPagingBinding
import com.anguomob.jecpack.viewmodel.FeedBackViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

class PagingActivity : AppCompatActivity() 
    private lateinit var binding: ActivityPagingBinding
    private  val TAG = "PagingActivity"
    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        binding = ActivityPagingBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.rv.layoutManager = LinearLayoutManager(this)
        val feedBackListAdapter = FeedBackListAdapter()
        binding.rv.adapter = feedBackListAdapter

        val viewModel =
            ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application)).get(
                FeedBackViewModel::class.java
            )

        lifecycleScope.launch
            viewModel.getPagingData().collect(  data ->
                feedBackListAdapter.submitData(data)
            )
        


    

但是他却实现了分页加载。

非常的不可思议

但是我们还没有写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">

    <data>

        <variable
            name="feedBack"
            type="com.anguomob.jecpack.bean.FeedBack" />


    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:context=".activity.DatabindingActivity">


        <TextView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="28dp"
            android:text="@String.valueOf(feedBack.id)"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="1" />


        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="36dp"
            android:text="@feedBack.create_time"
            android:textSize="24dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.471"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="姓名" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="20dp"
            android:text="@feedBack.content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="@+id/textView1"

            app:layout_constraintTop_toBottomOf="@+id/textView1"
            tools:text="年龄" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

这里面我用到了databinding数据绑定

你不想用。也没关系的 用正常的rv绑定数据那样就好 这个我就不教你啦。基础

布局代码

package com.anguomob.jecpack.adapter

import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.paging.PagedListAdapter
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.RecyclerView
import com.anguomob.jecpack.R
import com.anguomob.jecpack.bean.FeedBack
import com.anguomob.jecpack.databinding.ItemPagingBinding

//差分更新 只更新需要的元素 而不是全部更新 性能优化的一种方式
class FeedBackListAdapter :
    PagingDataAdapter<FeedBack, FeedBackListAdapter.FeedBackViewHolder>(differCallback) 

    companion object 
        private const val TAG = "FeedBackListAdapter"
        val differCallback: ItemCallback<FeedBack> = object :
            ItemCallback<FeedBack>() 
            override fun areItemsTheSame(oldItem: FeedBack, newItem: FeedBack): Boolean 
                return oldItem == newItem
            

            override fun areContentsTheSame(oldItem: FeedBack, newItem: FeedBack): Boolean 
                return oldItem == newItem
            
        
    

    class FeedBackViewHolder(val binding: ItemPagingBinding) :
        RecyclerView.ViewHolder(binding.root) 

    

    override fun onBindViewHolder(holder: FeedBackListAdapter.FeedBackViewHolder, position: Int) 
        val item = getItem(position);
        Log.e(TAG, "onBindViewHolder: $item", )
        if (item != null) 
            holder.binding.feedBack = item
        
    

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): FeedBackListAdapter.FeedBackViewHolder 

        val binding: ItemPagingBinding = DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.item_paging,
            parent,
            false
        )

        return FeedBackViewHolder(binding)

    

这里你会返现没有getItemCount 相关的方法。因为他们内部帮我们实现了

而且多了一个

differCallback

这个是做什么的

他是为了防止数据重复加载刷新的。

正常情况下我们的分页加载是 加载完毕 data a  然后加载data b 然后调用

notifiyDataChange()

这会有一个小小的瑕疵。他刷新的是全部数据。

但是、这个是

ItemCallback

局部刷新 帮我们仅仅在局部真正变更的部分进行了刷新。我们开发者要是用以往的rv自己处理刷新方式其实是很难做到这点的。要对很多数据做比较。想想都头大。

而现在 实现这个类就好啦 java 建议要对bean重写 hashCode 和 equal方法。kt使用==就好啦。也不用重写方法。

onBindViewHolder 对数据进行设置。
onCreateViewHolder 中对数据视图进行绑定。

好处不仅仅是性能上面。他会加载的很流畅、而且没有那么卡顿的感觉 好像数据都是正常很丝滑一次性没有做分页加载的这样。还有更多的专业配置。
我后续学习了接着往下更新。

刚开始学的时候是想参照java写kt。。后来直接闪退不报错

。。。

再后来看到郭霖大神的博客。才茅塞顿开 

依赖都不一样。能用才怪呢、、、

以上是关于Android Jetpack Kotlin/Java pageing3的基础使用。的主要内容,如果未能解决你的问题,请参考以下文章

Android Jetpack架构组件带你了解Android Jetpack

Android Jetpack简介

Android Jetpack架构组件——什么是Jetpack?

Android Jetpack架构组件——什么是Jetpack?

《Android Jetpack从入门到精通+高级Jetpack强化实战》,最新Jetpack系列开发笔记开源

Android高级Jetpack架构组件+Jetpack compose强化实战