feat(android): directional tab transitions in the bottom nav
Tab switches slid in a fixed direction (and sometimes flipped between push/pop feel, so the direction felt random). Now the slide follows the target tab's position in the bottom bar: tapping a tab to the right slides in from the right, a tab to the left slides in from the left — applied across all four enter/exit/popEnter/popExit transitions so it's consistent regardless of how Compose classifies the move. Detail screen push/pop transitions are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
179e0eb902
commit
b1a32f5130
1 file changed
+34
-4
@@ -53,6 +53,10 @@ private enum class TopLevel(val route: String, val icon: ImageVector, val label:
|
||||
|
||||
private val NavBg = Color(0xF7080814)
|
||||
|
||||
// Position of a route among the top-level tabs (in bottom-bar order), or -1 if it isn't one.
|
||||
// Used to slide tab switches toward the side the target tab sits on, instead of a push/pop feel.
|
||||
private fun topLevelIndex(route: String?): Int = TopLevel.entries.indexOfFirst { it.route == route }
|
||||
|
||||
@Composable
|
||||
fun AppNavigation() {
|
||||
val context = LocalContext.current
|
||||
@@ -245,10 +249,36 @@ fun AppNavigation() {
|
||||
navController = navController,
|
||||
startDestination = TopLevel.HOME.route,
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
enterTransition = { slideInHorizontally(initialOffsetX = { it }, animationSpec = tween(280, easing = FastOutSlowInEasing)) + fadeIn(tween(200)) },
|
||||
exitTransition = { slideOutHorizontally(targetOffsetX = { -it / 3 }, animationSpec = tween(280, easing = FastOutSlowInEasing)) + fadeOut(tween(180)) },
|
||||
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it / 3 }, animationSpec = tween(280, easing = FastOutSlowInEasing)) + fadeIn(tween(200)) },
|
||||
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }, animationSpec = tween(280, easing = FastOutSlowInEasing)) + fadeOut(tween(180)) },
|
||||
// Direction of a tab switch: +1 if the target tab sits to the right of the current
|
||||
// one, -1 if to the left, 0 if this isn't a tab-to-tab move (detail push/pop).
|
||||
enterTransition = {
|
||||
val f = topLevelIndex(initialState.destination.route)
|
||||
val t = topLevelIndex(targetState.destination.route)
|
||||
val dir = if (f >= 0 && t >= 0 && f != t) (if (t > f) 1 else -1) else 0
|
||||
if (dir != 0) slideInHorizontally(tween(280, easing = FastOutSlowInEasing)) { dir * it } + fadeIn(tween(200))
|
||||
else slideInHorizontally(tween(280, easing = FastOutSlowInEasing)) { it } + fadeIn(tween(200))
|
||||
},
|
||||
exitTransition = {
|
||||
val f = topLevelIndex(initialState.destination.route)
|
||||
val t = topLevelIndex(targetState.destination.route)
|
||||
val dir = if (f >= 0 && t >= 0 && f != t) (if (t > f) 1 else -1) else 0
|
||||
if (dir != 0) slideOutHorizontally(tween(280, easing = FastOutSlowInEasing)) { -dir * it } + fadeOut(tween(180))
|
||||
else slideOutHorizontally(tween(280, easing = FastOutSlowInEasing)) { -it / 3 } + fadeOut(tween(180))
|
||||
},
|
||||
popEnterTransition = {
|
||||
val f = topLevelIndex(initialState.destination.route)
|
||||
val t = topLevelIndex(targetState.destination.route)
|
||||
val dir = if (f >= 0 && t >= 0 && f != t) (if (t > f) 1 else -1) else 0
|
||||
if (dir != 0) slideInHorizontally(tween(280, easing = FastOutSlowInEasing)) { dir * it } + fadeIn(tween(200))
|
||||
else slideInHorizontally(tween(280, easing = FastOutSlowInEasing)) { -it / 3 } + fadeIn(tween(200))
|
||||
},
|
||||
popExitTransition = {
|
||||
val f = topLevelIndex(initialState.destination.route)
|
||||
val t = topLevelIndex(targetState.destination.route)
|
||||
val dir = if (f >= 0 && t >= 0 && f != t) (if (t > f) 1 else -1) else 0
|
||||
if (dir != 0) slideOutHorizontally(tween(280, easing = FastOutSlowInEasing)) { -dir * it } + fadeOut(tween(180))
|
||||
else slideOutHorizontally(tween(280, easing = FastOutSlowInEasing)) { it } + fadeOut(tween(180))
|
||||
},
|
||||
) {
|
||||
composable(TopLevel.HOME.route) {
|
||||
HomeScreen(
|
||||
|
||||
Reference in new issue
Block a user