2021.08.12 - [안드로이드/Jetpack-Compose] - [Android] Jetpack Compose 제트팩 컴포즈 사용해보기 - (2) 기초 사용법
에서 이어지는 글입니다.
이번엔 코드랩과 구글문서를 참고하여 레이아웃에 대해 알아보려고 한다.
이 글은 Google 공식문서를 기반으로 작성되었습니다.
저번에 사용해본 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로 지정해놓았기 때문에 별도의 파라미터를 전달받지 않더라도 이상 없다.
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등을 통해 디자인을 다듬어준다.
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는 기본으로 제공되는 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로 선언해주고 사용해준다.
@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가 보여진다.
위에서 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
참고
https://developer.android.com/jetpack/compose/layouts/basics?hl=ko
https://developer.android.com/codelabs/jetpack-compose-layouts?hl=ko#2