[Kotlin / Compose] BottomNavigation Migration

Subeen·2024년 5월 14일
0

Compose

목록 보기
13/20

MainActivity

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MainScreenView()
        }
    }
}

MainScreenView

MainScreenView 함수는 하단 바를 포함하여 메인 화면을 구성하며, 화면 탐색을 위한 네비게이션 그래프를 호스팅하는 역할을 한다.
Scaffold는 앱의 레이아웃을 설정하는 데 사용된다.
bottomBarScaffold의 하단에 배치되는 요소로 BottomNavigationUI 함수를 호출하여 바텀 네비게이션 뷰를 구성한다.
NavigationGraphScaffold의 메인 Content로 NavigationGraph 함수가 호출되어 네비게이션 그래프를 설정한다.

@SuppressLint("UnusedMaterialScaffoldPaddingParameter", "UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainScreenView() {
    val navController = rememberNavController()
    Scaffold(
        bottomBar = { BottomNavigationUI(navController = navController) } // 바텀 네비게이션 뷰 
    ) {
        NavigationGraph(navController = navController) // 네비게이션 그래프 
    }
}

BottomNavigationUI

BottomNavigationUI 함수는 사용자가 여러 화면을 이동할 때 사용되는 바텀 네비게이션을 UI로 정의하는 역할을 한다.

@Composable
fun BottomNavigationUI(navController: NavHostController) {
	// 바텀 네비게이션에 표시될 아이템 목록 
    val items = listOf(
        BottomNavItem.Search,
        BottomNavItem.Storage,
    )

    // 현재 선택된 라우트를 추적하기 위한 MutableState를 선언
    val selectedRoute = remember { mutableStateOf(BottomNavItem.Search.screenRoute) }
    
	// 네비게이션을 감싸 그림자와 모양을 설정하기 위한 Surface
    Surface(
    	// Modifier.shadow 함수 내부에서 그림자를 그리기 위해 shadowElevation을 0으로 설정 
        shadowElevation = 0.dp,
        modifier = Modifier
            .padding(12.dp)
            .height(60.dp)
            .shadow(
                color = colorResource(id = R.color.empty),
                borderRadius = 32.dp,
                blurRadius = 32.dp,
                offsetY = 12.dp,
                offsetX = 12.dp,
                spread = 1f.dp
                ),
        color = Color.Transparent
    ) {
        BottomNavigation(
            backgroundColor = Color.White,
            contentColor = Color(0xFF3F414E),
            modifier = Modifier
                .clip(RoundedCornerShape(32.dp))
        ) {
        	// 현재의 BackStackEntry와 루트를 찾아 현재 경로를 결정 
            val navBackStackEntry by navController.currentBackStackEntryAsState()
            val currentRoute = navBackStackEntry?.destination?.route

			// 아이템 목록을 순회하여 BottomNavigationItem을 구성 
            items.forEach { item ->
                BottomNavigationItem(
                    icon = {
                        Icon(
                            painter = painterResource(id = item.icon), // 아이콘 설정
                            contentDescription = stringResource(id = item.title), // 아이콘 설명 
                            modifier = Modifier
                                .width(26.dp)
                                .height(26.dp), // 아이콘 크기 
                            tint = if (currentRoute == item.screenRoute) {
                                colorResource(id = R.color.color_1) // 선택된 아이콘 색상
                            } else {
                                colorResource(id = R.color.empty) // 선택되지 않은 아이콘 색상 
                            }
                        )
                    },
                    label = {
                        Text(
                            stringResource(id = item.title), // 라벨 텍스트 
                            fontSize = 9.sp,
                            color = if (currentRoute == item.screenRoute) {
                                colorResource(id = R.color.color_1)
                            } else {
                                colorResource(id = R.color.empty)
                            }
                        )
                    },
                    selected = currentRoute == item.screenRoute,
                    alwaysShowLabel = true,
                    onClick = {
                        // 클릭된 아이템의 화면 라우트를 선택된 라우트로 설정
                        selectedRoute.value = item.screenRoute

                        // 네비게이션 그래프에서 시작 목적지의 경로를 찾음
                        val startDestination = navController.graph.findStartDestination().route

                        // 클릭된 아이템의 화면으로 네비게이션을 시작합니다.
                        navController.navigate(item.screenRoute) {
                            // 시작 경로로 백 스택을 pop하고 상태를 저장
                            if (startDestination != null) {
                                popUpTo(startDestination) {
                                    saveState = true
                                }
                            }
                            // 목적지가 백 스택의 맨 위에 있으면 해당 목적지를 다시 시작하지 않도록 지정
                            launchSingleTop = true
                            // 목적지로 이동할 때 이전 상태를 복원하도록 지정
                            restoreState = true
                        }
                    }
                )
            }
        }
    }
}
	onClick = {
		// 클릭된 아이템의 화면 라우트를 선택된 라우트로 설정
		selectedRoute.value = item.screenRoute
		// 네비게이션 그래프에서 시작 목적지의 경로를 찾음
		val startDestination = navController.graph.findStartDestination().route
		// 클릭된 아이템의 화면으로 네비게이션을 시작
		navController.navigate(item.screenRoute) {
		// 시작 경로로 백 스택을 pop하고 상태를 저장
			if (startDestination != null) {
				popUpTo(startDestination) {
					saveState = true
				}
			}
			// 목적지가 백 스택의 맨 위에 있으면 해당 목적지를 다시 시작하지 않도록 지정
			launchSingleTop = true
			// 목적지로 이동할 때 이전 상태를 복원하도록 지정
			restoreState = true
		}
	}
  • selectedRoute.value = item.screenRoute : 클릭된 아이템의 화면 라우트를 selectedRoute에 설정하며 현재 선택된 아이템을 추적하는 데 사용된다.
  • val startDestination = navController.graph.findStartDestination().route : 네비게이션 그래프에서 시작 목적지의 route를 찾으며 현재 경로를 비교하여 back stack을 설정하는 데 사용된다.
  • navController.navigate(item.screenRoute) { ... } : 네비게이션을 시작한다.
    • item.screenRoute는 클릭된 바텀 네비게이션 아이템의 화면 라우트이다.
  • if (startDestination != null) { popUpTo(startDestination) { saveState = true } } : 시작 경로로 백 스택을 pop하고, 상태를 저장한다.
  • launchSingleTop = true : 목적지가 이미 스택에 있으면 새 인스턴스를 만들지 않습니다.
  • restoreState = true : 목적지로 이동할 때 이전 상태를 복원하도록 지정한다.

