경험의 기록

2021.08.12 - [안드로이드/Jetpack-Compose] - [Android] Jetpack Compose 제트팩 컴포즈 사용해보기 - (2) 기초 사용법

 

[Android] Jetpack Compose 제트팩 컴포즈 사용해보기 - (2) 기초 사용법

2021.08.10 - [안드로이드/Jetpack-Compose] - [Android] Jetpack Compose 제트팩 컴포즈 사용해보기 - (1) 개념과 구조 [Android] Jetpack Compose 제트팩 컴포즈 사용해보기 - (1) 개념과 구조 https://android-..

hanyeop.tistory.com

에서 이어지는 글입니다.

 

이번엔 코드랩과 구글문서를 참고하여 레이아웃에 대해 알아보려고 한다.

 

이 글은 Google 공식문서를 기반으로 작성되었습니다.


Modifiers

저번에 사용해본 Modifiers를 통해 컴포저블을 꾸밀 수 있다. 동작, 모양을 변경하고, 접근성 레이블과 같은 정보를 추가하고, 사용자 입력을 처리하거나, 클릭 가능, 스크롤 가능, 드래그 가능 또는 확대/축소 가능을 만드는 것과 같은 고급 상호 작용을 추가할 수도 있다. 수정자는 일반 Kotlin 객체이며, 변수에 할당하고 재사용할 수 있다.

 

즉 쉽게 말해서, Modifiers를 사용하여 UI를 꾸미고, 상호작용 할 수 있다.

위와 유사한 레이아웃을 만들어보자.

 

@Composable
fun PhotographerCard() {
    Column {
        Text("안드로이드", fontWeight = FontWeight.Bold)
        // LocalContentAlpha is defining opacity level of its children
        CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
            Text("3 minutes ago", style = MaterialTheme.typography.body2)
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    ComposeLayoutTheme { // 프로젝트명 Theme
        PhotographerCard()
    }
}

PhotographerCard를 정의하고 Preview화면에서 확인해본다.

 

여기서 CompositionLocalProvider로 묶어주고 LocalContentAlpha provides ContentAlpha.medium 를 통하여 투명도를 설정해주면 묶인 composables 들에 전부 영향을 준다.

 

3 minutes ago가 투명하게 출력되는 것을 확인 할 수 있고

 

@Composable
fun PhotographerCard() {
    Column {
        Text("안드로이드", fontWeight = FontWeight.Bold)
        // LocalContentAlpha is defining opacity level of its children
        CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
            Text("3 minutes ago", style = MaterialTheme.typography.body2)
            Text("2 minutes ago", style = MaterialTheme.typography.body2)
            Text("1 minutes ago", style = MaterialTheme.typography.body2)
        }
    }
}

예를 들어 여러 텍스트들을 블럭내에 추가할 경우

 

전부 투명하게 출력되는 것을 확인할 수 있다.

 

또한 ContentAlpha 속성을 .disabled 로 변경하면

더 투명하게 출력되는 것을 확인할 수 있다.

 

@Composable
fun PhotographerCard() {
    Row {
        Surface(
            modifier = Modifier.size(50.dp),
            shape = CircleShape,
            color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f)
        ) {
            // Image goes here
        }

        Column {
            Text("안드로이드", fontWeight = FontWeight.Bold)
            // LocalContentAlpha is defining opacity level of its children
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text("3 minutes ago", style = MaterialTheme.typography.body2)
            }
        }
    }
}

이제 Surface를 활용하여

이미지가 로딩되기 전의 회색 화면을 만들어준다.

 

@Composable
fun PhotographerCard() {
    Row {
        Surface(
            modifier = Modifier.size(50.dp),
            shape = CircleShape,
            color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f)
        ) {
            // Image goes here
        }

        Column(
            modifier = Modifier
                .padding(start = 8.dp)
                .align(Alignment.CenterVertically)
        ) {
            Text("안드로이드", fontWeight = FontWeight.Bold)
            // LocalContentAlpha is defining opacity level of its children
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text("3 minutes ago", style = MaterialTheme.typography.body2)
            }
        }
    }
}

그리고 Column에 padding을 추가해주고, CenterVertically을 통해 수직 중앙 정렬을 해준다.

 

이미지와 텍스트의 간격이 벌어지고, 텍스트가 수직중앙정렬이 된 것을 확인할 수 있다.

 

@Composable
fun PhotographerCard(modifier: Modifier = Modifier) {
    Row(modifier) { ... }
}

