如何结合分页API使用LazyVerticalGrid并实现滚动加载与连续填充?
To implement infinite scrolling with proper grid layout continuation for odd-length pages, follow these steps:
1. ViewModel for State & Pagination Logic
Create a ViewModel to manage the item list, pagination state, and network requests. This keeps state intact across configuration changes and separates business logic from the UI.
import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.rx2.await class ItemViewModel(private val itemRepository: ItemRepository) : ViewModel() { // Holds the full list of items to render private val _items = MutableStateFlow(emptyList<Item>()) val items: StateFlow<List<Item>> = _items // Tracks if we're currently loading the next page private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow<Boolean> = _isLoading // Tracks if there are more pages to load private val _hasMore = MutableStateFlow(true) val hasMore: StateFlow<Boolean> = _hasMore private var currentPage = 1 fun loadNextPage() { // Prevent duplicate requests or loading when no more pages exist if (_isLoading.value || !_hasMore.value) return _isLoading.value = true viewModelScope.launch { try { val newItems = itemRepository.getItems(currentPage).await() if (newItems.isEmpty()) { // No more items to load _hasMore.value = false } else { // Append new items to the existing list _items.update { it + newItems } currentPage++ } } catch (e: Exception) { // Handle error (e.g., show snackbar, log) } finally { _isLoading.value = false } } } } // Assume this is your repository class interface ItemRepository { fun getItems(pageNumber: Int): Single<List<Item>> }
2. Composable with Infinite Scrolling & Grid Layout
Update the ItemGridView to use the ViewModel, detect scroll position, and render items correctly. The LazyVerticalGrid will automatically fill empty grid spots for odd-length pages when new items are appended.
import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @Composable fun ItemGridView(viewModel: ItemViewModel = viewModel()) { val items by viewModel.items.collectAsState() val isLoading by viewModel.isLoading.collectAsState() val hasMore by viewModel.hasMore.collectAsState() val listState = rememberLazyGridState() val coroutineScope = rememberCoroutineScope() // Detect when user scrolls to the last item to trigger next page load LaunchedEffect(listState) { snapshotFlow { val lastVisibleIndex = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index lastVisibleIndex == items.size - 1 }.collectLatest { isAtLastItem -> if (isAtLastItem) { coroutineScope.launch { viewModel.loadNextPage() } } } } LazyVerticalGrid( columns = GridCells.Fixed(2), modifier = Modifier.fillMaxSize(), state = listState, contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { // Render all items items(items) { item -> item.toPrettyComposableView() } // Show loading indicator when loading next page if (isLoading) { item(span = { GridItemSpan(2) }) { CircularProgressIndicator( modifier = Modifier .fillMaxWidth() .padding(16.dp) .align(Alignment.Center) ) } } // Show end message when no more items exist if (!hasMore && items.isNotEmpty()) { item(span = { GridItemSpan(2) }) { Text( text = "No more items available", modifier = Modifier .fillMaxWidth() .padding(16.dp), textAlign = TextAlign.Center ) } } } }
Key Details Explained
Grid Layout Continuation: By appending new items to the existing list, LazyVerticalGrid automatically fills empty grid spots (from odd-length pages) before starting new rows. For example, if the first page has 3 items, the 4th item (from the next page) will occupy the empty second column in the second row.
Scroll Detection: Uses
snapshotFlowto observe the LazyGridState's visible items. When the last item becomes visible, it triggersloadNextPage().Loading State Management: The
isLoadingflag prevents duplicate network requests, and the loading indicator spans both grid columns for a clean UI.End of Pagination: The
hasMoreflag stops further requests when no more items are returned from the API, and shows a message to the user.
Dependencies
To use await() with RxJava Single, add this coroutines dependency to your module-level build.gradle:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.7.3"
Content of the question来源于stack exchange,提问作者gongie

