fix: 修正嵌套分页惯性滑动

This commit is contained in:
HapeLee
2026-06-21 01:20:19 +08:00
parent f8f93b8be8
commit bea1095b0a
6 changed files with 88 additions and 41 deletions
@@ -7,6 +7,7 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -40,7 +41,6 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringArrayResource
@@ -59,7 +59,7 @@ import io.legado.app.ui.widget.components.FontFolderState
import io.legado.app.ui.widget.components.FontSelectSheet import io.legado.app.ui.widget.components.FontSelectSheet
import io.legado.app.ui.widget.components.SectionTitle import io.legado.app.ui.widget.components.SectionTitle
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
import io.legado.app.ui.widget.components.pager.rememberConsumeHorizontalPagerNestedScrollConnection import io.legado.app.ui.widget.components.pager.rememberPagerFlingPassThroughConnection
import io.legado.app.ui.widget.components.settingItem.TinyClickableSettingItem import io.legado.app.ui.widget.components.settingItem.TinyClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.TinyColorSettingItem import io.legado.app.ui.widget.components.settingItem.TinyColorSettingItem
import io.legado.app.ui.widget.components.settingItem.TinyDropdownSettingItem import io.legado.app.ui.widget.components.settingItem.TinyDropdownSettingItem
@@ -91,7 +91,10 @@ internal fun HeaderFooterPage(
val pagerState = rememberPagerState(pageCount = { 3 }) val pagerState = rememberPagerState(pageCount = { 3 })
var selectedTab by remember { mutableIntStateOf(0) } var selectedTab by remember { mutableIntStateOf(0) }
var clickScrollCount by remember { mutableIntStateOf(0) } var clickScrollCount by remember { mutableIntStateOf(0) }
val childPagerNestedScrollConnection = rememberConsumeHorizontalPagerNestedScrollConnection() val childPagerNestedScrollConnection = rememberPagerFlingPassThroughConnection(
state = pagerState,
orientation = Orientation.Horizontal,
)
val pageHeights = remember { mutableStateMapOf<Int, Int>() } val pageHeights = remember { mutableStateMapOf<Int, Int>() }
val animatedHeight by rememberPagerAnimatedHeight(pagerState, pageHeights) val animatedHeight by rememberPagerAnimatedHeight(pagerState, pageHeights)
@@ -221,10 +224,10 @@ internal fun HeaderFooterPage(
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
pageNestedScrollConnection = childPagerNestedScrollConnection,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clipToBounds() .clipToBounds()
.nestedScroll(childPagerNestedScrollConnection)
.pagerHeight(animatedHeight), .pagerHeight(animatedHeight),
) { page -> ) { page ->
Box( Box(
@@ -2,6 +2,7 @@ package io.legado.app.ui.book.read.sheet
import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -21,7 +22,6 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
@@ -33,7 +33,7 @@ import io.legado.app.ui.book.read.ConfigUpdate
import io.legado.app.ui.book.read.ReadBookButtonConfigItem import io.legado.app.ui.book.read.ReadBookButtonConfigItem
import io.legado.app.ui.book.read.ReadBookIntent import io.legado.app.ui.book.read.ReadBookIntent
import io.legado.app.ui.book.read.ReadBookStyleConfig import io.legado.app.ui.book.read.ReadBookStyleConfig
import io.legado.app.ui.widget.components.pager.rememberConsumeHorizontalPagerNestedScrollConnection import io.legado.app.ui.widget.components.pager.rememberPagerFlingPassThroughConnection
import io.legado.app.ui.widget.components.tabRow.CardTabRow import io.legado.app.ui.widget.components.tabRow.CardTabRow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -56,7 +56,10 @@ fun ReadStyleContent(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(pageCount = { 2 }) val pagerState = rememberPagerState(pageCount = { 2 })
var currentPage by remember { mutableIntStateOf(0) } var currentPage by remember { mutableIntStateOf(0) }
val childPagerNestedScrollConnection = rememberConsumeHorizontalPagerNestedScrollConnection() val childPagerNestedScrollConnection = rememberPagerFlingPassThroughConnection(
state = pagerState,
orientation = Orientation.Horizontal,
)
val pageHeights = remember { mutableStateMapOf<Int, Int>() } val pageHeights = remember { mutableStateMapOf<Int, Int>() }
val animatedHeight by rememberPagerAnimatedHeight(pagerState, pageHeights) val animatedHeight by rememberPagerAnimatedHeight(pagerState, pageHeights)
@@ -75,10 +78,10 @@ fun ReadStyleContent(
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
pageNestedScrollConnection = childPagerNestedScrollConnection,
modifier = Modifier modifier = Modifier
.weight(1f, fill = false) .weight(1f, fill = false)
.clipToBounds() .clipToBounds()
.nestedScroll(childPagerNestedScrollConnection)
.pagerHeight(animatedHeight), .pagerHeight(animatedHeight),
) { page -> ) { page ->
Box( Box(
@@ -4,6 +4,7 @@ import android.content.Context
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -46,7 +47,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
@@ -69,7 +69,7 @@ import io.legado.app.ui.book.read.ReadBookButtonConfigItem
import io.legado.app.ui.book.read.ReadBookIntent import io.legado.app.ui.book.read.ReadBookIntent
import io.legado.app.ui.book.read.ReadBookSheet import io.legado.app.ui.book.read.ReadBookSheet
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.pager.rememberConsumeHorizontalPagerNestedScrollConnection import io.legado.app.ui.widget.components.pager.rememberPagerFlingPassThroughConnection
import io.legado.app.ui.widget.components.SectionTitle import io.legado.app.ui.widget.components.SectionTitle
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
import io.legado.app.ui.widget.components.settingItem.TinyClearColorModeSettingItem import io.legado.app.ui.widget.components.settingItem.TinyClearColorModeSettingItem
@@ -111,7 +111,10 @@ internal fun SystemMenuPage(
val pagerState = rememberPagerState(pageCount = { 3 }) val pagerState = rememberPagerState(pageCount = { 3 })
var selectedTab by remember { mutableIntStateOf(0) } var selectedTab by remember { mutableIntStateOf(0) }
var clickScrollCount by remember { mutableIntStateOf(0) } var clickScrollCount by remember { mutableIntStateOf(0) }
val childPagerNestedScrollConnection = rememberConsumeHorizontalPagerNestedScrollConnection() val childPagerNestedScrollConnection = rememberPagerFlingPassThroughConnection(
state = pagerState,
orientation = Orientation.Horizontal,
)
val pageHeights = remember { mutableStateMapOf<Int, Int>() } val pageHeights = remember { mutableStateMapOf<Int, Int>() }
val animatedHeight by rememberPagerAnimatedHeight(pagerState, pageHeights) val animatedHeight by rememberPagerAnimatedHeight(pagerState, pageHeights)
@@ -161,9 +164,9 @@ internal fun SystemMenuPage(
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
pageNestedScrollConnection = childPagerNestedScrollConnection,
modifier = Modifier modifier = Modifier
.weight(1f, fill = false) .weight(1f, fill = false)
.nestedScroll(childPagerNestedScrollConnection)
.clipToBounds() .clipToBounds()
.pagerHeight(animatedHeight), .pagerHeight(animatedHeight),
) { page -> ) { page ->
@@ -2,6 +2,7 @@ package io.legado.app.ui.book.read.sheet
import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -35,7 +36,6 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -49,7 +49,7 @@ import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.SectionTitle import io.legado.app.ui.widget.components.SectionTitle
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
import io.legado.app.ui.widget.components.pager.rememberConsumeHorizontalPagerNestedScrollConnection import io.legado.app.ui.widget.components.pager.rememberPagerFlingPassThroughConnection
import io.legado.app.ui.widget.components.settingItem.TinyClickableSettingItem import io.legado.app.ui.widget.components.settingItem.TinyClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.TinyColorSettingItem import io.legado.app.ui.widget.components.settingItem.TinyColorSettingItem
import io.legado.app.ui.widget.components.settingItem.TinyDropdownSettingItem import io.legado.app.ui.widget.components.settingItem.TinyDropdownSettingItem
@@ -135,7 +135,10 @@ internal fun ReadStyleTextTitleContent(
) { ) {
val pageHeights = remember { mutableStateMapOf<Int, Int>() } val pageHeights = remember { mutableStateMapOf<Int, Int>() }
val animatedHeight by rememberPagerAnimatedHeight(pagerState, pageHeights) val animatedHeight by rememberPagerAnimatedHeight(pagerState, pageHeights)
val childPagerNestedScrollConnection = rememberConsumeHorizontalPagerNestedScrollConnection() val childPagerNestedScrollConnection = rememberPagerFlingPassThroughConnection(
state = pagerState,
orientation = Orientation.Horizontal,
)
Column( Column(
modifier = modifier modifier = modifier
@@ -152,10 +155,10 @@ internal fun ReadStyleTextTitleContent(
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
pageNestedScrollConnection = childPagerNestedScrollConnection,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clipToBounds() .clipToBounds()
.nestedScroll(childPagerNestedScrollConnection)
.pagerHeight(animatedHeight), .pagerHeight(animatedHeight),
) { page -> ) { page ->
Box( Box(
@@ -27,6 +27,7 @@ import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -129,7 +130,7 @@ import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
import io.legado.app.ui.widget.components.log.AppLogSheet import io.legado.app.ui.widget.components.log.AppLogSheet
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.pager.rememberConsumeHorizontalPagerNestedScrollConnection import io.legado.app.ui.widget.components.pager.rememberPagerFlingPassThroughConnection
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import io.legado.app.ui.widget.components.tabRow.AppTabRow import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@@ -232,7 +233,10 @@ fun BookshelfScreen(
initialPage = uiState.selectedGroupIndex.coerceAtLeast(0), initialPage = uiState.selectedGroupIndex.coerceAtLeast(0),
pageCount = { uiState.groups.size } pageCount = { uiState.groups.size }
) )
val childPagerNestedScrollConnection = rememberConsumeHorizontalPagerNestedScrollConnection() val childPagerNestedScrollConnection = rememberPagerFlingPassThroughConnection(
state = pagerState,
orientation = Orientation.Horizontal,
)
val latestGroups by rememberUpdatedState(uiState.groups) val latestGroups by rememberUpdatedState(uiState.groups)
val latestSelectedGroupId by rememberUpdatedState(uiState.selectedGroupId) val latestSelectedGroupId by rememberUpdatedState(uiState.selectedGroupId)
@@ -835,9 +839,9 @@ fun BookshelfScreen(
} else { } else {
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
pageNestedScrollConnection = childPagerNestedScrollConnection,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.nestedScroll(childPagerNestedScrollConnection)
.then( .then(
with(sharedTransitionScope) { with(sharedTransitionScope) {
if (this != null) Modifier.skipToLookaheadSize() else Modifier if (this != null) Modifier.skipToLookaheadSize() else Modifier
@@ -1,5 +1,8 @@
package io.legado.app.ui.widget.components.pager package io.legado.app.ui.widget.components.pager
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.pager.PagerDefaults
import androidx.compose.foundation.pager.PagerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
@@ -7,29 +10,57 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.Velocity
@Composable private class PagerFlingPassThroughConnection(
fun rememberConsumeHorizontalPagerNestedScrollConnection(): NestedScrollConnection { private val delegate: NestedScrollConnection,
return remember { private val orientation: Orientation,
object : NestedScrollConnection { ) : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
return if (source == NestedScrollSource.Drag && available.x != 0f) {
Offset(x = available.x, y = 0f)
} else {
Offset.Zero
}
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { private fun Velocity.reverseOnOrientation(): Velocity {
return if (available.x != 0f) { return if (orientation == Orientation.Horizontal) {
Velocity(x = available.x, y = 0f) copy(x = -x, y = 0f)
} else { } else {
Velocity.Zero copy(x = 0f, y = -y)
}
}
} }
} }
override fun onPreScroll(
available: Offset,
source: NestedScrollSource,
): Offset {
return delegate.onPreScroll(available, source)
}
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
return delegate.onPostScroll(consumed, available, source)
}
override suspend fun onPreFling(available: Velocity): Velocity {
return delegate.onPreFling(available)
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
delegate.onPostFling(consumed, available)
return consumed.reverseOnOrientation()
}
}
@Composable
fun rememberPagerFlingPassThroughConnection(
state: PagerState,
orientation: Orientation,
): NestedScrollConnection {
val defaultConnection = PagerDefaults.pageNestedScrollConnection(
state = state,
orientation = orientation,
)
return remember(state, orientation, defaultConnection) {
PagerFlingPassThroughConnection(
delegate = defaultConnection,
orientation = orientation,
)
}
} }