❗ 기본적으로는 Composable 을 유연하게 만들기 위하여 파라미터로 modifier를 받게 하는 것이 좋다. 이렇게 만들어 modifier를 통해 Composable 들을 변경할 수 있도록 한다. 기본값을 빈 Modifier로 지정해놓았기 때문에 별도의 파라미터를 전달받지 않더라도 이상 없다.

 

 

Chain

modifier 는 여러 속성을 chain 으로 지정할 수 있는데,

순서에 따라 동작이 달라지기 때문에 순서를 잘 신경써야한다.

clickable을 통해 클릭효과를 정의해줄 수 있는데

예를들어 padding을 지정하고 클릭을 선언해주면

클릭 범위에 패딩이 포함되지 않지만

 

 

이렇게 클릭 이후에 선언해준 패딩은 클릭 범위에 포함된다.

 

최종 Composable

@Composable
fun PhotographerCard(modifier: Modifier = Modifier) {
    Row(
        modifier
            .padding(8.dp)
            .clip(RoundedCornerShape(4.dp))
            .background(color = MaterialTheme.colors.surface)
            .clickable(onClick = { /* Ignoring onClick */ })
            .padding(16.dp)

    ) {
        Surface(
            modifier = Modifier.size(50.dp),
            shape = CircleShape,
            color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f)
        ) {
            // Image goes here
        }

        Column(
            modifier = Modifier
                .padding(start = 8.dp)
                .align(Alignment.CenterVertically)
        ) {
            Text("안드로이드", fontWeight = FontWeight.Bold)
            // LocalContentAlpha is defining opacity level of its children
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text("3 minutes ago", style = MaterialTheme.typography.body2)
            }
        }
    }
}

이제 clip등을 통해 디자인을 다듬어준다.

 

 

 

Slot APIs

Compose 에서는 속성을 개발자가 정의하도록 하기 위하여 Slot 이란 것을 제공한다.

 

예를 들어 개발자가

Button(text = "Button")

단순한 버튼을 만들어 사용해야 할 수도 있고,

 

Button(
    text = "Button",
    icon: Icon? = myIcon,
    textStyle = TextStyle(...),
    spacingBetweenIconAndText = 4.dp,
    ...
)

여러가지 속성을 가진 버튼을 만들어 사용해야 할 경우가 있다.

 

위의 두 코드는 실제론 작동하지 않고 이론을 설명하기 위한 code이다.

이와같이 Slot이란 여러가지 속성들을 개발자가 정의하여 사용하도록 하기위해 제공하는, 빈공간이라고 보면 된다.

 

@Composable
fun Button(
    modifier: Modifier = Modifier,
    onClick: (() -> Unit)? = null,
    ...
    content: @Composable () -> Unit
)

Button의 API 코드를 보면 content가 람다식으로 정의 된 것을 확인할 수 있다. 이것이 슬롯이다.

 

