안녕하세요 점냥입니다:)
이번 장에서는 bottom navigation bar를 compose로 어떻게 만들어야 할지 알아봅시다
Bottom Navigation Bar UI with Compose - (1) <- 현재 글입니다.
Bottom Navigation Bar State with Compose - (2)
Bottom Navigaiton Bar?
Bottom Navigation Bar은 material 위젯 중 하나로 하단에 위치하며, 대부분 클릭하면 다른 페이지로 이동하는 버튼들을 포함하는 위젯으로 사용합니다.
최근 Single Activity에 Multiple Fragment를 사용하는 것이 트렌드가 되기도 했고 Jetpack Navigation Component에서도 Bottom Navigation과 호환성을 제공해 주면서 흔히 사용하고 있는 Ux입니다. 그렇다면 Compose에서는 어떻게 사용할 수 있을까요?
Bottom Navigation Bar with Compose
@Composable
fun MainNavigation() {
//TODO: implement BottomNavigation
}
@Preview
@Composable
fun PrevMainNavigation() {
MainNavigation()
}
@Composable annotation을 함수 위에 선언하면 해당 함수는 내부에 Compose context가 전달되어 Composable 함수들을 호출할 수 있게 됩니다. 또한 해당 함수 그 자체가 하나의 Compose로 생성된 뷰, 객체를 뜻하게 됩니다.
(자세히 보면 함수 네이밍도 카멜 표기법이 아닌 파스칼 표기법처럼 사용한 것을 알 수 있습니다.)
@Preview annotation은 Compose를 사용하는 장점 중에 하나인데요! xml을 더 이상 사용하지 않는 Compose에서 xml처럼 작성한 코드를 실시간으로 디자인으로 확인할 수 있게 하는 기능입니다.
Composable BottomNavigation
@Composable
fun MainNavigation(modifier: Modifier = Modifier) {
BottomNavigation(modifier = modifier) {
//RowScope
}
}
@Preview
@Composable
fun PrevMainNavigation() {
MainNavigation(modifier = Modifier.fillMaxWidth())
}
감사하게도 구글은 Compose UI ToolKit을 배포하면서 Material 위젯과 관련한 Composable 함수들을 많이 제공해주고 있습니다.
BottomNavigation Composable도 그중 하나인데요. 내부 구현을 볼까요?
@Composable
fun BottomNavigation(
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
elevation: Dp = BottomNavigationDefaults.Elevation,
content: @Composable RowScope.() -> Unit
)
저희가 작성한 코드처럼 @Composable annotation이 선언되어있는 것을 확인할 수 있습니다.
Modifier 등 처음 보는 개념이 등장할 수 있지만 우리가 현재 살펴볼 부분은 content 파라미터입니다 :)
@Composable은 함수 또는 람다에도 선언해줄 수 있는데요. content처럼 @Composable 함수의 매개변수로 @Composable 람다를 주입받는 방식을 Slot API라고 부르고 있습니다. Text와 Icon 등을 작은 UI 요소들의 값을 주입받는 방식을 사용하지 않고 좀 더 확장성 있게 개발자가 직접 내부를 직접 Composable로 구현하는 패턴이라고 생각하면 됩니다.
또 하나의 봐야 할 점은 content의 람다가 RowScope 확장 형태라는 것입니다.
RowScope는 Compose에서 뷰를 정렬하는 여러 Composable 중 Row Composable의 Scope로 뷰들을 수평으로 정렬합니다.
위 두 가지 개념을 이용해서 우리는 Navigation Item 3개를 content 람다 내부에 정의해 줄 겁니다.
Navigation Item with Compose
BottomNavigation content에 내부에 직접 Image와 Text들을 선언하는 것이 아니라 @Composable를 이용해서 Navigation Item도 또 하나의 Composable 형태로 만드는 것이 좋습니다.
첫 번째 이유로, 우리는 3개의 Navigation Item을 만들 것이기 때문에 반복적이고 동일한 코드를 재사용하기 쉽게 개발하는 것이 효율적입니다.
두 번째 이유로는 Compose는 Recomposition 되면서 뷰 트리를 다시 그리게 되는 데 Composable 함수들은 참조한 데이터의 값이 변경될 때만 다시 그려지게 됩니다. 따라서 가능한 Composable로 쪼갤수록 이러한 Compose의 이점을 활용할 수 있습니다.
@Composable
fun RowScope.MainNavigationItem(
onClick: () -> Unit,
selected: Boolean,
@StringRes labelRes: Int,
@DrawableRes iconRes: Int,
modifier: Modifier = Modifier,
) {
BottomNavigationItem(
modifier = modifier,
icon = {
Icon(painter = painterResource(id = iconRes), contentDescription = null)
},
label = {
Text(text = stringResource(id = labelRes))
},
unselectedContentColor = colorResource(id = R.color.colorNotAccent),
selectedContentColor = colorResource(id = R.color.white),
onClick = onClick,
selected = selected,
alwaysShowLabel = false
)
}
@Preview
@Composable
fun PrevMainNavigationItem() {
Row {
MainNavigationItem(
labelRes = R.string.menu_story,
iconRes = R.drawable.ic_story,
onClick = {},
selected = false
)
}
}
사실 Compose는 BottomNavigationItem이라는 Composable을 제공해줍니다. 하지만 선택될 때 색상과 idle일 때 색상 등 공통부분을 처리하기 위해 MainNavigationItem Composable을 만들었습니다.
onClick과 selected 처리를 미흡하게 한 부분은 있지만 추후 이 부분에 대해서도 개선하는 작업을 진행해 봅시다!
마무리
@Composable
fun MainNavigation(modifier: Modifier = Modifier) {
BottomNavigation(
modifier = modifier,
backgroundColor = colorResource(id = R.color.colorPrimary)
) {
MainNavigationItem(
iconRes = R.drawable.ic_draw,
labelRes = R.string.menu_draw,
selected = true,
onClick = {}
)
MainNavigationItem(
iconRes = R.drawable.ic_story,
labelRes = R.string.menu_story,
selected = false,
onClick = {}
)
MainNavigationItem(
iconRes = R.drawable.ic_settings,
labelRes = R.string.menu_setting,
selected = false,
onClick = {}
)
}
}
Preview를 보니 우리가 의도한 대로 UI가 잘 구성된 것을 확인할 수 있습니다.
하지만 실행해서 확인하거나 Preview의 interactive mode를 실행해 보면 버튼을 눌러도 선택이 되지 않는 상태를 볼 수 있습니다. 이는 Compose의 State 관리를 해주지 않았기 때문입니다. Composable은 참조하고 있는 값이 변경되었을 때 ReComposition이 발생하는 데 MainNavigationItem은 항상 값이 고정이기 때문에 재구성이 일어나지 않습니다 ㅜㅜ
그러므로 다음 포스팅에서는 MainNavigationItem의 상태 관리하는 부분에 대해서 다뤄보겠습니다.
'Android > Compose' 카테고리의 다른 글
[Android] Compose BackdropScaffold 적용해보기 (0) | 2023.01.19 |
---|---|
[Android] Compose PreviewParameterProvider (2) | 2022.09.22 |
[Android] Compose - Accompanist Inset (0) | 2022.09.14 |
[Android] Jetpack Compose란? (0) | 2022.05.22 |
[Android] Compose Text 양끝으로 정렬하는 여러 방법 (0) | 2021.06.03 |