You've already forked KernelSU
mirror of
https://github.com/tiann/KernelSU.git
synced 2025-08-27 23:46:34 +00:00
feat(ui): improve predictive back animations (#2675)
## Summary
This pull request introduces custom screen transition animations to
enhance the overall user experience during navigation.
The key change is the implementation of a custom slide/fade effect for
navigating from main screens (i.e., screens hosted in the bottom
navigation bar) to detail screens. Transitions between the bottom
navigation bar tabs themselves retain a simple, clean cross-fade effect
to ensure a fast and smooth user interaction.
This PR also addresses the root cause of an issue where custom
animations were being overridden by the navigation library's defaults.
## Root Cause
During implementation, it was discovered that custom transition
animations defined in the `defaultTransitions` parameter of the
`DestinationsNavHost` in `MainActivity` were not being applied. Instead,
a default fade-in/fade-out animation was always present.
The root cause was traced to the `compose-destinations` KSP (Kotlin
Symbol Processing) code generator. By default, the generator creates a
`NavGraphSpec` (e.g., `RootNavGraph.kt`) that includes its own
`defaultTransitions` property. This property, defined at compile-time
within the generated graph object, has a higher precedence than the
`defaultTransitions` parameter supplied to the `DestinationsNavHost`
composable at runtime.
As a result, our intended custom animations were being ignored and
overridden by the generated default.
## Solution
To resolve this precedence issue permanently, this PR adopts the
official configuration method recommended by the `compose-destinations`
library.
- The following KSP argument has been added to the
`app/build.gradle.kts` file:
```kotlin
ksp {
arg("compose-destinations.defaultTransitions", "none")
}
```
- This argument instructs the code generator to omit the
`defaultTransitions` property from the generated `NavGraphSpec`.
- By removing the higher-priority, generated default, the
`defaultTransitions` parameter on `DestinationsNavHost` now functions as
the effective default, allowing our custom animation logic to execute as
intended.
## Animation Logic Details
The new animation logic is conditional and defined within
`MainActivity`. It distinguishes between two primary navigation types:
- Main Screen → Detail Screen:
- Enter: The new detail screen slides in from the right.
- Exit: The old main screen slides out to the left while fading out.
- Detail Screen → Main Screen (on Pop):
- Pop Enter: The main screen slides back in from the left while fading
in.
- Pop Exit: The detail screen slides out to the right.
- Between Bottom Navigation Tabs:
- A simple cross-fade (`fadeIn`/`fadeOut`) is maintained for these
transitions to provide a quick and non-disruptive experience when
switching between primary sections of the app.
This commit is contained in:
@@ -92,6 +92,10 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ksp {
|
||||||
|
arg("compose-destinations.defaultTransitions", "none")
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import androidx.compose.animation.ExitTransition
|
|||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
|
import androidx.compose.animation.slideInHorizontally
|
||||||
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
import androidx.compose.foundation.layout.displayCutout
|
import androidx.compose.foundation.layout.displayCutout
|
||||||
@@ -59,13 +62,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
if (isManager) install()
|
if (isManager) install()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
KernelSUTheme {
|
KernelSUTheme {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val snackBarHostState = remember { SnackbarHostState() }
|
val snackBarHostState = remember { SnackbarHostState() }
|
||||||
|
val bottomBarRoutes = remember {
|
||||||
|
BottomBarDestination.entries.map { it.direction.route }.toSet()
|
||||||
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = { BottomBar(navController) },
|
bottomBar = { BottomBar(navController) },
|
||||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||||
@@ -78,10 +84,45 @@ class MainActivity : ComponentActivity() {
|
|||||||
navGraph = NavGraphs.root,
|
navGraph = NavGraphs.root,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
|
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
|
||||||
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
|
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
|
||||||
get() = { fadeIn(animationSpec = tween(340)) }
|
// If the target is a detail page (not a bottom navigation page), slide in from the right
|
||||||
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
|
if (targetState.destination.route !in bottomBarRoutes) {
|
||||||
get() = { fadeOut(animationSpec = tween(340)) }
|
slideInHorizontally(initialOffsetX = { it })
|
||||||
|
} else {
|
||||||
|
// Otherwise (switching between bottom navigation pages), use fade in
|
||||||
|
fadeIn(animationSpec = tween(340))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
|
||||||
|
// If navigating from the home page (bottom navigation page) to a detail page, slide out to the left
|
||||||
|
if (initialState.destination.route in bottomBarRoutes && targetState.destination.route !in bottomBarRoutes) {
|
||||||
|
slideOutHorizontally(targetOffsetX = { -it / 4 }) + fadeOut()
|
||||||
|
} else {
|
||||||
|
// Otherwise (switching between bottom navigation pages), use fade out
|
||||||
|
fadeOut(animationSpec = tween(340))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val popEnterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
|
||||||
|
// If returning to the home page (bottom navigation page), slide in from the left
|
||||||
|
if (targetState.destination.route in bottomBarRoutes) {
|
||||||
|
slideInHorizontally(initialOffsetX = { -it / 4 }) + fadeIn()
|
||||||
|
} else {
|
||||||
|
// Otherwise (e.g., returning between multiple detail pages), use default fade in
|
||||||
|
fadeIn(animationSpec = tween(340))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val popExitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
|
||||||
|
// If returning from a detail page (not a bottom navigation page), scale down and fade out
|
||||||
|
if (initialState.destination.route !in bottomBarRoutes) {
|
||||||
|
scaleOut(targetScale = 0.9f) + fadeOut()
|
||||||
|
} else {
|
||||||
|
// Otherwise, use default fade out
|
||||||
|
fadeOut(animationSpec = tween(340))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user