@Composable
fun createButton(){
    Button(onClick = { /*TODO*/ }) {
        Row(){
            Image(painter = painterResource(id = R.drawable.ic_baseline_heart_broken_24), contentDescription = "")
            Text(
                "버튼",
                fontWeight = FontWeight.Bold,
                modifier = Modifier
                    .padding(4.dp)
                    .align(Alignment.CenterVertically)
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun buttonPreview(){
    ComposeLayoutTheme {
        createButton()
    }
}

이런식으로 Button 내부에 속성을 정의해줌으로써

 

원하는 속성을 사용할 수 있다.

이 Slots의 특징이 Compose가 강조하고 있는 재사용성을 더 향상시켜준다.

 

위와 같이 앱바같은 몇몇 Compose의 경우

여러개의 슬롯을 지원한다.

 

TopAppBar(
    title = {
        Text(text = "Page title", maxLines = 2)
    },
    navigationIcon = {
        Icon(myNavIcon)
    }
)

 

 

Scaffold

Scaffold는 기본으로 제공되는 Material Component 컴포저블 중 가장 높은 레벨의 컴포저블 이다.

안에 다양한 UI 구성요소 들을 넣을 수 있는 슬롯을 제공하여, 뼈대의 역할을 한다.

이를 활용하여 앱바, 텍스트 등이 들어간 기본적인 UI을 만들 수 있다.

 

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

기존의 Greeting 같은 텍스트만 존재하는 간단한 코드에서

 

@Composable
fun ScaffoldEx() {
    Scaffold { innerPadding ->
        Column(modifier = Modifier.padding(innerPadding)) {
            Text(text = "Hi there!")
            Text(text = "Thanks for going through the Layouts codelab")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun ScaffoldEPreview() {
    ComposeLayoutTheme { // 프로젝트명 Theme
        ScaffoldEx()
    }
}

Scaffold를 람다식으로 추가해주고 파라미터로 패딩을 받는다.

그리고 Column을 추가해서 텍스트들을 추가해본다.

 

기본적인 UI 구성을 확인할 수 있다.

 

@Composable
fun ScaffoldEx() {
    Scaffold { innerPadding ->
        BodyContent(Modifier.padding(innerPadding))
    }
}

@Composable
fun BodyContent(modifier: Modifier = Modifier) {
    Column(modifier = modifier) {
        Text(text = "Hi there!")
        Text(text = "Thanks for going through the Layouts codelab")
    }
}

이 코드도 재사용성을 향상시키기 위하여

별도의 Composable로 선언해주고 사용해준다.

 

TopAppbar

@Composable
fun ScaffoldEx() {
    Scaffold(topBar = {
        Text(
            text = "탑바",
            style = MaterialTheme.typography.h3
        )
    }) { innerPadding ->
        BodyContent(Modifier.padding(innerPadding))
    }
}

또한 Scaffold에서는 Appbar등을 배치하기 위하여 사용되는 공간인 topBar 슬롯을 람다형태로 제공한다.

위와같이 추가해주면

 

맨위에 탑바가 생성된 것을 확인할 수 있다.

 

 

@Composable
fun ScaffoldEx() {
    Scaffold(topBar = {
        TopAppBar(
            title = {
                Text(text = "앱바 타이틀")
            }
        )
    }) { innerPadding ->
        BodyContent(Modifier.padding(innerPadding))
    }
}

또한 Composable에서는 TopAppBar 를 기본으로 지원한다.

이제 topBar를 TopAppBar 로 변경하여 넣어준다.

 

 

@Composable
fun ScaffoldEx() {
    Scaffold(topBar = {
        TopAppBar(
            title = {
                Text(text = "앱바 타이틀")
            },
            actions = {
                IconButton(onClick = { /* doSomething() */ }) {
                    Icon(Icons.Filled.Favorite, contentDescription = null)
                }
            }
        )
    }) { innerPadding ->
        BodyContent(Modifier.padding(innerPadding))
    }
}

그리고 AppBar에 actions 속성을 사용하여 추가적인 동작을 할 수 있는 버튼을 생성할 수 있다.

아이콘은 기본으로 머테리얼 디자인에서 제공하는 아이콘을 사용한다.

 

dependencies {
  ...
  implementation "androidx.compose.material:material-icons-extended:$compose_version"
}

전체 머테리얼 아이콘을 사용하려면 위 코드를 dependencies에 별도로 추가해주면 된다.

 

이제 우리에게 익숙한 형태의 UI가 보여진다.

 

Placing modifiers

위에서 modifiers 를 인자로 받게 했기 때문에

 

@Composable
fun ScaffoldEx() {
    Scaffold(topBar = {
        TopAppBar(
            title = {
                Text(text = "앱바 타이틀")
            },
            actions = {
                IconButton(onClick = { /* doSomething() */ }) {
                    Icon(Icons.Filled.Favorite, contentDescription = null)
                }
            }
        )
    }) { innerPadding ->
        BodyContent(Modifier.padding(innerPadding).padding(8.dp))
    }
}

BodyContent를 사용하는 과정에서 패딩을 추가해줄 수 있다.

 

@Composable
fun BodyContent(modifier: Modifier = Modifier) {
    Column(modifier = modifier.padding(8.dp)) {
        Text(text = "Hi there!")
        Text(text = "Thanks for going through the Layouts codelab")
    }
}

아니면 직접 자신의 composable에서 패딩을 추가해줄수도 있다.

용도에 따라 다양하고 유연하게 사용할 수 있다.

 

Modifiers는 연속으로 Chain 할 수 있는데, 더이상 Chain으로 연결할 수 없다면 .then() 을 사용하여 계속 연결할 수 있다.

 

 


생각보다 내용이 길어져 여러 글로 나눠 작성하려고 한다.

 

코드는 깃허브에서 확인할 수 있다.

https://github.com/HanYeop/Jetpack-Compose/tree/master/ComposeLayout

 

GitHub - HanYeop/Jetpack-Compose: Jetpack Compose 사용해보기

Jetpack Compose 사용해보기. Contribute to HanYeop/Jetpack-Compose development by creating an account on GitHub.

github.com

 

 

 

 

 

참고

https://developer.android.com/jetpack/compose/layouts/basics?hl=ko

https://developer.android.com/codelabs/jetpack-compose-layouts?hl=ko#2 

 

반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading