• Android Kotlin Paging3 Flow完整教程


    准备好接口

    1. package com.example.android_learn_paging.net
    2. import com.example.android_learn_paging.model.NetDataList
    3. import retrofit2.http.GET
    4. import retrofit2.http.Query
    5. interface FeedBackApi {
    6. @GET("api/v1/open/test")
    7. suspend fun getFeedBack(
    8. @Query("page") page: Int,
    9. @Query("size") size: Int
    10. ): NetDataList
    11. }

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

    1. public function index($size, $search): JsonResponse
    2. {
    3. $res = AppFeedBack::query();
    4. if ($search) {
    5. if (isset($search['app_name'])) {
    6. $res = $res->where('app_name', 'like', "%" . $search['app_name'] . "%");
    7. }
    8. if (isset($search['content'])) {
    9. $res = $res->where('content', 'like', "%" . $search['content'] . "%");
    10. }
    11. }
    12. $data = $res
    13. ->orderBy('create_time', 'desc')
    14. ->paginate($size)
    15. ->toArray();
    16. // return AppFeedBack::simplePaginate(15);
    17. return $this->apiSuccess("", $data);
    18. }

    大概给大家看下工程结构

    很复杂。

     返回的数据bean格式如下

    1. package com.example.android_learn_paging.model
    2. data class FeedBack(
    3. val id: Int, val app_name: String, val package_name: String, val content: String
    4. )
    5. data class FeedBacks(
    6. val current_page: Int, val data: ArrayList, val last_page: Int
    7. )
    8. data class NetDataList(
    9. val code: Int, val message: String, val data: FeedBacks
    10. )

    api接口

    1. package com.example.android_learn_paging.net
    2. import com.example.android_learn_paging.model.NetDataList
    3. import retrofit2.http.GET
    4. import retrofit2.http.Query
    5. interface FeedBackApi {
    6. @GET("api/v1/open/test")
    7. suspend fun getFeedBack(
    8. @Query("page") page: Int,
    9. @Query("size") size: Int
    10. ): NetDataList
    11. }

    初始化数据类

    1. package com.example.android_flow_practice.net
    2. import android.util.Log
    3. import okhttp3.OkHttpClient
    4. import okhttp3.logging.HttpLoggingInterceptor
    5. import retrofit2.Retrofit
    6. import retrofit2.converter.gson.GsonConverterFactory
    7. import retrofit2.create
    8. object RetrofitClient {
    9. val url = "https://xxx.xxxx.xxxx";
    10. private const val TAG = "RetrofitClient"
    11. private val instance: Retrofit by lazy {
    12. val interceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger {
    13. Log.w(TAG, "${it}")
    14. })
    15. interceptor.level = HttpLoggingInterceptor.Level.BODY;
    16. Retrofit.Builder().client(
    17. OkHttpClient.Builder().addInterceptor(interceptor).build()
    18. ).baseUrl(url)
    19. .addConverterFactory(GsonConverterFactory.create())
    20. .build()
    21. }
    22. fun createApi(clazz: Class<T>): T {
    23. return instance.create(clazz) as T
    24. }
    25. }

    用到了三方依赖

    1. implementation "androidx.activity:activity-ktx:1.5.1"
    2. implementation "androidx.fragment:fragment-ktx:1.5.2"
    3. implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
    4. implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
    5. implementation "com.squareup.retrofit2:retrofit:2.9.0"
    6. implementation "com.squareup.retrofit2:converter-gson:2.9.0"
    7. implementation "com.squareup.okhttp3:logging-interceptor:3.4.1"
    8. implementation "androidx.paging:paging-runtime-ktx:3.1.1"
    9. implementation "com.squareup.picasso:picasso:2.71828"

     

     这俩勾上 kapt加上

    好了 

    继续

    AppConfigs
    1. package com.example.android_learn_paging.config
    2. object AppConfigs {
    3. const val LOAD_PAGE = 8
    4. //默认就是3
    5. const val INITPAGE_SIZE = LOAD_PAGE * 3
    6. }

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

    核心adapter PageSource

    FeedBackPagingSource
    1. package com.example.android_learn_paging.paging
    2. import android.util.Log
    3. import androidx.paging.PagingSource
    4. import androidx.paging.PagingState
    5. import com.example.android_flow_practice.net.RetrofitClient
    6. import com.example.android_learn_paging.config.AppConfigs
    7. import com.example.android_learn_paging.model.FeedBack
    8. import com.example.android_learn_paging.net.FeedBackApi
    9. import kotlinx.coroutines.delay
    10. class FeedBackPagingSource : PagingSource<Int, FeedBack>() {
    11. private val TAG = "FeedBackPagingSource"
    12. /**
    13. * 1 8
    14. * 2 8
    15. * 3 8
    16. *
    17. *
    18. * null 2
    19. * 1 3
    20. * 2 4
    21. *
    22. *
    23. * 1 24
    24. * 2 8
    25. * 3 8
    26. *
    27. *
    28. * null 4
    29. * 3 5
    30. * 4 6
    31. */
    32. override suspend fun load(params: LoadParams<Int>): LoadResult<Int, FeedBack> {
    33. val currentPage = params.key ?: 1
    34. val pageSize = params.loadSize
    35. val feedbacks =
    36. RetrofitClient.createApi(FeedBackApi::class.java).getFeedBack(currentPage, pageSize)
    37. Log.d(TAG, "load: currentPage $currentPage ,pageSize $pageSize")
    38. var prevKey: Int? = null
    39. var nextKey: Int? = null
    40. if (currentPage == 1) {
    41. prevKey = null
    42. nextKey = AppConfigs.INITPAGE_SIZE / AppConfigs.LOAD_PAGE + 1
    43. } else {
    44. prevKey = currentPage - 1
    45. nextKey =
    46. if (feedbacks.data.last_page > feedbacks.data.current_page) currentPage + 1 else null
    47. }
    48. return try {
    49. LoadResult.Page(data = feedbacks.data.data, prevKey = prevKey, nextKey = nextKey)
    50. } catch (e: Exception) {
    51. e.printStackTrace()
    52. return LoadResult.Error(e)
    53. }
    54. }
    55. override fun getRefreshKey(state: PagingState<Int, FeedBack>): Int? {
    56. return null
    57. }
    58. }

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

     这个注释上面是指的

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

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

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

    就懂了

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

    1. package com.example.android_learn_paging.viewmodel
    2. import androidx.lifecycle.ViewModel
    3. import androidx.lifecycle.viewModelScope
    4. import androidx.paging.Pager
    5. import androidx.paging.PagingConfig
    6. import androidx.paging.PagingData
    7. import androidx.paging.cachedIn
    8. import com.example.android_learn_paging.config.AppConfigs
    9. import com.example.android_learn_paging.model.FeedBack
    10. import com.example.android_learn_paging.paging.FeedBackPagingSource
    11. import kotlinx.coroutines.flow.Flow
    12. class FeedBackViewModel : ViewModel() {
    13. private val moives by lazy {
    14. Pager(config = PagingConfig(
    15. pageSize = AppConfigs.LOAD_PAGE,
    16. initialLoadSize = AppConfigs.INITPAGE_SIZE,
    17. prefetchDistance = 1
    18. ), pagingSourceFactory = { FeedBackPagingSource() }).flow.cachedIn(viewModelScope)
    19. }
    20. fun loadFeedBack(): Flow<PagingData<FeedBack>> = moives
    21. }

    下面就是页面代码了。

    先 MainActivity吧

    1. package com.example.android_learn_paging.activity
    2. import androidx.appcompat.app.AppCompatActivity
    3. import android.os.Bundle
    4. import androidx.activity.viewModels
    5. import androidx.lifecycle.lifecycleScope
    6. import androidx.paging.LoadState
    7. import com.example.android_learn_paging.adapter.FeedBackAdapter
    8. import com.example.android_learn_paging.adapter.MovieLoadMoreAdapter
    9. import com.example.android_learn_paging.databinding.ActivityMainBinding
    10. import com.example.android_learn_paging.viewmodel.FeedBackViewModel
    11. import kotlinx.coroutines.flow.collectLatest
    12. class MainActivity : AppCompatActivity() {
    13. private val feedBackViewModel: FeedBackViewModel by viewModels()
    14. private val mBinding: ActivityMainBinding by lazy {
    15. ActivityMainBinding.inflate(layoutInflater)
    16. }
    17. override fun onCreate(savedInstanceState: Bundle?) {
    18. super.onCreate(savedInstanceState)
    19. setContentView(mBinding.root)
    20. val feedBackAdapter = FeedBackAdapter(this)
    21. mBinding.apply {
    22. rv.adapter =
    23. feedBackAdapter.withLoadStateFooter(MovieLoadMoreAdapter(this@MainActivity))
    24. swipeRefreshLayout.setOnRefreshListener {
    25. feedBackAdapter.refresh()
    26. }
    27. }
    28. lifecycleScope.launchWhenCreated {
    29. feedBackViewModel.loadFeedBack().collectLatest {
    30. feedBackAdapter.submitData(it)
    31. }
    32. }
    33. lifecycleScope.launchWhenCreated {
    34. feedBackAdapter.loadStateFlow.collectLatest { status ->
    35. mBinding.swipeRefreshLayout.isRefreshing = status.refresh is LoadState.Loading
    36. }
    37. }
    38. }
    39. }

    布局

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. xmlns:app="http://schemas.android.com/apk/res-auto"
    4. xmlns:tools="http://schemas.android.com/tools"
    5. android:layout_width="match_parent"
    6. android:layout_height="match_parent"
    7. tools:context=".activity.MainActivity">
    8. <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    9. android:id="@+id/swipeRefreshLayout"
    10. android:layout_width="match_parent"
    11. android:layout_height="match_parent">
    12. <androidx.recyclerview.widget.RecyclerView
    13. android:id="@+id/rv"
    14. android:layout_width="match_parent"
    15. android:layout_height="wrap_content"
    16. android:orientation="vertical"
    17. app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
    18. </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
    19. </androidx.constraintlayout.widget.ConstraintLayout>

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

    FeedBackAdapter.kt
    1. package com.example.android_learn_paging.adapter
    2. import android.content.Context
    3. import android.view.LayoutInflater
    4. import android.view.ViewGroup
    5. import androidx.paging.PagingDataAdapter
    6. import androidx.recyclerview.widget.DiffUtil
    7. import com.example.android_learn_paging.databinding.ItemPagingBinding
    8. import com.example.android_learn_paging.model.FeedBack
    9. val diffUtil = object : DiffUtil.ItemCallback() {
    10. //如果id一样 就认为是同一个元素
    11. override fun areContentsTheSame(oldItem: FeedBack, newItem: FeedBack): Boolean {
    12. return oldItem.id == newItem.id
    13. }
    14. override fun areItemsTheSame(oldItem: FeedBack, newItem: FeedBack): Boolean {
    15. return oldItem == newItem
    16. }
    17. }
    18. class FeedBackAdapter(private val context: Context) :
    19. PagingDataAdapter(diffUtil) {
    20. override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
    21. val feedBack = getItem(position)
    22. feedBack?.let {
    23. val binding = holder.binding as ItemPagingBinding
    24. binding.feedback = it
    25. 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";
    26. }
    27. }
    28. override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
    29. // val binding = Pagint
    30. val binding = ItemPagingBinding.inflate(LayoutInflater.from(context), parent, false)
    31. return BindingViewHolder(binding)
    32. }
    33. }
    BindingViewHolder
    1. package com.example.android_learn_paging.adapter
    2. import androidx.recyclerview.widget.RecyclerView
    3. import androidx.viewbinding.ViewBinding
    4. class BindingViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {}
    ImageViewBindingAdapter

     对app:image标签来进行注解

    1. package com.example.android_learn_paging.adapter
    2. import android.graphics.Color
    3. import com.example.android_learn_paging.R
    4. import android.text.TextUtils
    5. import android.widget.ImageView
    6. import androidx.databinding.BindingAdapter
    7. import com.squareup.picasso.Picasso
    8. class ImageViewBindingAdapter {
    9. companion object {
    10. @JvmStatic
    11. @BindingAdapter("image")
    12. fun setImage(imageView: ImageView, url: String) {
    13. if (!TextUtils.isEmpty(url)) {
    14. Picasso.get().load(url).placeholder(R.drawable.ic_launcher_background)
    15. .into(imageView)
    16. } else {
    17. imageView.setBackgroundColor(Color.GRAY)
    18. }
    19. }
    20. }
    21. }

     下拉加载更多组件

    MovieLoadMoreAdapter
    1. package com.example.android_learn_paging.adapter
    2. import android.content.Context
    3. import android.view.LayoutInflater
    4. import android.view.ViewGroup
    5. import androidx.paging.LoadState
    6. import androidx.paging.LoadStateAdapter
    7. import com.example.android_learn_paging.databinding.FeedbackLoadmoreBinding
    8. class MovieLoadMoreAdapter(private val context: Context) : LoadStateAdapter() {
    9. override fun onBindViewHolder(holder: BindingViewHolder, loadState: LoadState) {
    10. }
    11. override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): BindingViewHolder {
    12. val binding = FeedbackLoadmoreBinding.inflate(LayoutInflater.from(context), parent, false)
    13. return BindingViewHolder(binding)
    14. }
    15. }

    adapter非常简单

    feedback_loadmore.xml
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. xmlns:app="http://schemas.android.com/apk/res-auto"
    4. android:layout_width="match_parent"
    5. android:layout_height="50dp"
    6. android:padding="10dp">
    7. <ProgressBar
    8. android:id="@+id/progressBar"
    9. android:layout_width="20dp"
    10. android:layout_height="20dp"
    11. android:layout_marginStart="20dp"
    12. app:layout_constraintBottom_toBottomOf="parent"
    13. app:layout_constraintStart_toStartOf="parent" />
    14. <TextView
    15. android:id="@+id/tv_loading"
    16. android:layout_width="wrap_content"
    17. android:layout_height="wrap_content"
    18. android:text="加载更多数据"
    19. app:layout_constraintBottom_toBottomOf="parent"
    20. app:layout_constraintEnd_toEndOf="parent"
    21. app:layout_constraintStart_toStartOf="@id/progressBar" />
    22. </androidx.constraintlayout.widget.ConstraintLayout>

     adapter item布局

    item_paging.xml
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <layout xmlns:android="http://schemas.android.com/apk/res/android"
    3. xmlns:app="http://schemas.android.com/apk/res-auto"
    4. xmlns:tools="http://schemas.android.com/tools">
    5. <data>
    6. <variable
    7. name="packageName"
    8. type="String" />
    9. <variable
    10. name="feedback"
    11. type="com.example.android_learn_paging.model.FeedBack" />
    12. </data>
    13. <androidx.constraintlayout.widget.ConstraintLayout
    14. android:layout_width="match_parent"
    15. android:layout_height="wrap_content">
    16. <TextView
    17. android:id="@+id/tv_name"
    18. android:layout_width="wrap_content"
    19. android:layout_height="wrap_content"
    20. android:layout_marginTop="20dp"
    21. android:text="@{feedback.app_name}"
    22. app:layout_constraintEnd_toEndOf="parent"
    23. app:layout_constraintStart_toStartOf="parent"
    24. app:layout_constraintTop_toTopOf="parent" />
    25. <TextView
    26. android:id="@+id/tv_context"
    27. android:layout_width="wrap_content"
    28. android:layout_height="wrap_content"
    29. android:layout_marginTop="10dp"
    30. android:text="@{feedback.content}"
    31. app:layout_constraintEnd_toEndOf="parent"
    32. app:layout_constraintStart_toStartOf="parent"
    33. app:layout_constraintTop_toBottomOf="@+id/tv_name" />
    34. <ImageView
    35. android:layout_width="wrap_content"
    36. android:layout_height="wrap_content"
    37. android:layout_marginTop="10dp"
    38. app:image="@{packageName}"
    39. app:layout_constraintEnd_toEndOf="parent"
    40. app:layout_constraintStart_toStartOf="parent"
    41. app:layout_constraintTop_toBottomOf="@+id/tv_context" />
    42. </androidx.constraintlayout.widget.ConstraintLayout>
    43. </layout>

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

    下拉刷新用的是

     

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

    是的 

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

     

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

    .invalidate()

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

  • 相关阅读:
    巧用CSS3之雨伞
    使用 pair 做 unordered_map 的键值
    前端分支规范
    剑指offer(C++)-JZ29:顺时针打印矩阵(算法-模拟)
    flex布局属性概括
    LeetCode:2304. 网格中的最小路径代价(C++)
    区块链技术在金融领域的应用场景
    前端开发:Vue混入(mixin)的使用
    腾讯笔试题总结2
    制造企业如何开展客户满意度调查?民安智库为你解答
  • 原文地址:https://blog.csdn.net/mp624183768/article/details/126898642