Jetpack Android Compose 利用Paging3实现分页加载,下拉刷新上拉更多

Paging3分页加载

服务端是网上找的开源接口,来自:https://www.free-api.com/

最终效果
  1. 引入依赖:

      //Paging 3.0
      implementation 'androidx.paging:paging-compose:1.0.0-alpha14'
      implementation "androidx.paging:paging-runtime-ktx:3.1.0-rc01"
    
  2. Paging实现分页加载,简单快捷可定制,内部管理了分页逻辑和异常处理,而分页规则需要自己定义。主要代码:

    
    class ExamSource(private val repository: Repository) : PagingSource<Int, Question>() {
    
        private val TAG = "--ExamSource"
    
        override fun getRefreshKey(state: PagingState<Int, Question>): Int? {
            return null
        }
    
        override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Question> {
    
            return try {
                val currentPage = params.key ?: 1
                val pageSize = params.loadSize
                Log.d(TAG, "currentPage: $currentPage")
                Log.d(TAG, "pageSize: $pageSize")
    
                // 传入当前页码,每页大小,然后请求数据。网络请求封装在repository
                val responseList = repository.getExamList(currentPage, pageSize = pageSize)
                    .result?.resultData?.questionList ?: emptyList<Question>()
    
                // 加载分页
                val everyPageSize = 4
                val initPageSize = 8
                // 前一页
                val preKey = if (currentPage == 1) null else currentPage.minus(1)
                // 下一页
                var nextKey: Int? = if (currentPage == 1) {
                    initPageSize / everyPageSize
                } else {
                    currentPage.plus(1)
                }
                Log.d(TAG, "preKey: $preKey")
                Log.d(TAG, "nextKey: $nextKey")
                if (responseList.isEmpty()) {
                    nextKey = null
                }
                Log.d(TAG, "final nextKey: $nextKey")
    
                LoadResult.Page(
                    data = responseList,
                    prevKey = preKey,
                    nextKey = nextKey
                )
            } catch (e: Exception) {
                e.printStackTrace()
                LoadResult.Error(e)
            }
        }
    }
    

沉浸式状态栏

// 状态栏相关
implementation "com.google.accompanist:accompanist-insets:0.21.2-beta"
implementation "com.google.accompanist:accompanist-insets-ui:0.21.2-beta"
implementation "com.google.accompanist:accompanist-systemuicontroller:0.21.2-beta"
  • WindowCompat.setDecorFitsSystemWindows(window, true)

    /**
    * 设置decorView是否适配windowsetscompat的WindowInsetsCompat根视图。
    * 若置为false,将不适配内容视图的insets,只会适配内容视图。
    * 请注意:在应用程序中使用View.setSystemUiVisibility(int) API可能会与此方法冲突。应停止使用View.setSystemUiVisibility(int)。
    */
    public static void setDecorFitsSystemWindows(@NonNull Window window,
                final boolean decorFitsSystemWindows) {
            if (Build.VERSION.SDK_INT >= 30) {
                Impl30.setDecorFitsSystemWindows(window, decorFitsSystemWindows);
            } else if (Build.VERSION.SDK_INT >= 16) {
                Impl16.setDecorFitsSystemWindows(window, decorFitsSystemWindows);
            }
        }
    
    • 状态栏透明
      拿到SystemUiController的setStatusBarColor()方法来改变状态栏,也可以修改底部导航栏
     setContent {
          UseComposeTheme {
               // 状态栏改为透明,参数:color(状态栏颜色),darkIcons(是否为深色图标)
                rememberSystemUiController().setStatusBarColor(
                      Color.Transparent, darkIcons = MaterialTheme.colors.isLight
                )
    
                // 底部导航栏颜色
                rememberSystemUiController().setNavigationBarColor(
                     Color.Transparent, darkIcons = MaterialTheme.colors.isLight
                )
    
                // ...
          }
     }
    
    • 调整以适配状态栏高度

    需要用到ProvideWindowInsets,在显示内容的外围包一层ProvideWindowInsets,在Theme以下包裹ProvideWindowInsets以便取得状态栏的高度。

     setContent {
          UseComposeTheme {
                     // 加入ProvideWindowInsets
                     ProvideWindowInsets {
                         // 状态栏改为透明
                         rememberSystemUiController().setStatusBarColor(
                             Color.Transparent, darkIcons = MaterialTheme.colors.isLight
                         )
    
                         Surface(color = MaterialTheme.colors.background) {
                             Scaffold(
                                 modifier = Modifier.fillMaxSize()
                             ) {
                                 Column {
                                       // 填充留白状态栏高度
                                      Spacer(modifier = Modifier
                                          .statusBarsHeight()
                                          .fillMaxWidth()
                                      )
    
                                      // 你的业务 Composable
                                 }
                             }
                         }
                     }
                 }
          }
    
    • 效果
    修改前 FitsSystemWindows :false 修改颜色 适配高度
    1
    2
    3
    4

下拉刷新

