ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Jetpack compose 레이아웃 - ②
    Android 2023. 5. 12. 16:58

     

     

    들어가기 전에

    Compose 에서 제공하는 Material 레이아웃의 대해 알아보자.

     

    Material 구성요소 및 레이아웃

    Jetpack Compose 는 디지털 인터페이스를 만들기 위한 포괄적인 디자인 시스템인 Material Design 구현을 제공한다.

    Material 구성요소(버튼, 카드, 스위치 등) 및 레이아웃(예: Scaffold)은 구성 가능한 함수로 사용할 수 있다.

     

    Material 구성요소는 사용자 인터페이스를 만드는 대화형 구성요소이다. Compose 는 이러한 여러 구성요소를 바로 제공한다.

     

    Material 구성요소는 앱의 MaterialTheme 에서 제공하는 값을 사용한다.

    @Composable
    fun MyApp() {
        MaterialTheme {
            // Material Components like Button, Card, Switch, etc.
        }
    }

     

    Material 테마 설정에 관한 자세한 내용은 여기를 참고하자.

     

    콘텐츠 슬롯

    내부 콘텐츠(텍스트 라벨, 아이콘 등)를 지원하는 Material 구성요소는 구성 가능한 콘텐츠를 허용하는 일반 람다인 '슬롯'과 함께 공개 상수(예: 크기, 패딩)를 제공하여 Material 사양과 일치하도록 내부 콘텐츠 배치를 지원하는 경향이 있다.

     

    Button 예시를 보자.

    Button(
        onClick = { /* ... */ },
        // Uses ButtonDefaults.ContentPadding by default
        contentPadding = PaddingValues(
            start = 20.dp,
            top = 12.dp,
            end = 20.dp,
            bottom = 12.dp
        )
    ) {
        // Inner content including an icon and a text label
        Icon(
            Icons.Filled.Favorite,
            contentDescription = "Favorite",
            modifier = Modifier.size(ButtonDefaults.IconSize)
        )
        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
        Text("Like")
    }

     

    Button 에는 일반 content 후행 람다 슬롯이 있고 이 슬롯은 RowScope 를 사용하여 행에서 콘텐츠 컴포저블을 배치한다.

    내부 콘텐츠에 패딩을 적용하는 contentPadding 매개변수도 있다. ButtonDefaults 를 통해 제공된 상수나 맞춤 값을 사용할 수 있다.

     

    무슨 소리인가 싶다..... 다시 설명하자면 이런것이다.

    @Composable
    fun Button(
        onClick: () -> Unit,
        modifier: Modifier = Modifier,
        enabled: Boolean = true,
        interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
        elevation: ButtonElevation? = ButtonDefaults.elevation(),
        shape: Shape = MaterialTheme.shapes.small,
        border: BorderStroke? = null,
        colors: ButtonColors = ButtonDefaults.buttonColors(),
        contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
        content: @Composable RowScope.() -> Unit
    ): Unit

     

    content 후행 람다 슬롯이 있다 ☞ @Composable RowScope.() → Unit 형태를 제공한다는 뜻이며 이것이 하나의 슬롯이된다. 이 슬롯은 RowScope 인터페이스를 사용해 행에서 콘텐츠를 배치한다는 뜻이다.

     

    내부 콘텐츠에 패딩을 적용하는 contentPadding 매개변수도 있다 ☞ 말 그대로 매개변수로 contentPadding 제공하는데 ButtonDefaults.contentPadding 을 통해 미리 정해놓은 상수 값을 사용할 수 있고 상/하/좌/우 값을 커스텀 할 수 있다는 뜻이다.

     

     

    ExtendedFloatingActionButton 예시를 보자.

    ExtendedFloatingActionButton(
        onClick = { /* ... */ },
        icon = {
            Icon(
                Icons.Filled.Favorite,
                contentDescription = "Favorite"
            )
        },
        text = { Text("Like") }
    )

     

    일반 content 람다가 아닌 ExtendedFloatingActionButton 에는 icon 과 text 라벨용 슬롯 2개가 있다. 각 슬롯은 구성 가능한 일반 콘텐츠를 지원하지만 구성요소는 이러한 내부 콘텐츠 배치 방식에 관해 독단적이다. 내부적으로 패딩과 정렬, 크기를 처리한다.

     

    @Composable
    fun ExtendedFloatingActionButton(
        text: @Composable () -> Unit,
        onClick: () -> Unit,
        modifier: Modifier = Modifier,
        icon: (@Composable () -> Unit)? = null,
        interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
        shape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
        backgroundColor: Color = MaterialTheme.colors.secondary,
        contentColor: Color = contentColorFor(backgroundColor),
        elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation()
    ): Unit

     

    슬롯에 대해서 정리한 후 이 구문을 보니 그나마 조금 이해가 된다.

    icon 과 text 라벨용 슬롯 2개가 있다 ☞ @Composable () → Unit 형태를 제공하는게 2개 보이니 슬롯 2개가 있다는 뜻이다.

     

    각 슬롯은 일반 콘텐츠를 지원하지만 구성요소는 이러한 내부 콘텐츠 배치 방식에 관해 독단적 ☞ icon 과 text 요소는 슬롯형태로 또 다른 구성 요소를 구성할 수 있다는 뜻이며 이렇게 구성된 콘텐츠 배치 방식은 상위 구성요소와는 연관되어 움직이지 않고 독단적으로 배치된다는 뜻이다.

     

    말을 아주 쉽지 않게 설명 되어있어 재해석해보았다.

     

    Scaffold

    compose 는 Material 구성요소를 일반 화면 패턴으로 결합하는 편리한 레이아웃을 제공한다. Scaffold 와 같은 컴포저블은 다양한 구성요소와 기타 화면 요소를 위한 슬롯을 제공한다.

     

    ✅ 여기서 잠깐
    Slot 의 사전적 정의는 '무엇인가 집어넣게 만든 가느다란 구멍, 자리, 틈" 을 뜻하며 쉽게 말해 무언가 넣을 수 있는 빈 틈을 말한다.

     

     

    화면 콘텐츠

    Scaffold 에는 일반 content 후행 람다 슬롯이 있다. 람다는 콘텐츠 루트에 적용해야 하는 PaddingValues 인스턴스를 수신(예: Modifier.padding 을 통해) 하여 상단과 하단 막대가 있으면 이를 오프셋한다.

    @Composable
    fun Scaffold(
        modifier: Modifier = Modifier,
        scaffoldState: ScaffoldState = rememberScaffoldState(),
        topBar: @Composable () -> Unit = {},
        bottomBar: @Composable () -> Unit = {},
        snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
        floatingActionButton: @Composable () -> Unit = {},
        floatingActionButtonPosition: FabPosition = FabPosition.End,
        isFloatingActionButtonDocked: Boolean = false,
        drawerContent: (@Composable ColumnScope.() -> Unit)? = null,
        drawerGesturesEnabled: Boolean = true,
        drawerShape: Shape = MaterialTheme.shapes.large,
        drawerElevation: Dp = DrawerDefaults.Elevation,
        drawerBackgroundColor: Color = MaterialTheme.colors.surface,
        drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
        drawerScrimColor: Color = DrawerDefaults.scrimColor,
        backgroundColor: Color = MaterialTheme.colors.background,
        contentColor: Color = contentColorFor(backgroundColor),
        content: @Composable (PaddingValues) -> Unit
    ): Unit

     

    위 내용도 한번에 이해하기 쉽지 않다. 쉽게 말해 화면 콘텐츠 꾸밀 때 Scaffold 를 제공하고 이 함수는 매개변수도 받을 수 있고 슬롯도 제공하고 있어 상단과 하단에 bar 가 있으면 요소 간의 거리를 루트 콘텐츠와 수신해 배치한다.

    아래 예시를 보며 이해해보자.

     

     

    앱 바

    Scaffold 는 상단 앱 바 and 하단 앱 바 슬롯을 제공한다.

    Scaffold(
        topBar = {
            TopAppBar { /* Top app bar content */ }
        }
    ) {
        // Screen content
    }

     

    Scaffold(
        bottomBar = {
            BottomAppBar { /* Bottom app bar content */ }
        }
    ) {
        // Screen content
    }

     

    이러한 슬롯은 BottomNavigation 과 같은 다른 Material 구성요소에 사용할 수 있다.

     

     

    플로팅 작업 버튼

    Scaffold 는 floatingActionButton 슬롯과 FloatingActionButton 을 사용할 수 있다.

    Scaffold(
        floatingActionButton = {
            FloatingActionButton(onClick = { /* ... */ }) {
                /* FAB content */
            }
        }
    ) {
        // Screen content
    }

     

     

    새 이메일 작성 플로팅 작업 버튼

     

     

    두 개(현재 위치 찾기, 길 찾기)의 중요한 기능을 제공하는 플로팅 작업 버튼

     

     

    스낵바

    Scaffold 는 스낵바를 표시하는 방법을 제공한다.

    SnackbarHostState 속성이 포함된 ScaffoldState 를 통해 제공된다. rememberScaffoldState 를 사용하여 scaffoldState 매개변수로 Scaffold 에 전달해야 하는 ScaffoldState 인스턴스를 만들 수 있다.

    SnackbarHostState 를 통해 showSnackbar 함수에 액세스할 수 있다. 이 함수는 CoroutineScope 가 필요하고 UI 이벤트에 대한 응답으로 호출되어 Scaffold 내의 Snackbar 를 표시할 수 있다.

    val scaffoldState = rememberScaffoldState()
    val scope = rememberCoroutineScope()
    Scaffold(
        scaffoldState = scaffoldState,
        floatingActionButton = {
            ExtendedFloatingActionButton(
                text = { Text("Show snackbar") },
                onClick = {
                    scope.launch {
                        scaffoldState.snackbarHostState
                            .showSnackbar("Snackbar")
                    }
                }
            )
        }
    ) {
        // Screen content
    }

     

     

    모달 탐색 창을 구현할 때 Scaffold 를 사용하는 방법과 사용하지 않는 방법 2가지가 존재한다.

     

     

    • ColumnScope 인터페이스를 사용하여 열에서 콘텐츠를 배치하는 drawerContent 슬롯을 사용할 수 있다.
    Scaffold(
        drawerContent = {
            Text("Drawer title", modifier = Modifier.padding(16.dp))
            Divider()
            // Drawer items
        }
    ) {
        // Screen content
    }

     

     

    • Scaffold 없이 MoadlDrawer 컴포저블을 사용하면 된다.
    val drawerState = rememberDrawerState(DrawerValue.Closed)
    ModalDrawer(
        drawerState = drawerState,
        drawerContent = {
            // Drawer content
        }
    ) {
        // Screen content
    }

     

     

    • 하단 탐색 창을 구현하려면 BottomDrawer 컴포저블을 사용하면 된다.
    val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
    BottomDrawer(
        drawerState = drawerState,
        drawerContent = {
            // Drawer content
        }
    ) {
        // Screen content
    }

     

     

    하단 시트

    표준 하단 시트를 구현하려면 BottomSheetScaffold 컴포저블을 사용하면 된다.

    BottomSheetScaffold(
        sheetContent = {
            // Sheet content
        }
    ) {
        // Screen content
    }

     

     

    모달 하단 시트를 구현하려면 ModalBottomSheetLayout 컴포저블을 사용하면 된다.

    val sheetState = rememberModalBottomSheetState(
        ModalBottomSheetValue.Hidden
    )
    ModalBottomSheetLayout(
        sheetState = sheetState,
        sheetContent = {
            // Sheet content
        }
    ) {
        // Screen content
    }

     

     

    배경화면

    배경화면을 구현하려면 BackdropScaffold 컴포저블을 사용하면 된다.

    BackdropScaffold(
        appBar = {
            // Top app bar
        },
        backLayerContent = {
            // Back layer content
        },
        frontLayerContent = {
            // Front layer content
        }
    )

     

Designed by Tistory.