Skip to content

Commit 5d387f1

Browse files
committed
Animate indication in bottom navigation bar
1 parent 06d9ecd commit 5d387f1

1 file changed

Lines changed: 88 additions & 51 deletions

File tree

app/src/main/java/ua/com/radiokot/money/home/view/HomeActivity.kt

Lines changed: 88 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,18 @@ import android.os.Bundle
2525
import androidx.activity.SystemBarStyle
2626
import androidx.activity.compose.setContent
2727
import androidx.activity.enableEdgeToEdge
28+
import androidx.compose.animation.AnimatedVisibility
29+
import androidx.compose.animation.core.Spring
30+
import androidx.compose.animation.core.animateFloatAsState
31+
import androidx.compose.animation.core.spring
2832
import androidx.compose.animation.core.tween
2933
import androidx.compose.animation.fadeIn
3034
import androidx.compose.animation.fadeOut
3135
import androidx.compose.foundation.LocalIndication
3236
import androidx.compose.foundation.background
3337
import androidx.compose.foundation.clickable
3438
import androidx.compose.foundation.layout.Arrangement
39+
import androidx.compose.foundation.layout.Box
3540
import androidx.compose.foundation.layout.Column
3641
import androidx.compose.foundation.layout.IntrinsicSize
3742
import androidx.compose.foundation.layout.Row
@@ -40,6 +45,7 @@ import androidx.compose.foundation.layout.WindowInsets
4045
import androidx.compose.foundation.layout.WindowInsetsSides
4146
import androidx.compose.foundation.layout.add
4247
import androidx.compose.foundation.layout.displayCutoutPadding
48+
import androidx.compose.foundation.layout.fillMaxHeight
4349
import androidx.compose.foundation.layout.fillMaxWidth
4450
import androidx.compose.foundation.layout.height
4551
import androidx.compose.foundation.layout.navigationBars
@@ -63,8 +69,10 @@ import androidx.compose.ui.Modifier
6369
import androidx.compose.ui.draw.drawWithContent
6470
import androidx.compose.ui.geometry.Offset
6571
import androidx.compose.ui.graphics.Color
72+
import androidx.compose.ui.graphics.graphicsLayer
6673
import androidx.compose.ui.platform.LocalContext
6774
import androidx.compose.ui.platform.LocalDensity
75+
import androidx.compose.ui.platform.LocalInspectionMode
6876
import androidx.compose.ui.text.TextStyle
6977
import androidx.compose.ui.text.font.FontWeight
7078
import androidx.compose.ui.text.style.TextAlign
@@ -445,7 +453,8 @@ private fun BottomNavigation(
445453
BottomNavigationEntry(
446454
text = "More",
447455
icon = "⚙️",
448-
isCurrent = lastVisitedBottomRoute == PreferencesScreenRoute,
456+
isCurrent = lastVisitedBottomRoute == PreferencesScreenRoute
457+
|| LocalInspectionMode.current,
449458
hasNotice = hasMoreNotice.value,
450459
modifier = Modifier
451460
.weight(1f)
@@ -471,67 +480,95 @@ private fun BottomNavigationEntry(
471480
.then(modifier)
472481
.width(IntrinsicSize.Max)
473482
) {
474-
Text(
475-
text = icon,
476-
style = TextStyle(
477-
fontSize = 16.sp,
478-
textAlign = TextAlign.Center,
479-
),
483+
Box(
484+
contentAlignment = Alignment.Center,
480485
modifier = Modifier
481-
.fillMaxWidth(0.65f)
482-
.then(
483-
other =
484-
if (isCurrent)
485-
Modifier.background(
486-
color = Color(0xFFD8CCE1),
487-
shape = RoundedCornerShape(
488-
percent = 50,
489-
)
490-
)
491-
else
492-
Modifier
493-
)
494-
.padding(
495-
vertical = 4.dp,
486+
.fillMaxWidth()
487+
.height(IntrinsicSize.Min)
488+
) {
489+
val indicationScaleX = animateFloatAsState(
490+
targetValue =
491+
if (isCurrent)
492+
1f
493+
else
494+
0.5f,
495+
animationSpec = spring(
496+
stiffness = Spring.StiffnessMediumLow,
497+
dampingRatio = Spring.DampingRatioMediumBouncy,
496498
)
497-
.run {
498-
if (!hasNotice) {
499-
return@run this
499+
)
500+
this@Column.AnimatedVisibility(
501+
visible = isCurrent,
502+
enter = fadeIn(initialAlpha = 0.5f),
503+
exit = fadeOut(),
504+
modifier = Modifier
505+
.fillMaxWidth(0.65f)
506+
.fillMaxHeight()
507+
.graphicsLayer {
508+
scaleX =
509+
if (isCurrent)
510+
indicationScaleX.value
511+
else
512+
1f
500513
}
501-
502-
val noticeCircleRadiusPx: Float
503-
val noticeCircleOffset: Offset
504-
with(LocalDensity.current) {
505-
noticeCircleRadiusPx = 4.dp.toPx()
506-
noticeCircleOffset = Offset(
507-
x = 18.dp.toPx(),
508-
y = (-8).dp.toPx(),
514+
) {
515+
Box(
516+
modifier = Modifier
517+
.background(
518+
color = Color(0xFFD8CCE1),
519+
shape = RoundedCornerShape(
520+
percent = 50,
521+
),
509522
)
510-
}
523+
)
524+
}
511525

512-
then(Modifier.drawWithContent {
513-
drawContent()
514-
drawCircle(
515-
color = Color.Red,
516-
radius = noticeCircleRadiusPx,
517-
center = center + noticeCircleOffset
518-
)
519-
})
520-
}
521-
)
526+
Text(
527+
text = icon,
528+
style = TextStyle(
529+
fontSize = 16.sp,
530+
textAlign = TextAlign.Center,
531+
),
532+
modifier = Modifier
533+
.fillMaxWidth(0.65f)
534+
.padding(
535+
vertical = 4.dp,
536+
)
537+
.run {
538+
if (!hasNotice) {
539+
return@run this
540+
}
541+
542+
val noticeCircleRadiusPx: Float
543+
val noticeCircleOffset: Offset
544+
with(LocalDensity.current) {
545+
noticeCircleRadiusPx = 4.dp.toPx()
546+
noticeCircleOffset = Offset(
547+
x = 18.dp.toPx(),
548+
y = (-8).dp.toPx(),
549+
)
550+
}
551+
552+
then(Modifier.drawWithContent {
553+
drawContent()
554+
drawCircle(
555+
color = Color.Red,
556+
radius = noticeCircleRadiusPx,
557+
center = center + noticeCircleOffset
558+
)
559+
})
560+
}
561+
)
562+
}
522563

523-
Spacer(modifier = Modifier.height(4.dp))
564+
Spacer(modifier = Modifier.height(2.dp))
524565

525566
Text(
526567
text = text,
527568
style = TextStyle(
528-
fontSize = 13.sp,
569+
fontSize = 12.sp,
529570
textAlign = TextAlign.Center,
530-
fontWeight =
531-
if (isCurrent)
532-
FontWeight.SemiBold
533-
else
534-
FontWeight.Normal
571+
fontWeight = FontWeight.SemiBold,
535572
),
536573
modifier = Modifier
537574
.fillMaxWidth()

0 commit comments

Comments
 (0)