refactor: 优化阅读界面菜单切换page时的高度过渡动画
This commit is contained in:
@@ -613,6 +613,7 @@ private fun ReadBookMenuSurface(
|
||||
title = stringResource(R.string.read_config),
|
||||
maxHeight = maxHeight,
|
||||
bottomPadding = if (extendSurfaceToNavigationBar) navBarHeight else 0.dp,
|
||||
animateSize = false,
|
||||
onBack = { onIntent(ReadBookIntent.ReadMenuBack) },
|
||||
) {
|
||||
ReadStyleContent(
|
||||
@@ -762,6 +763,7 @@ private fun ReadBookMenuRoutePage(
|
||||
maxHeight: Dp,
|
||||
scrollContent: Boolean = false,
|
||||
bottomPadding: Dp = 0.dp,
|
||||
animateSize: Boolean = true,
|
||||
onBack: () -> Unit,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
@@ -769,7 +771,7 @@ private fun ReadBookMenuRoutePage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = maxHeight)
|
||||
.animateContentSize()
|
||||
.let { if (animateSize) it.animateContentSize() else it }
|
||||
.padding(top = 16.dp, bottom = 16.dp + bottomPadding),
|
||||
) {
|
||||
Row(
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package io.legado.app.ui.book.read.sheet
|
||||
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -15,20 +18,26 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
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.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import io.legado.app.R
|
||||
@@ -39,7 +48,6 @@ import io.legado.app.ui.book.read.ConfigUpdate
|
||||
import io.legado.app.ui.book.read.ReadBookIntent
|
||||
import io.legado.app.ui.widget.components.FontSelectSheet
|
||||
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
|
||||
import org.koin.compose.koinInject
|
||||
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.TinyDropdownSettingItem
|
||||
@@ -48,6 +56,7 @@ import io.legado.app.ui.widget.components.settingItem.TinySwitchSettingItem
|
||||
import io.legado.app.ui.widget.components.tabRow.CardTabRow
|
||||
import io.legado.app.utils.getCompatColor
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
// Color picker IDs
|
||||
private const val COLOR_HEADER = 7
|
||||
@@ -70,6 +79,34 @@ internal fun HeaderFooterPage(
|
||||
val pagerState = rememberPagerState(pageCount = { 3 })
|
||||
var selectedTab by remember { mutableIntStateOf(0) }
|
||||
|
||||
val pageHeights = remember { mutableStateMapOf<Int, Int>() }
|
||||
val density = LocalDensity.current
|
||||
val animatedHeight by remember(pagerState, density) {
|
||||
derivedStateOf {
|
||||
val pageCount = pagerState.pageCount
|
||||
val position = (pagerState.currentPage + pagerState.currentPageOffsetFraction)
|
||||
.coerceIn(0f, (pageCount - 1).coerceAtLeast(0).toFloat())
|
||||
val floorPage = position.toInt().coerceIn(0, pageCount - 1)
|
||||
val ceilPage = (floorPage + 1).coerceIn(0, pageCount - 1)
|
||||
|
||||
val floorHeight = pageHeights[floorPage] ?: 0
|
||||
val ceilHeight = pageHeights[ceilPage] ?: floorHeight
|
||||
|
||||
val fraction = position - floorPage
|
||||
|
||||
val startHeight = if (floorHeight > 0) floorHeight else (pageHeights.values.firstOrNull() ?: 0)
|
||||
val endHeight = if (ceilHeight > 0) ceilHeight else startHeight
|
||||
|
||||
val interpolated = startHeight + (endHeight - startHeight) * fraction
|
||||
|
||||
if (interpolated > 0) {
|
||||
with(density) { interpolated.toDp() }
|
||||
} else {
|
||||
Dp.Unspecified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Header state
|
||||
var headerMode by remember { mutableIntStateOf(ReadBookConfig.headerMode) }
|
||||
var headerLeft by remember { mutableIntStateOf(ReadBookConfig.tipHeaderLeft) }
|
||||
@@ -138,15 +175,31 @@ internal fun HeaderFooterPage(
|
||||
selectedTabIndex = selectedTab,
|
||||
onTabSelected = { index ->
|
||||
selectedTab = index
|
||||
scope.launch { pagerState.animateScrollToPage(index) }
|
||||
scope.launch {
|
||||
pagerState.animateScrollToPage(
|
||||
page = index,
|
||||
animationSpec = tween(durationMillis = 400, easing = FastOutSlowInEasing)
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp),
|
||||
)
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Top,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clipToBounds()
|
||||
.pagerHeight(animatedHeight),
|
||||
) { page ->
|
||||
when (page) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onSizeChanged { size ->
|
||||
pageHeights[page] = size.height
|
||||
}
|
||||
) {
|
||||
when (page) {
|
||||
0 -> {
|
||||
// Header tab
|
||||
Column(
|
||||
@@ -372,6 +425,7 @@ internal fun HeaderFooterPage(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Color picker
|
||||
ColorPickerSheet(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.legado.app.ui.book.read.sheet
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -8,14 +10,22 @@ import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
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.mutableStateMapOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.layout.layout
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.book.read.ConfigUpdate
|
||||
@@ -44,6 +54,34 @@ fun ReadStyleContent(
|
||||
val pagerState = rememberPagerState(pageCount = { 3 })
|
||||
var currentPage by remember { mutableIntStateOf(0) }
|
||||
|
||||
val pageHeights = remember { mutableStateMapOf<Int, Int>() }
|
||||
val density = LocalDensity.current
|
||||
val animatedHeight by remember(pagerState, density) {
|
||||
derivedStateOf {
|
||||
val pageCount = pagerState.pageCount
|
||||
val position = (pagerState.currentPage + pagerState.currentPageOffsetFraction)
|
||||
.coerceIn(0f, (pageCount - 1).coerceAtLeast(0).toFloat())
|
||||
val floorPage = position.toInt().coerceIn(0, pageCount - 1)
|
||||
val ceilPage = (floorPage + 1).coerceIn(0, pageCount - 1)
|
||||
|
||||
val floorHeight = pageHeights[floorPage] ?: 0
|
||||
val ceilHeight = pageHeights[ceilPage] ?: floorHeight
|
||||
|
||||
val fraction = position - floorPage
|
||||
|
||||
val startHeight = if (floorHeight > 0) floorHeight else (pageHeights.values.firstOrNull() ?: 0)
|
||||
val endHeight = if (ceilHeight > 0) ceilHeight else startHeight
|
||||
|
||||
val interpolated = startHeight + (endHeight - startHeight) * fraction
|
||||
|
||||
if (interpolated > 0) {
|
||||
with(density) { interpolated.toDp() }
|
||||
} else {
|
||||
Dp.Unspecified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(pagerState) {
|
||||
snapshotFlow { pagerState.currentPage }.collect { page ->
|
||||
currentPage = page
|
||||
@@ -57,35 +95,46 @@ fun ReadStyleContent(
|
||||
) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
verticalAlignment = Alignment.Top,
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.animateContentSize(),
|
||||
.clipToBounds()
|
||||
.pagerHeight(animatedHeight),
|
||||
) { page ->
|
||||
when (page) {
|
||||
0 -> GlobalThemePage(
|
||||
onToggleDayNight = onToggleDayNight,
|
||||
onOpenBgTextConfig = onOpenBgTextConfig,
|
||||
onOpenTextTitle = onOpenTextTitle,
|
||||
onOpenPaddingConfig = onOpenPaddingConfig,
|
||||
onShareLayoutChange = { shareLayout ->
|
||||
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ShareLayout(shareLayout)))
|
||||
},
|
||||
onStyleSelect = { index ->
|
||||
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.StyleSelect(index)))
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
onIntent = onIntent,
|
||||
styleConfig = styleConfig,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onSizeChanged { size ->
|
||||
pageHeights[page] = size.height
|
||||
}
|
||||
) {
|
||||
when (page) {
|
||||
0 -> GlobalThemePage(
|
||||
onToggleDayNight = onToggleDayNight,
|
||||
onOpenBgTextConfig = onOpenBgTextConfig,
|
||||
onOpenTextTitle = onOpenTextTitle,
|
||||
onOpenPaddingConfig = onOpenPaddingConfig,
|
||||
onShareLayoutChange = { shareLayout ->
|
||||
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ShareLayout(shareLayout)))
|
||||
},
|
||||
onStyleSelect = { index ->
|
||||
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.StyleSelect(index)))
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
onIntent = onIntent,
|
||||
styleConfig = styleConfig,
|
||||
)
|
||||
|
||||
1 -> SystemMenuPage(
|
||||
customIcons = readMenuCustomIcons,
|
||||
bottomBarButtons = bottomBarButtons,
|
||||
onIntent = onIntent,
|
||||
)
|
||||
2 -> HeaderFooterPage(
|
||||
onIntent = onIntent,
|
||||
)
|
||||
1 -> SystemMenuPage(
|
||||
customIcons = readMenuCustomIcons,
|
||||
bottomBarButtons = bottomBarButtons,
|
||||
onIntent = onIntent,
|
||||
)
|
||||
|
||||
2 -> HeaderFooterPage(
|
||||
onIntent = onIntent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +149,12 @@ fun ReadStyleContent(
|
||||
selectedTabIndex = currentPage,
|
||||
onTabSelected = { index ->
|
||||
if (index < 3) {
|
||||
scope.launch { pagerState.animateScrollToPage(index) }
|
||||
scope.launch {
|
||||
pagerState.animateScrollToPage(
|
||||
page = index,
|
||||
animationSpec = tween(durationMillis = 400, easing = FastOutSlowInEasing)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
onOpenMoreConfig()
|
||||
}
|
||||
@@ -109,3 +163,11 @@ fun ReadStyleContent(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Modifier.pagerHeight(height: Dp) = this.layout { measurable, constraints ->
|
||||
val placeable = measurable.measure(constraints)
|
||||
val layoutHeight = if (height != Dp.Unspecified) height.roundToPx() else placeable.height
|
||||
layout(placeable.width, layoutHeight) {
|
||||
placeable.placeRelative(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package io.legado.app.ui.book.read.sheet
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -24,20 +26,28 @@ import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.SkipNext
|
||||
import androidx.compose.material.icons.filled.SkipPrevious
|
||||
import androidx.compose.material.icons.filled.Translate
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import io.legado.app.R
|
||||
@@ -89,6 +99,34 @@ internal fun SystemMenuPage(
|
||||
val pagerState = rememberPagerState(pageCount = { 3 })
|
||||
var selectedTab by remember { mutableIntStateOf(0) }
|
||||
|
||||
val pageHeights = remember { mutableStateMapOf<Int, Int>() }
|
||||
val density = LocalDensity.current
|
||||
val animatedHeight by remember(pagerState, density) {
|
||||
derivedStateOf {
|
||||
val pageCount = pagerState.pageCount
|
||||
val position = (pagerState.currentPage + pagerState.currentPageOffsetFraction)
|
||||
.coerceIn(0f, (pageCount - 1).coerceAtLeast(0).toFloat())
|
||||
val floorPage = position.toInt().coerceIn(0, pageCount - 1)
|
||||
val ceilPage = (floorPage + 1).coerceIn(0, pageCount - 1)
|
||||
|
||||
val floorHeight = pageHeights[floorPage] ?: 0
|
||||
val ceilHeight = pageHeights[ceilPage] ?: floorHeight
|
||||
|
||||
val fraction = position - floorPage
|
||||
|
||||
val startHeight = if (floorHeight > 0) floorHeight else (pageHeights.values.firstOrNull() ?: 0)
|
||||
val endHeight = if (ceilHeight > 0) ceilHeight else startHeight
|
||||
|
||||
val interpolated = startHeight + (endHeight - startHeight) * fraction
|
||||
|
||||
if (interpolated > 0) {
|
||||
with(density) { interpolated.toDp() }
|
||||
} else {
|
||||
Dp.Unspecified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shared state for sheets
|
||||
var showColorPicker by remember { mutableStateOf(false) }
|
||||
var colorPickerId by remember { mutableIntStateOf(0) }
|
||||
@@ -112,39 +150,58 @@ internal fun SystemMenuPage(
|
||||
selectedTabIndex = selectedTab,
|
||||
onTabSelected = { index ->
|
||||
selectedTab = index
|
||||
scope.launch { pagerState.animateScrollToPage(index) }
|
||||
scope.launch {
|
||||
pagerState.animateScrollToPage(
|
||||
page = index,
|
||||
animationSpec = tween(durationMillis = 400, easing = FastOutSlowInEasing)
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp),
|
||||
)
|
||||
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.weight(1f, fill = false),
|
||||
verticalAlignment = Alignment.Top,
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.clipToBounds()
|
||||
.pagerHeight(animatedHeight),
|
||||
) { page ->
|
||||
when (page) {
|
||||
0 -> GlobalMenuTab(
|
||||
preferences = preferences,
|
||||
readBarStyle = preferences.readBarStyle,
|
||||
readMenuColorMode = preferences.readMenuColorMode,
|
||||
onIntent = onIntent,
|
||||
onShowColorPicker = { id, initial ->
|
||||
colorPickerId = id
|
||||
colorPickerInitial = initial
|
||||
showColorPicker = true
|
||||
},
|
||||
)
|
||||
1 -> BottomBarTab(
|
||||
preferences = preferences,
|
||||
customIcons = customIcons,
|
||||
onIntent = onIntent,
|
||||
onShowIconSheet = { showIconSheet = true },
|
||||
onShowColorPicker = { id, initial ->
|
||||
colorPickerId = id
|
||||
colorPickerInitial = initial
|
||||
showColorPicker = true
|
||||
},
|
||||
)
|
||||
2 -> TopBarTab(preferences = preferences, onIntent = onIntent)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onSizeChanged { size ->
|
||||
pageHeights[page] = size.height
|
||||
}
|
||||
) {
|
||||
when (page) {
|
||||
0 -> GlobalMenuTab(
|
||||
preferences = preferences,
|
||||
readBarStyle = preferences.readBarStyle,
|
||||
readMenuColorMode = preferences.readMenuColorMode,
|
||||
onIntent = onIntent,
|
||||
onShowColorPicker = { id, initial ->
|
||||
colorPickerId = id
|
||||
colorPickerInitial = initial
|
||||
showColorPicker = true
|
||||
},
|
||||
)
|
||||
|
||||
1 -> BottomBarTab(
|
||||
preferences = preferences,
|
||||
customIcons = customIcons,
|
||||
onIntent = onIntent,
|
||||
onShowIconSheet = { showIconSheet = true },
|
||||
onShowColorPicker = { id, initial ->
|
||||
colorPickerId = id
|
||||
colorPickerInitial = initial
|
||||
showColorPicker = true
|
||||
},
|
||||
)
|
||||
|
||||
2 -> TopBarTab(preferences = preferences, onIntent = onIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user