构建错误:是com.google.accompanist:accompanist:xxx 相关库的版本不兼容,需要依赖相同的版本

   21:35:51.503 7789-7789/com.jesen.driverexampaging E/AndroidRuntime: FATAL EXCEPTION: main
       Process: com.jesen.driverexampaging, PID: 7789
       java.lang.NoSuchMethodError: No interface method startReplaceableGroup(ILjava/lang/String;)V in class Landroidx/compose/runtime/Composer; or its super classes (declaration of 'androidx.compose.runtime.Composer' appears in /data/app/~~4FT0iYbXWuoHva-X3Y0lBg==/com.jesen.driverexampaging-4uB2hZ7cDclbzM5qgmkttA==/base.apk)
           at com.google.accompanist.swiperefresh.SwipeRefreshKt.rememberSwipeRefreshState(Unknown Source:5)
           at com.jesen.driverexampaging.common.SwipeRefreshListKt.SwipeRefreshList(SwipeRefreshList.kt:31)
           at com.jesen.driverexampaging.ui.composeview.RefreshExamListScreenKt.RefreshExamListScreen(RefreshExamListScreen.kt:33)
           at com.jesen.driverexampaging.Main2Activity$onCreate$1$1$1$1$1.invoke(Main2Activity.kt:54)
           at com.jesen.driverexampaging.Main2Activity$onCreate$1$1$1$1$1.invoke(Main2Activity.kt:47)
           at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
           at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
           at androidx.compose.material.ScaffoldKt$ScaffoldLayout$1$1$1$bodyContentPlaceables$1.invoke(Scaffold.kt:316)
           at androidx.compose.material.ScaffoldKt$ScaffoldLayout$1$1$1$bodyContentPlaceables$1.invoke(Scaffold.kt:314)
  1. 效果 :
    <img src="./capture/11-20_225027.gif" alt="下拉刷新效果" style="zoom:50%;" />

  2. 基本用法:

    • 接入依赖:

      // 下拉刷新
          implementation "com.google.accompanist:accompanist-swiperefresh:0.21.2-beta"
      
    • 设置下拉刷新加载更多并判断状态

      @Composable
      fun refreshLoadUse(viewModel: ExamViewModel) {
          // Swipe 的状态
          val refreshState = rememberSwipeRefreshState(isRefreshing = false)
          val collectAsLazyPagingItems = viewModel.examList.collectAsLazyPagingItems()
      
          SwipeRefresh(state = refreshState, onRefresh = {
              collectAsLazyPagingItems.refresh()
          }) {
              LazyColumn(
                  modifier = Modifier
                      .fillMaxWidth()
                      .fillMaxHeight(),
                  content = {
                      itemsIndexed(collectAsLazyPagingItems) { _, refreshData ->//每个item的展示
                          Box(
                              modifier = Modifier
                                  .padding(horizontal = 14.dp, vertical = 4.dp)
                                  .fillMaxWidth()
                                  .height(50.dp)
                                  .background(Color.Green, shape = RoundedCornerShape(8.dp))
                                  .border(
                                      width = 1.dp,
                                      color = Color.Red,
                                      shape = RoundedCornerShape(8.dp)
                                  )
                                  .padding(start = 10.dp),
                              contentAlignment = Alignment.CenterStart
                          ) {
                              Text(text = refreshData?.data ?: "")
                          }
                      }
                      // append 标识非第一页,也就是指下一页或加载更多
                      when (collectAsLazyPagingItems.loadState.append) {
                          is LoadState.Loading -> {
                              //加载中的尾部item展示
                              item {
                                  Box(
                                      modifier = Modifier
                                          .fillMaxWidth()
                                          .height(50.dp),
                                      contentAlignment = Alignment.Center
                                  ) {
                                      Text(text = "加载中。。。")
                                  }
                              }
                          }
                          is LoadState.Error -> {
                              //更多,加载错误展示的尾部item
                              item {
                                  Box(
                                      modifier = Modifier
                                          .fillMaxWidth()
                                          .height(50.dp),
                                      contentAlignment = Alignment.Center
                                  ) {
                                      Text(text = "--加载错误--")
                                  }
                              }
                          }
                      }
                  }
              )
          }
      }
      
  3. 简单封装
    参数1:LazyPagingItems包装的请求结果,可以存储在ViewModel,从ViewMode获取
    参数2:列表内容 listContent 需要外部传入需要携带上下文LazyListScope,可复用

    /**
     * 下拉加载封装
     *
     * implementation "com.google.accompanist:accompanist-swiperefresh:xxx"
     * */
    @Composable
    fun <T : Any> SwipeRefreshList(
        collectAsLazyPagingItems: LazyPagingItems<T>,
        listContent: LazyListScope.() -> Unit,
    ) {
    
        val rememberSwipeRefreshState = rememberSwipeRefreshState(isRefreshing = false)
    
        SwipeRefresh(
            state = rememberSwipeRefreshState,
            onRefresh = { collectAsLazyPagingItems.refresh() }
        ) {
    
            rememberSwipeRefreshState.isRefreshing =
                collectAsLazyPagingItems.loadState.refresh is LoadState.Loading
    
            LazyColumn(
                modifier = Modifier
                    .fillMaxWidth()
                    .fillMaxHeight(),
    
                ) {
                listContent()
                collectAsLazyPagingItems.apply {
                    when {
                        loadState.append is LoadState.Loading -> {
                            //加载更多,底部loading
                            item { LoadingItem() }
                        }
                        loadState.append is LoadState.Error -> {
                            //加载更多异常
                            item {
                                ErrorMoreRetryItem() {
                                    collectAsLazyPagingItems.retry()
                                }
                            }
                        }
                        loadState.refresh is LoadState.Error -> {
                            if (collectAsLazyPagingItems.itemCount <= 0) {
                                //刷新的时候,如果itemCount小于0,第一次加载异常
                                item {
                                    ErrorContent() {
                                        collectAsLazyPagingItems.retry()
                                    }
                                }
                            } else {
                                item {
                                    ErrorMoreRetryItem() {
                                        collectAsLazyPagingItems.retry()
                                    }
                                }
                            }
                        }
                        loadState.refresh is LoadState.Loading -> {
                            // 第一次加载且正在加载中
                            if (collectAsLazyPagingItems.itemCount == 0) {
                            }
                        }
                    }
                }
    
            }
        }
    }
    
    /**
     * 底部加载更多失败处理
     * */
    @Composable
    fun ErrorMoreRetryItem(retry: () -> Unit) {
        Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
            TextButton(
                onClick = { retry() },
                modifier = Modifier
                    .padding(20.dp)
                    .width(80.dp)
                    .height(30.dp),
                shape = RoundedCornerShape(6.dp),
                contentPadding = PaddingValues(3.dp),
                colors = textButtonColors(backgroundColor = gray300),
                elevation = elevation(
                    defaultElevation = 2.dp,
                    pressedElevation = 4.dp,
                ),
            ) {
                Text(text = "请重试", color = gray600)
            }
        }
    }
    
    /**
     * 页面加载失败处理
     * */
    @Composable
    fun ErrorContent(retry: () -> Unit) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(top = 100.dp),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Image(
                modifier = Modifier.padding(top = 80.dp),
                painter = painterResource(id = R.drawable.ic_default_empty),
                contentDescription = null
            )
            Text(text = "请求失败,请检查网络", modifier = Modifier.padding(8.dp))
            TextButton(
                onClick = { retry() },
                modifier = Modifier
                    .padding(20.dp)
                    .width(80.dp)
                    .height(30.dp),
                shape = RoundedCornerShape(10.dp),
                contentPadding = PaddingValues(5.dp),
                colors = textButtonColors(backgroundColor = gray300),
                elevation = elevation(
                    defaultElevation = 2.dp,
                    pressedElevation = 4.dp,
                )
                //colors = ButtonDefaults
            ) { Text(text = "重试", color = gray700) }
        }
    }
    
    /**
     * 底部加载更多正在加载中...
     * */
    @Composable
    fun LoadingItem() {
        Row(
            modifier = Modifier
                .height(34.dp)
                .fillMaxWidth()
                .padding(5.dp),
            horizontalArrangement = Arrangement.Center
        ) {
            CircularProgressIndicator(
                modifier = Modifier
                    .size(24.dp),
                color = gray600,
                strokeWidth = 2.dp
            )
            Text(
                text = "加载中...",
                color = gray600,
                modifier = Modifier
                    .fillMaxHeight()
                    .padding(start = 20.dp),
                fontSize = 18.sp,
            )
        }
    }
    
    • 用法:

      1. 列表布局:

        /**
         * 首页列表加载 ---下拉刷新,加载更多动效
         * */
        @Composable
        fun RefreshExamListScreen(
            viewModel: ExamViewModel,
            context: Context,
        ) {
        
            val collectAsLazyPagingIDataList = viewModel.examList.collectAsLazyPagingItems()
        
            SwipeRefreshList(
                collectAsLazyPagingItems = collectAsLazyPagingIDataList
            ) {
        
                itemsIndexed(collectAsLazyPagingIDataList) { index, data ->
                    // 列表Item
                    QItemView(
                        index = index,
                        que = data,
                        onClick = { Toast.makeText(context, "ccc", Toast.LENGTH_SHORT).show() },
                    )
                }
            }
        }
        
      2. ViewModel,包括Paging3的配置:

      class ExamViewModel : ViewModel() {
      
          val examList = Pager(
              config = PagingConfig(
                  pageSize = 4,    // 每一页个数
                  initialLoadSize = 8, // 第一次加载数量,如果不设置的话是 pageSize * 2
                  prefetchDistance = 2, // 距离下一页请求的距离
              )
          ) {
              // 此类处理了分页功能
              ExamSource(Repository)
          }.flow.cachedIn(viewModelScope)
      }
      

代码:【驾校考题列表】 https://github.com/Jesen0823/UseCompose/tree/main/DriverExamPaging

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容