탑 바를 custom 하게 관리하려고 composable을 따로 만들었다. compose의 Layout을 이용하여 좀 더 세세하게 만들 수 있었지만, 이는 추후에 필요할 경우 만들기로 한다.
@Composable
fun CustomTopBar(
color: Color = Color.Transparent,
headContent: @Composable (() -> Unit)? = null,
titleContent: @Composable () -> Unit,
actionContent: @Composable (() -> Unit)? = null
) {
Surface(color = color) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp)
.requiredHeight(56.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (headContent != null) {
headContent()
Spacer(modifier = Modifier.requiredWidth(12.dp))
}
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.CenterStart
) {
titleContent()
}
if (actionContent != null) {
Spacer(modifier = Modifier.requiredWidth(12.dp))
actionContent()
}
}
}
}
다음으로는 NavigationBar를 만들었다. 여러 탭들을 만들고 자연스럽게 이리저리 옮겨 다닐 수 있게 만들기 위해선 필수적인 composable이라 할 수 있고, 나는 이를 Scaffold 로 만들어 AppContent.kt에서 필요한 screen에만 사용해주었다.
## BottomNavigationBar.kt
@Composable
fun BottomNavigationScaffold(
onHomeTabClick: () -> Unit,
onSearchTabClick: () -> Unit,
content: @Composable () -> Unit
) {
Box(
modifier = Modifier.fillMaxSize()
) {
Box(
modifier = Modifier.padding(bottom = 40.dp)
) {
content()
}
BottomNavigationBar(
onHomeTabClick = onHomeTabClick,
onSearchTabClick = onSearchTabClick
)
}
}
@Composable
private fun BoxScope.BottomNavigationBar(
onHomeTabClick: () -> Unit,
onSearchTabClick: () -> Unit
) {
Row(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.height(40.dp)
.background(Color.Black)
) {
BottomNavigationItem(
tab = Tab.Home,
onClick = onHomeTabClick
)
BottomNavigationItem(
tab = Tab.Search,
onClick = onSearchTabClick
)
}
}
@Composable
private fun RowScope.BottomNavigationItem(
tab: Tab,
onClick: () -> Unit,
) {
Column(
modifier = Modifier
.weight(1f)
.height(40.dp)
.clickable { onClick() },
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
modifier = Modifier.requiredSize(20.dp),
painter = painterResource(id = tab.icon),
contentDescription = null,
tint = Color.White
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = tab.title,
color = Color.White
)
}
}
## AppContent.kt (이렇게 적용해주면 된다.)
#### 과거 코드
composable(
route = "home"
) {
CompositionLocalProvider(
provideHomeViewModelFactory {
hiltViewModel<RealHomeViewModel>()
}
) {
HomeScreen()
}
}
#### 수정한 코드
composable(
route = "home"
) {
CompositionLocalProvider(
provideHomeViewModelFactory {
hiltViewModel<RealHomeViewModel>()
}
) {
BottomNavigationScaffold(
onHomeTabClick = {},
onSearchTabClick = {
navController.navigate("search")
}
) {
HomeScreen()
}
}
}
## 요것도 AppContent.kt에 추가하기
enum class Tab(
val title: String,
val icon: Int
) {
Home(title = "Home", icon = R.drawable.ic_home),
Search(title = "Search", icon = R.drawable.ic_search)
}
이렇게 AppContent는 navController를 알고있기 때문에 바로 navigation callback을 넣어주면 쉽게 적용시킬 수 있다. 아직 아주 못생겼지만 잘 동작하는 걸 볼 수 있다. 또한 Tab 추가도 매우 간편해졌다.
오늘의 작업 결과를 코드는 요 branch에서 확인해 볼 수 있고, 앱 자체는 현재 아래와 같은 상태이다. 아직 텅텅 비어있지만 이제 Tab도 구축되었으니 쭉쭉 화면을 한번 그려나가 볼 일만 남았다. UI 자체를 단순히 그리는 일은 별로 어렵지 않을거라 예상되는데, API를 쓰고, 또 composition 관리를 해보며 어떠한 어려움을 마주하게 될지 궁금해진다. 그럼 이만.
icon의 경우 FreeIcon이나 Flaticon을 이용하고 있다.