지난 편에서는 BottomNavigation 기능 자체를 구현하는 데 집중했다.
‼️ 하지만 ‼️ 클론 코딩은 어디까지나 실제 앱마냥 착각할 수 있을 만큼 만들어야하는 것.
지난 편에서 구현한 것과, 실제 토스를 비교해보면서 디테일을 조금 더 살려보자.
지난 편에 구현한 모습 | 실제 토스 |
---|
[커스텀 목록]
1. 상단 모서리 둥글게하기
2. 아이템 클릭 효과 커스텀하기
3. 시스템 모드(라이트/다크) 적용하기
사실 다른 BottomNavigationItem을 클릭했을 때 잠깐 아이콘이 작아졌다가 손을 떼면 원래대로 돌아오도록 만들고 싶었는데.. 방법을 찾지 못했다. 시도해보고 성공하게 된다면 다음번에 글을 하나 더 써보겠다.
그럼 우선 위의 커스텀 목록을 하나씩 살펴보겠다!
@Composable
fun BottomBar(navController: NavHostController) {
val screens = listOf<BottomNavItem>(
//...
)
//...
BottomNavigation(
backgroundColor = MaterialTheme.colorScheme.primaryContainer
) {
screens.forEach { screens ->
AddItem(item = screens, currentDestination = currentDestination, navController =navController )
}
}
}
@Composable
fun RowScope.AddItem(
item: BottomNavItem,
currentDestination: NavDestination?,
navController: NavHostController
) {
BottomNavigationItem(
label = { Text(text = stringResource(item.title), fontSize = 10.sp) },
icon = {
Icon(
painter = painterResource(item.icon),
contentDescription = item.route,
)
},
selected = currentDestination?.hierarchy?.any {
it.route == item.route
} == true,
selectedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
onClick = {
navController.navigate(item.route){
popUpTo(navController.graph.findStartDestination().id)
launchSingleTop =true
}
}
)
}
사용되는 부분만 들고왔다. MainScreen.kt
전체 코드는 이 글 마지막에 다시 정리하겠다.
BottomBar()
함수 안에서 BottomNavigation
를 Box로 감싸주면 된다.
// 상단 모서리 둥글게하기
Box(Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
.background(Color.Transparent)
.border(
0.3.dp,
MaterialTheme.colorScheme.tertiaryContainer,
RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
)
) {
BottomNavigation(
// ..
){}
}
상단 쪽만 radius를 줄 거기 때문에 topStart, topEnd를 20.dp로 RoundedCornerShape
를 사용해 둥근 상단을 만들어준다.
border
도 살짝 만들어준다.
그럼 이렇게 만들어진다!
하단에 네비게이션바 색은 우선 무시하도록 하자. (실제 토스는 투명색이다)
@Composable
fun RowScope.AddItem(
item: BottomNavItem,
currentDestination: NavDestination?,
navController: NavHostController
) {
BottomNavigationItem(
// label, icon, selectedContentColor 등 지정
// 아이템 클릭 효과
modifier = Modifier
.width(24.dp)
.padding(4.dp)
.background(Color.Transparent, RoundedCornerShape(16.dp))
.clip(RoundedCornerShape(16.dp))
)
}
클릭 시 모습
이전 | 이후 | 실제 토스 |
---|
실제 토스와 완벽하게 똑같지는 않지만.. (실제 토스는 클릭 효과가 거의 정사각형이고, 클릭하면 아이콘이 조금 작아진다.) Modifier에 width와 height를 둘 다 주거나, width 대신 size로 지정하게 되면 아이콘과 라벨이 나오는 영역이 조금 이상해져서ㅜㅜ 우선 저기에서 타협을 보기로 했다.
시작할 때 실제 앱마냥 착각할 수 있을 만큼 만들어야한다고 말했던 사람 나야나....
이것도 우선 구현할 다른 기능이 많으니 다음번을 기약하도록 하자....
이건 간단하다!
라이트모드일 때와 다크모드일 때의 색상을 Color.kt에 미리 지정해두고, Theme에서 지정해주면 된다.
// 라이트모드 색상
val md_theme_light_primaryContainer = Color(0xFFFFFFFF) // backgroundColor
val md_theme_light_tertiaryContainer = Color(0xFFE7EBEE) // borderColor
val md_theme_light_onPrimaryContainer = Color(0xFF191F28) // selectedColor
val val md_theme_light_onSurface = Color(0xFFB0B8C1) // unselectedColor
// 다크모드 색상
val md_theme_dark_primaryContainer = Color(0xFF17171C) // backgroundColor
val md_theme_dark_tertiaryContainer = Color(0xFF18171C) // borderColor
val md_theme_dark_onPrimaryContainer = Color(0xFFFFFFFF) // selectedColor
val md_theme_dark_onSurface = Color(0xFF62626D) // unselectedColor
라이트-다크 색상이 1:1로 매칭되면 된다.
색상 코드의 경우 토스 화면을 캡쳐해서 추출한 색을 바탕으로 추가했다.
private val LightColors = lightColorScheme(
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
tertiaryContainer = md_theme_light_tertiaryContainer,
onSurface = md_theme_light_onSurface,
//...
)
private val DarkColors = darkColorScheme(
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onSurface = md_theme_dark_onSurface,
// ...
)
@Composable
fun TossTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
val colors = if (!useDarkTheme) {
LightColors
} else {
DarkColors
}
MaterialTheme(
colorScheme = colors,
shapes = Shapes,
content = content
)
}
다른 색상도 추가했지만, BottomNavigation 구현에 쓰인 색상을 저들 뿐이라 나머지는 지웠다.
@Composable
fun BottomBar(navController: NavHostController) {
// ...
// 상단 모서리 둥글게
Box(
Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
.background(Color.Transparent)
.border(
0.5.dp,
MaterialTheme.colorScheme.tertiaryContainer,
RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
)
) {
BottomNavigation(
backgroundColor = MaterialTheme.colorScheme.primaryContainer,
) {
screens.forEach { screens ->
AddItem(item = screens, currentDestination = currentDestination, navController =navController )
}
}
}
}
@Composable
fun RowScope.AddItem(
item: BottomNavItem,
currentDestination: NavDestination?,
navController: NavHostController
) {
BottomNavigationItem(
// label, icon, selected..
selectedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
)
사용은 MaterialTheme.colorScheme.onPrimaryContainer
이런 식으로 Theme에서 적어준 이름을 사용하면 된다.
그럼 라이트 <-> 다크로 변할 때 색상이 알아서 잘 바뀐다.
라이트모드 | 다크모드 |
---|
라이트모드 | 다크모드 |
---|
화면이 바뀔 때 조금 껌뻑이는 문제, 네이게이션바를 투명하게 하는 법,아이콘에 애니메이션 넣기 등 자잘한 이슈들은 다음번에 추가로 다뤄보겠다!
🔗 Github: https://github.com/nahy-512/Compose_Toss
자세한 코드는 깃허브에서 확인할 수 있다.