Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

유스의 개발 일지

[Android/Kotlin]Compose Layout(2) 본문

Android

[Android/Kotlin]Compose Layout(2)

YusAndroid 2024. 11. 11. 14:11


Adaptive Layout (적응형 레이아웃)

 
XML 레이아웃으로 설정할 경우 여러 형태의 모바일 기기에 적용하기 위해서 많은 설정들이 필요합니다. 여러가지 화면에 대응할 때에 어려움이 있었는데 Jetpack Compose는 선언형 UI 도구 키트로, 다양한 디스플레이 크기에 맞게 콘텐츠를 다르게 렌더링하도록 동적으로 변경되는 레이아웃을 설계하고 구현하는 데 이상적이라고 합니다.
 
Android 앱은 폴더블 플립형 스마트폰에서부터 벽걸이 TV 모든 유형의 사용자에게 우수한 사용자 환경을 제공하기 위해 다양한 디스플레이 크기와 구성에 맞게 앱의 UI를 조정할 수 있습니다. 이 화면 공간을 최대한 활용하는 인기 Android 앱 런타임 시 해당 공간에 대한 변경사항(방향 변경 및 창 포함) 화면 분할 및 자유 형식 윈도잉 모드에서 크기 조절합니다.
 
 
표준 레이아웃은 다양한 폼 팩터에 최적의 사용자 경험을 제공하는 검증된 다목적 레이아웃입니다. 이 레이아웃은 작은 화면의 스마트폰부터 태블릿, 폴더블 기기, ChromeOS 기기까지 지원하며, Material Design 지침을 기반으로 하여 기능적이면서도 미적인 특징을 갖추고 있습니다. Android 프레임워크는 이러한 레이아웃 구현을 간편하고 안정적으로 지원하는 전문화된 컴포넌트를 포함하고 있습니다.
 
적응형 레이아웃 종류
  1. List-Detail
    • 설명이나 추가 정보를 포함한 항목을 리스트 패널과 디테인 패널, 두 개 패널로 나눠 보여줍니다. 확장된 화면에서는 목록과 상세 정보를 동시에 보여주고, 중간 또는 소형 화면에서는 상호작용에 따라 리스트 패널 또는 디테인 패널만 표시합니다. ( 메시징 앱, 연락처 관리자, 파일 탐색기 등 )
  2. 피드 레이아웃 (Feed Layout)
    • 피드 레이아웃은 동일한 콘텐츠 요소를 빠르게 볼 수 있도록 그리드로 배치합니다. 화면 크기에 맞춰 단일 열부터 다중 열의 스크롤 가능한 피드를 지원합니다. ( 뉴스 앱, 소셜 미디어 앱 등 )
  3. 지원창 레이아웃 (Supporting Pane Layout)
    • 지원창 레이아웃은 주 패널 (Main Pane) 와 보조 패널 (Supporting Pane) 를 두 개의 영역으로 나눕니다. 확장된 화면에서는 두 콘텐츠를 나란히 배치하고, 중간 및 소형 화면에서는 보조 콘텐츠를 하단 시트로 표시할 수 있습니다. (생산성 앱 - 문서와 검토 코멘트, 미디어 앱 - 동영상과 관련 비디오 목록 등 )

Compose 앱의 주요 컴포저블은 기본적으로 화면 전체를 채우도록 설계되며, 이 경우 화면 크기에 따라 UI Layout을 유연하게 변경하여 대형 화면에서도 최적화된 경험을 제공하는 것이 좋습니다.

  • 앱 수준 컴포저블 : 앱에 지정된 모든 공간을 차지하고 다른 모든 컴포저블을 포함하는 단일 루트 컴포저블입니다.
  • 화면 수준 컴포저블 : 앱의 모든 공간을 차지하는 앱 수준 컴포저블 내에 포함된 컴포저블입니다. 각 화면 수준 컴포저블은 앱을 탐색할 때 일반적으로 특정 대상을 나타냅니다.
  • 개별 컴포저블 : 앱 수준 또는 화면 수준 컴포저블이 아닌 모든 컴포저블 이러한 요소는 개별 요소, 재사용 가능한 콘텐츠 그룹, 화면 수준 컴포저블 내에서 호스팅되는 컴포저블일 수 있습니다.

BoxWithConstraints ( 사용 가능한 공간에 따라 다양한 컴포저블을 호출하는 데 사용할 수 있는 측정 제약 조건을 제공합니다. ) 레이아웃을 변경해도 상태를 유지할 수 있습니다.

일부 크기에서 사용되지 않을 수도 있는 정보를 호이스팅하여 Layout 크기가 변경될 때 사용자의 상태를 유지할 수 있습니다.

예를 들어 크기 조절로 인해 Layout이 설명 숨기기와 설명 표시하기 간에 전환될 때 사용자 상태가 유지되도록 showMore Boolean 플래그를 호이스팅할 수 있습니다.

 

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

 

