Paging3分頁加載
服務端是網(wǎng)上找的開源接口,來自:https://www.free-api.com/

最終效果
-
引入依賴:
//Paging 3.0 implementation 'androidx.paging:paging-compose:1.0.0-alpha14' implementation "androidx.paging:paging-runtime-ktx:3.1.0-rc01" -
Paging實現(xiàn)分頁加載,簡單快捷可定制,內(nèi)部管理了分頁邏輯和異常處理,而分頁規(guī)則需要自己定義。主要代碼:
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") // 傳入當前頁碼,每頁大小,然后請求數(shù)據(jù)。網(wǎng)絡請求封裝在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) } } }
沉浸式狀態(tài)欄
// 狀態(tài)欄相關
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,將不適配內(nèi)容視圖的insets,只會適配內(nèi)容視圖。 * 請注意:在應用程序中使用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); } }- 狀態(tài)欄透明
拿到SystemUiController的setStatusBarColor()方法來改變狀態(tài)欄,也可以修改底部導航欄
setContent { UseComposeTheme { // 狀態(tài)欄改為透明,參數(shù):color(狀態(tài)欄顏色),darkIcons(是否為深色圖標) rememberSystemUiController().setStatusBarColor( Color.Transparent, darkIcons = MaterialTheme.colors.isLight ) // 底部導航欄顏色 rememberSystemUiController().setNavigationBarColor( Color.Transparent, darkIcons = MaterialTheme.colors.isLight ) // ... } }- 調(diào)整以適配狀態(tài)欄高度
需要用到ProvideWindowInsets,在顯示內(nèi)容的外圍包一層ProvideWindowInsets,在Theme以下包裹ProvideWindowInsets以便取得狀態(tài)欄的高度。
setContent { UseComposeTheme { // 加入ProvideWindowInsets ProvideWindowInsets { // 狀態(tài)欄改為透明 rememberSystemUiController().setStatusBarColor( Color.Transparent, darkIcons = MaterialTheme.colors.isLight ) Surface(color = MaterialTheme.colors.background) { Scaffold( modifier = Modifier.fillMaxSize() ) { Column { // 填充留白狀態(tài)欄高度 Spacer(modifier = Modifier .statusBarsHeight() .fillMaxWidth() ) // 你的業(yè)務 Composable } } } } } }- 效果
修改前 FitsSystemWindows :false 修改顏色 適配高度 1234 - 狀態(tài)欄透明
下拉刷新
構建錯誤:是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)
效果 :
<img src="./capture/11-20_225027.gif" alt="下拉刷新效果" style="zoom:50%;" />-
基本用法:
-
接入依賴:
// 下拉刷新 implementation "com.google.accompanist:accompanist-swiperefresh:0.21.2-beta" -
設置下拉刷新加載更多并判斷狀態(tài)
@Composable fun refreshLoadUse(viewModel: ExamViewModel) { // Swipe 的狀態(tài) 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 = "--加載錯誤--") } } } } } ) } }
-
-
簡單封裝
參數(shù)1:LazyPagingItems包裝的請求結果,可以存儲在ViewModel,從ViewMode獲取
參數(shù)2:列表內(nèi)容 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 = "請求失敗,請檢查網(wǎng)絡", 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, ) } }-
用法:
-
列表布局:
/** * 首頁列表加載 ---下拉刷新,加載更多動效 * */ @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() }, ) } } } ViewModel,包括Paging3的配置:
class ExamViewModel : ViewModel() { val examList = Pager( config = PagingConfig( pageSize = 4, // 每一頁個數(shù) initialLoadSize = 8, // 第一次加載數(shù)量,如果不設置的話是 pageSize * 2 prefetchDistance = 2, // 距離下一頁請求的距離 ) ) { // 此類處理了分頁功能 ExamSource(Repository) }.flow.cachedIn(viewModelScope) } -
-
代碼:【駕??碱}列表】 https://github.com/Jesen0823/UseCompose/tree/main/DriverExamPaging