NavigationGraph 함수는 네비게이션을 설정하는 역할을 한다.

  • NavHost를 사용하여 네비게이션을 호스팅하며 화면 간의 이동을 관리한다.
    • NavHostnavController를 사용하여 화면 간의 이동을 처리한다.
    • startDestination를 사용하여 네비게이션의 시작점을 정의한다.
  • navHostController는 네비게이션을 관리하는 데 사용되는 객체이다.
  • composable 함수를 사용하여 각각의 바텀 네비게이션 아이템에 대한 화면을 정의한다.
sealed class BottomNavItem(
    val title: Int, // 아이템의 제목 리소스
    val icon: Int, // 아이템의 아이콘 리소스
    val screenRoute: String // 아이템에 연결된 화면의 경로 
) {
    data object Search : BottomNavItem(R.string.fragment_search, R.drawable.ic_tab_search, SEARCH)
    data object Storage :
        BottomNavItem(R.string.fragment_favorite, R.drawable.ic_tab_storage, STORAGE)
}

@Composable
fun NavigationGraph(navHostController: NavHostController) {
    // 네비게이션의 호스트를 설정 
    NavHost(navController = navHostController, startDestination = BottomNavItem.Search.screenRoute) {
        // 바텀 네비게이션 아이템에 따른 화면을 정의
        composable(route = BottomNavItem.Search.screenRoute) {
            SearchListScreen() // 검색 화면을 표시하는 컴포저블을 호출
        }
        composable(route = BottomNavItem.Storage.screenRoute) {
            StorageScreen() // 보관함 화면을 표시하는 컴포저블을 호출
        }
    }
}

shadow

fun Modifier.shadow(
    color: Color = Color.Black,
    borderRadius: Dp = 0.dp,
    blurRadius: Dp = 0.dp,
    offsetY: Dp = 0.dp,
    offsetX: Dp = 0.dp,
    spread: Dp = 0f.dp,
    modifier: Modifier = Modifier
) = this.then(
    modifier.drawBehind {
        this.drawIntoCanvas {
            val paint = Paint()
            val frameworkPaint = paint.asFrameworkPaint()
            val spreadPixel = spread.toPx()
            val leftPixel = (0f - spreadPixel) + offsetX.toPx()
            val topPixel = (0f - spreadPixel) + offsetY.toPx()
            val rightPixel = (this.size.width + spreadPixel)
            val bottomPixel = (this.size.height + spreadPixel)
            if (blurRadius != 0.dp) {
                frameworkPaint.maskFilter =
                    (BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL))
            }
            frameworkPaint.color = color.toArgb()
            it.drawRoundRect(
                left = leftPixel,
                top = topPixel,
                right = rightPixel,
                bottom = bottomPixel,
                radiusX = borderRadius.toPx(),
                radiusY = borderRadius.toPx(),
                paint
            )
        }
    }
)
profile
개발 공부 기록 🌱

0개의 댓글