Compose의 선언형 패러다임과 윈도우 크기 클래스 로직을 활용하여 화면 크기에 따라 적절한 레이아웃을 구현할 수 있습니다.

@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

 

  • Compact Width : 가로 < 600dp, 대부분 스마트폰 세로 화면에 해당
  • Compact Height : 높이 < 480dp, 대부분 스마트폰 가로 화면에 해당
  • Medium Width : 600dp ≤ 가로 < 840dp, 태블릿 세로 화면 및 대부분의 대형 폴더블 기기 화면
  • Medium Height : 480dp ≤ 높이 < 900dp, 태블릿 가로 화면 및 대부분의 스마트폰 세로 화면
  • Expanded Width : 가로 ≥ 840dp, 태블릿 가로 화면 및 대부분의 대형 폴더블 기기 가로 화면
  • Expanded Height : 높이 ≥ 900dp, 세로 화면 태블릿

* Window Size Class 특징은 기기의 화면 크기와 관계없이 앱에 할당된 화면 크기로 분류된다. 기기의 화면 크기가 동일하더라도, 분할 화면 모드, ChromeOS의 데스크톱 스타일 창, 폴더블 디바이스의 접힘/펼침 등으로 인해 앱에 할당된 화면 크기가 달라질 수 있습니다.

 

Compose의 ConstraintLayout

 

ConstraintLayout은 화면에 다른 컴포저블을 기준으로 컴포저블을 배치할 수 있는 레이아웃입니다. 여러 중첩된 Row, Column, Box, 맞춤 레이아웃 요소 대신 사용할 수 있습니다. ConstraintLayout은 더 복잡한 정렬 요구사항이 있는 더 큰 레이아웃을 구현할 때 유용합니다.

 

다음 시나리오일때 ConstraintLayout을 사용하는 것이 좋다.

  • 코드 가독성 개선을 위해 화면에 요소를 배치하는 여러 Column  Row를 중첩하지 않습니다.
  • 다른 컴포저블을 기준으로 컴포저블을 배치하거나 가이드라인, 배리어, 체인을 기반으로 컴포저블을 배치합니다.
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }

        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.layoutId("button")
            ) {
                Text("Button")
            }

            Text("Text", Modifier.layoutId("text"))
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin = margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

 

배리어 ( Barrier ) : 레이아웃에서 서로 겹치지 않도록 특정 UI 요소들 사이에 공간을 설정하는 역할을 합니다.

ConstraintLayout {
    val (text1, text2, button) = createRefs()
    val barrier = createEndBarrier(text1, text2)

    Text(
        text = "짧은 텍스트",
        Modifier.constrainAs(text1) {
            start.linkTo(parent.start)
            top.linkTo(parent.top)
        }
    )

    Text(
        text = "좀 더 긴 텍스트",
        Modifier.constrainAs(text2) {
            start.linkTo(parent.start)
            top.linkTo(text1.bottom)
        }
    )

    Button(
        onClick = { /* 클릭 동작 */ },
        Modifier.constrainAs(button) {
            start.linkTo(barrier) // 배리어의 끝에 맞춰 버튼 위치 설정
            top.linkTo(text2.bottom)
        }
    )
}

 

체인 ( Chain )  : ConstraintLayout을 사용하여 여러 UI 요소들을 하나의 축에 따라 정렬하고, 간격이나 분배 방식을 제어하는 기능입니다.

ConstraintLayout(modifier = Modifier.fillMaxSize()) {
    val (button1, button2, button3) = createRefs()

    Button(
        onClick = { /* 클릭 동작 */ },
        modifier = Modifier.constrainAs(button1) {
            top.linkTo(parent.top, margin = 16.dp)
        }
    ) {
        Text("Button 1")
    }

    Button(
        onClick = { /* 클릭 동작 */ },
        modifier = Modifier.constrainAs(button2) {
            top.linkTo(parent.top, margin = 16.dp)
        }
    ) {
        Text("Button 2")
    }

    Button(
        onClick = { /* 클릭 동작 */ },
        modifier = Modifier.constrainAs(button3) {
            top.linkTo(parent.top, margin = 16.dp)
        }
    ) {
        Text("Button 3")
    }

    createHorizontalChain(button1, button2, button3, chainStyle = ChainStyle.Spread)
}

'Android' 카테고리의 다른 글

[Android/Kotlin] Compose Lazy lists  (2) 2024.11.13
[Android/Kotlin]Compose Dialog  (0) 2024.11.12
[Android/Kotlin] Compose App Bar  (0) 2024.11.10
[Android/Kotlin] Compose Layout(1)  (5) 2024.11.09
[Android/Kotlin] Compose Layout  (2) 2024.11.08