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)
|
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
|
@Composable
|
||||||
fun AppNavigation() {
|
fun AppNavigation() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -245,10 +249,36 @@ fun AppNavigation() {
|
|||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = TopLevel.HOME.route,
|
startDestination = TopLevel.HOME.route,
|
||||||
modifier = Modifier.padding(innerPadding),
|
modifier = Modifier.padding(innerPadding),
|
||||||
enterTransition = { slideInHorizontally(initialOffsetX = { it }, animationSpec = tween(280, easing = FastOutSlowInEasing)) + fadeIn(tween(200)) },
|
// Direction of a tab switch: +1 if the target tab sits to the right of the current
|
||||||
exitTransition = { slideOutHorizontally(targetOffsetX = { -it / 3 }, animationSpec = tween(280, easing = FastOutSlowInEasing)) + fadeOut(tween(180)) },
|
// one, -1 if to the left, 0 if this isn't a tab-to-tab move (detail push/pop).
|
||||||
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it / 3 }, animationSpec = tween(280, easing = FastOutSlowInEasing)) + fadeIn(tween(200)) },
|
enterTransition = {
|
||||||
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }, animationSpec = tween(280, easing = FastOutSlowInEasing)) + fadeOut(tween(180)) },
|
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) {
|
composable(TopLevel.HOME.route) {
|
||||||
HomeScreen(
|
HomeScreen(
|
||||||
|
|||||||
Reference in new issue
Block a user