准备好接口

package com.example.android_learn_paging.net

import com.example.android_learn_paging.model.NetDataList
import retrofit2.http.GET
import retrofit2.http.Query

interface FeedBackApi {
@GET("api/v1/open/test")
suspend fun getFeedBack(
@Query("page") page: Int,
@Query("size") size: Int
): NetDataList
}

用laravel8.5 写的 就是对数据库进行分页查询

public function index($size, $search): JsonResponse
{
$res = AppFeedBack::query();
if ($search) {
if (isset($search['app_name'])) {
$res = $res->where('app_name', 'like', "%" . $search['app_name'] . "%");
}
if (isset($search['content'])) {
$res = $res->where('content', 'like', "%" . $search['content'] . "%");
}
}
$data = $res
->orderBy('create_time', 'desc')
->paginate($size)
->toArray();

// return AppFeedBack::simplePaginate(15);

return $this->apiSuccess("", $data);
}

大概给大家看下工程结构

很复杂。

Android Kotlin Paging3 Flow完整教程_加载

 返回的数据bean格式如下

package com.example.android_learn_paging.model

data class FeedBack(
val id: Int, val app_name: String, val package_name: String, val content: String
)

data class FeedBacks(
val current_page: Int, val data: ArrayList<FeedBack>, val last_page: Int

)

data class NetDataList(
val code: Int, val message: String, val data: FeedBacks
)

api接口

package com.example.android_learn_paging.net

import com.example.android_learn_paging.model.NetDataList
import retrofit2.http.GET
import retrofit2.http.Query

interface FeedBackApi {
@GET("api/v1/open/test")
suspend fun getFeedBack(
@Query("page") page: Int,
@Query("size") size: Int
): NetDataList
}

初始化数据类

package com.example.android_flow_practice.net

import android.util.Log
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create

object RetrofitClient {
val url = "https://xxx.xxxx.xxxx";
private const val TAG = "RetrofitClient"

private val instance: Retrofit by lazy {
val interceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger {
Log.w(TAG, "${it}")
})

interceptor.level = HttpLoggingInterceptor.Level.BODY;

Retrofit.Builder().client(
OkHttpClient.Builder().addInterceptor(interceptor).build()
).baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
}


fun <T> createApi(clazz: Class<T>): T {
return instance.create(clazz) as T
}
}

用到了三方依赖

implementation "androidx.activity:activity-ktx:1.5.1"
implementation "androidx.fragment:fragment-ktx:1.5.2"

implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"

implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"

implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:3.4.1"

implementation "androidx.paging:paging-runtime-ktx:3.1.1"
implementation "com.squareup.picasso:picasso:2.71828"

Android Kotlin Paging3 Flow完整教程_ide_02

 

Android Kotlin Paging3 Flow完整教程_加载_03

 这俩勾上 kapt加上

好了 

继续

AppConfigs
package com.example.android_learn_paging.config

object AppConfigs {
const val LOAD_PAGE = 8

//默认就是3倍
const val INITPAGE_SIZE = LOAD_PAGE * 3
}

paging3 的一次加载页数 。这里写死了。为啥要这么写 因为后面就知道了

核心adapter PageSource

FeedBackPagingSource
package com.example.android_learn_paging.paging

import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.example.android_flow_practice.net.RetrofitClient
import com.example.android_learn_paging.config.AppConfigs
import com.example.android_learn_paging.model.FeedBack
import com.example.android_learn_paging.net.FeedBackApi
import kotlinx.coroutines.delay

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

/**
* 1 8
* 2 8
* 3 8
*
*
* null 2
* 1 3
* 2 4
*
*
* 1 24
* 2 8
* 3 8
*
*
* null 4
* 3 5
* 4 6
*/
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, FeedBack> {
val currentPage = params.key ?: 1
val pageSize = params.loadSize
val feedbacks =
RetrofitClient.createApi(FeedBackApi::class.java).getFeedBack(currentPage, pageSize)

Log.d(TAG, "load: currentPage $currentPage ,pageSize $pageSize")


var prevKey: Int? = null
var nextKey: Int? = null
if (currentPage == 1) {
prevKey = null
nextKey = AppConfigs.INITPAGE_SIZE / AppConfigs.LOAD_PAGE + 1
} else {
prevKey = currentPage - 1
nextKey =
if (feedbacks.data.last_page > feedbacks.data.current_page) currentPage + 1 else null
}






return try {
LoadResult.Page(data = feedbacks.data.data, prevKey = prevKey, nextKey = nextKey)
} catch (e: Exception) {
e.printStackTrace()
return LoadResult.Error(e)
}

}

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

}

Android Kotlin Paging3 Flow完整教程_ide_04

 由于第一次加载了三页数 所以再次加载不可以直接从2开始

Android Kotlin Paging3 Flow完整教程_ide_05

 这个注释上面是指的

第一次 加载8个  对应下面  首次加载为null 然后下一页为2

第二次  8个  对应下面  第二次 加载  上一页为 1 然后下一页为3 、

这都是理想情况下 当如果是分页首次加载了 initialLoadSize  为默认3倍的时候要处理就往下面数

就懂了

viewModel记录了我们在如何处理这个数据 并且返回了FLow

package com.example.android_learn_paging.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.example.android_learn_paging.config.AppConfigs
import com.example.android_learn_paging.model.FeedBack
import com.example.android_learn_paging.paging.FeedBackPagingSource
import kotlinx.coroutines.flow.Flow

