xfy fc3c8ec882 feat: Android 13+ 预测性返回手势(Predictive Back)
- BackHandler 升级为 PredictiveBackHandler expect/actual
- Android 13+ 启用系统级预测返回,跟手阶段同步位移/缩放页面
- Android 低版本回退至普通 BackHandler
- iOS 保持空实现(无系统返回手势)
- 页面返回动画统一 250ms 时长,提升流畅感
- AndroidManifest 启用 enableOnBackInvokedCallback
2026-05-19 17:58:49 +08:00

111 lines
4.5 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package plus.rua.project
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.tooling.preview.Preview
import plus.rua.project.ui.AboutScreen
import plus.rua.project.ui.CalendarMonthView
import plus.rua.project.ui.LicensesScreen
private enum class Screen { Main, About, Licenses }
/**
* 应用入口 Composable根据系统主题切换明暗 ColorScheme 并管理页面导航。
*/
@Composable
@Preview(name = "Calendar App")
fun App() {
var currentScreen by remember { mutableStateOf(Screen.Main) }
var backProgress by remember { mutableFloatStateOf(0f) }
val handleBack: () -> Unit = {
backProgress = 0f
when (currentScreen) {
Screen.About -> currentScreen = Screen.Main
Screen.Licenses -> currentScreen = Screen.About
else -> {}
}
}
val handleCancel: () -> Unit = {
backProgress = 0f
}
val colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
MaterialTheme(colorScheme = colorScheme) {
AnimatedContent(
targetState = currentScreen,
transitionSpec = {
if (targetState.ordinal > initialState.ordinal) {
// 正向导航:新页面从右侧滑入覆盖,旧页面略微左移+淡出
(slideInHorizontally { it } + fadeIn()) togetherWith
(slideOutHorizontally { -it / 4 } + fadeOut())
} else {
// 返回导航:新页面从左侧滑入,旧页面向右侧滑出
(slideInHorizontally(animationSpec = tween(250)) { -it } + fadeIn(animationSpec = tween(250))) togetherWith
(slideOutHorizontally(animationSpec = tween(250)) { it } + fadeOut(animationSpec = tween(250)))
}
},
modifier = Modifier.fillMaxSize()
) { screen ->
when (screen) {
Screen.Main -> CalendarMonthView(
modifier = Modifier,
onNavigateToAbout = { currentScreen = Screen.About }
)
Screen.About -> {
PredictiveBackHandler(
enabled = backProgress == 0f,
onProgress = { backProgress = it },
onBack = handleBack,
onCancel = handleCancel
)
AboutScreen(
onBack = { currentScreen = Screen.Main },
onNavigateToLicenses = { currentScreen = Screen.Licenses },
modifier = Modifier.graphicsLayer {
translationX = backProgress * size.width * 0.3f
scaleX = 1f - backProgress * 0.05f
scaleY = 1f - backProgress * 0.05f
}
)
}
Screen.Licenses -> {
PredictiveBackHandler(
enabled = backProgress == 0f,
onProgress = { backProgress = it },
onBack = handleBack,
onCancel = handleCancel
)
LicensesScreen(
onBack = { currentScreen = Screen.About },
modifier = Modifier.graphicsLayer {
translationX = backProgress * size.width * 0.3f
scaleX = 1f - backProgress * 0.05f
scaleY = 1f - backProgress * 0.05f
}
)
}
}
}
}
}