feat: 列表滑动预加载

This commit is contained in:
HuanCheng65 2023-09-22 17:14:22 +08:00
parent 4f3538339e
commit 1e99d0fe21
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
9 changed files with 75 additions and 18 deletions

View File

@ -347,7 +347,9 @@ fun ForumThreadListPage(
)
)
},
loadEnd = !hasMore
loadEnd = !hasMore,
lazyListState = lazyListState,
isEmpty = threadList.isEmpty(),
) {
ThreadList(
state = lazyListState,

View File

@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@ -94,6 +95,7 @@ fun HistoryListPage(
viewModel.onEvent<HistoryListUiEvent.Delete.Success> {
snackbarHostState.showSnackbar(context.getString(R.string.delete_history_success))
}
val lazyListState = rememberLazyListState()
Box(
modifier = Modifier
.fillMaxSize()
@ -101,9 +103,15 @@ fun HistoryListPage(
LoadMoreLayout(
isLoading = isLoadingMore,
onLoadMore = { viewModel.send(HistoryListUiIntent.LoadMore(currentPage + 1)) },
loadEnd = !hasMore
loadEnd = !hasMore,
lazyListState = lazyListState,
isEmpty = todayHistoryData.isEmpty() && beforeHistoryData.isEmpty()
) {
LazyColumn(
modifier = Modifier
.fillMaxSize(),
state = lazyListState
) {
LazyColumn {
if (todayHistoryData.isNotEmpty()) {
stickyHeader(key = "TodayHistoryHeader") {
Column(

View File

@ -78,7 +78,8 @@ fun ConcernPage(
) {
LoadMoreLayout(
isLoading = isLoadingMore,
onLoadMore = { viewModel.send(ConcernUiIntent.LoadMore(nextPageTag)) }
onLoadMore = { viewModel.send(ConcernUiIntent.LoadMore(nextPageTag)) },
lazyListState = lazyListState,
) {
val windowSizeClass = BaseComposeActivity.LocalWindowSizeClass.current
val itemFraction = when (windowSizeClass.widthSizeClass) {

View File

@ -36,6 +36,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
@ -113,7 +114,7 @@ fun PersonalizedPage(
val lazyListState = rememberLazyListState()
viewModel.bindScrollToTopEvent(lazyListState = lazyListState)
var refreshCount by remember {
mutableStateOf(0)
mutableIntStateOf(0)
}
var showRefreshTip by remember {
mutableStateOf(false)
@ -150,8 +151,10 @@ fun PersonalizedPage(
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
LoadMoreLayout(
isLoading = isLoadingMore,
loadEnd = false,
onLoadMore = { viewModel.send(PersonalizedUiIntent.LoadMore(currentPage + 1)) },
loadEnd = false,
lazyListState = lazyListState,
isEmpty = data.isEmpty()
) {
FeedList(
state = lazyListState,

View File

@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Text
@ -80,17 +81,21 @@ fun NotificationsListPage(
)
val pullRefreshState = rememberPullRefreshState(
refreshing = isRefreshing,
onRefresh = { viewModel.send(NotificationsListUiIntent.Refresh) })
onRefresh = { viewModel.send(NotificationsListUiIntent.Refresh) }
)
val lazyListState = rememberLazyListState()
Box(
modifier = Modifier.pullRefresh(pullRefreshState)
) {
LoadMoreLayout(
isLoading = isLoadingMore,
loadEnd = !hasMore,
onLoadMore = { viewModel.send(NotificationsListUiIntent.LoadMore(currentPage + 1)) },
loadEnd = !hasMore,
lazyListState = lazyListState,
) {
LazyColumn(
contentPadding = PaddingValues(vertical = 4.dp)
contentPadding = PaddingValues(vertical = 4.dp),
state = lazyListState,
) {
items(
items = data,

View File

@ -354,7 +354,6 @@ internal fun SubPostsContent(
) { paddingValues ->
LoadMoreLayout(
isLoading = isLoading,
loadEnd = !hasMore,
onLoadMore = {
viewModel.send(
SubPostsUiIntent.LoadMore(
@ -365,7 +364,10 @@ internal fun SubPostsContent(
currentPage + 1,
)
)
}
},
loadEnd = !hasMore,
lazyListState = lazyListState,
isEmpty = post == null && subPosts.isEmpty(),
) {
LazyColumn(
modifier = Modifier.padding(paddingValues),

View File

@ -1114,7 +1114,6 @@ fun ThreadPage(
) {
LoadMoreLayout(
isLoading = isLoadingMore,
loadEnd = !hasMore,
onLoadMore = {
viewModel.send(
ThreadUiIntent.LoadMore(
@ -1128,7 +1127,10 @@ fun ThreadPage(
postIds = data.map { it.post.get { id } }
)
)
}
},
loadEnd = !hasMore,
lazyListState = lazyListState,
isEmpty = firstPost == null && data.isEmpty()
) {
LazyColumn(
state = lazyListState,

View File

@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
@ -157,13 +158,16 @@ fun ThreadStorePage(
onRefresh = { viewModel.send(ThreadStoreUiIntent.Refresh) }
)
val lazyListState = rememberLazyListState()
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
LoadMoreLayout(
isLoading = isLoadingMore,
onLoadMore = { viewModel.send(ThreadStoreUiIntent.LoadMore(currentPage + 1)) },
loadEnd = !hasMore
loadEnd = !hasMore,
lazyListState = lazyListState
) {
LazyColumn {
LazyColumn(state = lazyListState) {
items(
items = data,
key = { it.threadId }

View File

@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.ExperimentalMaterialApi
@ -23,11 +24,13 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
@ -45,6 +48,7 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.loadMoreIndicator
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
import kotlin.math.abs
@ -66,12 +70,15 @@ fun LoadMoreLayout(
willLoad = willLoad
)
},
content: @Composable () -> Unit
lazyListState: LazyListState? = null,
isEmpty: Boolean = lazyListState?.layoutInfo?.totalItemsCount == 0,
preloadCount: Int = 1,
content: @Composable () -> Unit,
) {
val loadDistance = with(LocalDensity.current) { LoadDistance.toPx() }
val curOnLoadMore by rememberUpdatedState(newValue = onLoadMore)
var lastTriggerTime by remember { mutableStateOf(0L) }
var lastTriggerTime by remember { mutableLongStateOf(0L) }
var waitingStateReset by remember { mutableStateOf(false) }
val loadMoreFlow = remember {
MutableSharedFlow<Long>(
@ -95,9 +102,32 @@ fun LoadMoreLayout(
}
val canLoadMore = remember(enableLoadMore, loadEnd) { enableLoadMore && !loadEnd }
val curIsEmpty by rememberUpdatedState(newValue = isEmpty)
val curIsLoading by rememberUpdatedState(newValue = isLoading)
val curCanLoadMore by rememberUpdatedState(newValue = canLoadMore)
// 处理列表滚动到底部时自动加载更多
val curLazyListState by rememberUpdatedState(newValue = lazyListState)
LaunchedEffect(curLazyListState) {
curLazyListState?.let { state ->
snapshotFlow {
val shouldPreload = !curIsEmpty && curCanLoadMore && !curIsLoading
val isInPreloadRange =
state.firstVisibleItemIndex + state.layoutInfo.visibleItemsInfo.size - 1 >= state.layoutInfo.totalItemsCount - preloadCount
shouldPreload && isInPreloadRange
}
.distinctUntilChanged()
.collect {
if (it) {
val curTime = System.currentTimeMillis()
coroutineScope.launch {
loadMoreFlow.emit(curTime)
}
curTime - lastTriggerTime >= 500
}
}
}
}
val swipeableState = rememberSwipeableState(false) { newValue ->
if (newValue && !curIsLoading && curCanLoadMore) {
@ -116,7 +146,7 @@ fun LoadMoreLayout(
}
}
LaunchedEffect(isLoading) { swipeableState.animateTo(isLoading) }
LaunchedEffect(isLoading) { if (!isLoading) swipeableState.animateTo(isLoading) }
Box(
modifier = Modifier