class FeedBackViewModel : ViewModel() {


private val moives by lazy {
Pager(config = PagingConfig(
pageSize = AppConfigs.LOAD_PAGE,
initialLoadSize = AppConfigs.INITPAGE_SIZE,
prefetchDistance = 1
), pagingSourceFactory = { FeedBackPagingSource() }).flow.cachedIn(viewModelScope)
}

fun loadFeedBack(): Flow<PagingData<FeedBack>> = moives
}

下面就是页面代码了。

先 MainActivity吧

package com.example.android_learn_paging.activity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import com.example.android_learn_paging.adapter.FeedBackAdapter
import com.example.android_learn_paging.adapter.MovieLoadMoreAdapter
import com.example.android_learn_paging.databinding.ActivityMainBinding
import com.example.android_learn_paging.viewmodel.FeedBackViewModel
import kotlinx.coroutines.flow.collectLatest

class MainActivity : AppCompatActivity() {

private val feedBackViewModel: FeedBackViewModel by viewModels()

private val mBinding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mBinding.root)

val feedBackAdapter = FeedBackAdapter(this)

mBinding.apply {
rv.adapter =
feedBackAdapter.withLoadStateFooter(MovieLoadMoreAdapter(this@MainActivity))

swipeRefreshLayout.setOnRefreshListener {
feedBackAdapter.refresh()

}
}

lifecycleScope.launchWhenCreated {
feedBackViewModel.loadFeedBack().collectLatest {
feedBackAdapter.submitData(it)
}
}

lifecycleScope.launchWhenCreated {
feedBackAdapter.loadStateFlow.collectLatest { status ->
mBinding.swipeRefreshLayout.isRefreshing = status.refresh is LoadState.Loading
}
}
}
}

布局

<?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.MainActivity">

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">


<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

看到了很多生代码 没关系 一点一点补充

FeedBackAdapter.kt
package com.example.android_learn_paging.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import com.example.android_learn_paging.databinding.ItemPagingBinding
import com.example.android_learn_paging.model.FeedBack

val diffUtil = object : DiffUtil.ItemCallback<FeedBack>() {
//如果id一样 就认为是同一个元素
override fun areContentsTheSame(oldItem: FeedBack, newItem: FeedBack): Boolean {
return oldItem.id == newItem.id
}

override fun areItemsTheSame(oldItem: FeedBack, newItem: FeedBack): Boolean {
return oldItem == newItem
}
}

class FeedBackAdapter(private val context: Context) :
PagingDataAdapter<FeedBack, BindingViewHolder>(diffUtil) {
override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
val feedBack = getItem(position)
feedBack?.let {
val binding = holder.binding as ItemPagingBinding
binding.feedback = it
binding.packageName = "https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc";
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
// val binding = Pagint
val binding = ItemPagingBinding.inflate(LayoutInflater.from(context), parent, false)
return BindingViewHolder(binding)
}
}
BindingViewHolder
package com.example.android_learn_paging.adapter

import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding

class BindingViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {}
ImageViewBindingAdapter

 对app:image标签来进行注解

package com.example.android_learn_paging.adapter

import android.graphics.Color
import com.example.android_learn_paging.R
import android.text.TextUtils
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.squareup.picasso.Picasso

class ImageViewBindingAdapter {


companion object {
@JvmStatic
@BindingAdapter("image")
fun setImage(imageView: ImageView, url: String) {
if (!TextUtils.isEmpty(url)) {
Picasso.get().load(url).placeholder(R.drawable.ic_launcher_background)
.into(imageView)
} else {
imageView.setBackgroundColor(Color.GRAY)
}
}
}

}

 下拉加载更多组件

MovieLoadMoreAdapter
package com.example.android_learn_paging.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.LoadState
import androidx.paging.LoadStateAdapter
import com.example.android_learn_paging.databinding.FeedbackLoadmoreBinding

class MovieLoadMoreAdapter(private val context: Context) : LoadStateAdapter<BindingViewHolder>() {
override fun onBindViewHolder(holder: BindingViewHolder, loadState: LoadState) {

}

override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): BindingViewHolder {
val binding = FeedbackLoadmoreBinding.inflate(LayoutInflater.from(context), parent, false)
return BindingViewHolder(binding)
}
}

adapter非常简单

feedback_loadmore.xml
<?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"
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"
android:layout_marginStart="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<TextView
android:id="@+id/tv_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="加载更多数据"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/progressBar" />

</androidx.constraintlayout.widget.ConstraintLayout>

 adapter item布局

item_paging.xml
<?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="packageName"
type="String" />

<variable
name="feedback"
type="com.example.android_learn_paging.model.FeedBack" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@{feedback.app_name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/tv_context"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@{feedback.content}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_name" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:image="@{packageName}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_context" />

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

Android Kotlin Paging3 Flow完整教程_ide_06

 系统的adapter指向一个上滑加载更多

下拉刷新用的是

Android Kotlin Paging3 Flow完整教程_加载_07

 

Android Kotlin Paging3 Flow完整教程_android_08

 检测刷新状态 并且刷新代码

是的 


PagingDataAdapter 有自己的刷新代码。。而我是刚知道。。我以前都去刷新


Android Kotlin Paging3 Flow完整教程_android studio_09

 

他 先通过一个成员变量引用出来 然后调用

.invalidate()

进行刷新 也是可行的。。。但是不知道会不会有什么潜在问题。