From fc3c8ec882f9eced9ef7f6e0805e074c1ac31df7 Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 19 May 2026 17:58:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Android=2013+=20=E9=A2=84=E6=B5=8B?= =?UTF-8?q?=E6=80=A7=E8=BF=94=E5=9B=9E=E6=89=8B=E5=8A=BF=EF=BC=88Predictiv?= =?UTF-8?q?e=20Back=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BackHandler 升级为 PredictiveBackHandler expect/actual - Android 13+ 启用系统级预测返回,跟手阶段同步位移/缩放页面 - Android 低版本回退至普通 BackHandler - iOS 保持空实现(无系统返回手势) - 页面返回动画统一 250ms 时长,提升流畅感 - AndroidManifest 启用 enableOnBackInvokedCallback --- androidApp/src/main/AndroidManifest.xml | 1 + .../plus/rua/project/Platform.android.kt | 28 ++++++++-- .../commonMain/kotlin/plus/rua/project/App.kt | 53 ++++++++++++++++--- .../kotlin/plus/rua/project/Platform.kt | 15 ++++-- .../kotlin/plus/rua/project/Platform.ios.kt | 11 ++-- 5 files changed, 90 insertions(+), 18 deletions(-) diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml index 298cd22..edf4072 100644 --- a/androidApp/src/main/AndroidManifest.xml +++ b/androidApp/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ Unit) { - androidx.activity.compose.BackHandler(enabled = enabled, onBack = onBack) -} \ No newline at end of file +actual fun PredictiveBackHandler( + enabled: Boolean, + onProgress: (Float) -> Unit, + onBack: () -> Unit, + onCancel: () -> Unit +) { + if (Build.VERSION.SDK_INT >= 34) { + val scope = rememberCoroutineScope() + androidx.activity.compose.PredictiveBackHandler(enabled) { progress -> + try { + progress.collect { backEvent -> + onProgress(backEvent.progress) + } + onBack() + } catch (e: CancellationException) { + onCancel() + } + } + } else { + androidx.activity.compose.BackHandler(enabled = enabled, onBack = onBack) + } +} diff --git a/shared/src/commonMain/kotlin/plus/rua/project/App.kt b/shared/src/commonMain/kotlin/plus/rua/project/App.kt index aa8bf72..68a8521 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/App.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/App.kt @@ -1,6 +1,7 @@ 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 @@ -13,10 +14,12 @@ 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 @@ -31,6 +34,20 @@ private enum class Screen { Main, About, Licenses } @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) { @@ -38,13 +55,13 @@ fun App() { targetState = currentScreen, transitionSpec = { if (targetState.ordinal > initialState.ordinal) { - // 向前导航:新页面从右侧滑入覆盖,旧页面略微左移+淡出 + // 正向导航:新页面从右侧滑入覆盖,旧页面略微左移+淡出 (slideInHorizontally { it } + fadeIn()) togetherWith (slideOutHorizontally { -it / 4 } + fadeOut()) } else { - // 向后导航:新页面从左侧滑入,旧页面略微右移+淡出 - (slideInHorizontally { -it } + fadeIn()) togetherWith - (slideOutHorizontally { it / 4 } + fadeOut()) + // 返回导航:新页面从左侧滑入,旧页面向右侧滑出 + (slideInHorizontally(animationSpec = tween(250)) { -it } + fadeIn(animationSpec = tween(250))) togetherWith + (slideOutHorizontally(animationSpec = tween(250)) { it } + fadeOut(animationSpec = tween(250))) } }, modifier = Modifier.fillMaxSize() @@ -55,16 +72,36 @@ fun App() { onNavigateToAbout = { currentScreen = Screen.About } ) Screen.About -> { - BackHandler { currentScreen = Screen.Main } + PredictiveBackHandler( + enabled = backProgress == 0f, + onProgress = { backProgress = it }, + onBack = handleBack, + onCancel = handleCancel + ) AboutScreen( onBack = { currentScreen = Screen.Main }, - onNavigateToLicenses = { currentScreen = Screen.Licenses } + 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 -> { - BackHandler { currentScreen = Screen.About } + PredictiveBackHandler( + enabled = backProgress == 0f, + onProgress = { backProgress = it }, + onBack = handleBack, + onCancel = handleCancel + ) LicensesScreen( - onBack = { currentScreen = Screen.About } + onBack = { currentScreen = Screen.About }, + modifier = Modifier.graphicsLayer { + translationX = backProgress * size.width * 0.3f + scaleX = 1f - backProgress * 0.05f + scaleY = 1f - backProgress * 0.05f + } ) } } diff --git a/shared/src/commonMain/kotlin/plus/rua/project/Platform.kt b/shared/src/commonMain/kotlin/plus/rua/project/Platform.kt index 6faab22..116f9d3 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/Platform.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/Platform.kt @@ -19,10 +19,17 @@ expect fun getGifUri(gifFile: String): String expect fun getAppIconUri(): String /** - * 拦截系统返回手势。 + * 预测性返回手势处理器(Android 13+)。 * - * @param enabled 是否启用拦截 - * @param onBack 返回回调 + * @param enabled 是否启用 + * @param onProgress 手势进度回调(0.0~1.0),跟手过程中持续调用 + * @param onBack 手势完成回调(滑动距离足够,执行返回) + * @param onCancel 手势取消回调(滑动距离不足,回弹) */ @Composable -expect fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) \ No newline at end of file +expect fun PredictiveBackHandler( + enabled: Boolean = true, + onProgress: (Float) -> Unit = {}, + onBack: () -> Unit, + onCancel: () -> Unit = {} +) diff --git a/shared/src/iosMain/kotlin/plus/rua/project/Platform.ios.kt b/shared/src/iosMain/kotlin/plus/rua/project/Platform.ios.kt index 3c1efc8..683436e 100644 --- a/shared/src/iosMain/kotlin/plus/rua/project/Platform.ios.kt +++ b/shared/src/iosMain/kotlin/plus/rua/project/Platform.ios.kt @@ -15,6 +15,11 @@ actual fun getGifUri(gifFile: String): String = "compose.resource://files/$gifFi actual fun getAppIconUri(): String = "compose.resource://files/app_icon.png" @Composable -actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) { - // iOS 没有系统返回键,由导航栏按钮处理 -} \ No newline at end of file +actual fun PredictiveBackHandler( + enabled: Boolean, + onProgress: (Float) -> Unit, + onBack: () -> Unit, + onCancel: () -> Unit +) { + // iOS 没有预测性返回手势,由导航栏按钮处理 +}