Android
안드로이드 [Kotlin] - Jetpack Compose 코트랩 실습(1)
🤖 Play with Android 🤖
2023. 5. 7. 20:25
728x90
코드랩 실습 (Layout In Jetpack Compose)
시작하기
- 표준 레이아웃 컴포넌트들을 사용하여 다음과 같은 UI를 만들 수 있다.
Modifiers
- 검색창을 구현하려면 TextField라는 Material 구성 요소를 사용해야 한다.
- Compose Material 라이브러리에는 이 Material 구성요소의 구현인 TextField 라는 컴포저블이 존재한다.
@Composable
fun SearchBar(
modifier: Modifier = Modifier
) {
TextField(
value = "",
onValueChange = {},
modifier = modifier
.fillMaxWidth()
.heightIn(min = 56.dp)
)
}
- 검색창은 높이가 최소 56dp이고 너비가 상위 요소의 전체 너비를 차지한다.
- 높이는
heightIn
설정으로 최소 높이를 제한할 수 있다. (단, 사용자가 시스템 글꼴 크기를 확대하면 크기가 커질 수 있다.) - 너비로는
fillMaxWidth
Modifier 을 사용할 수 있다. - 여기서 하나의 Modifier는 너비에 영향을 주고 하나는 높이에 영향을 주기 때문에 순서는 중요하지 않다.
이제 조금 더 업그레이드를 해보자
@Composable
fun SearchBar(
modifier: Modifier = Modifier
) {
TextField(
value = "",
onValueChange = {},
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = null
)
},
colors = TextFieldDefaults.textFieldColors(
backgroundColor = MaterialTheme.colors.surface
),
placeholder = {
Text(stringResource(R.string.placeholder_search))
},
modifier = modifier
.fillMaxWidth()
.heightIn(min = 56.dp)
)
}
- 검색 아이콘을 추가한다.
- TextField에는 다른 컴포저블을 받는 매개변수 leadingIcon이 있다.
- 내부에는 Icon을 설정할 수 있다. 여기서는 Search 아이콘을 설정한다. 올바른 Compose Icon import를 사용해야 한다.
- 텍스트 필드의 배경색을 MaterialTheme의 surface 색으로 설정한다. TextFieldDefaults.textFieldColors를 사용하여 특정 색상을 재정의할 수 있다.
- 자리표시자 텍스트(EditText의 Hint 역할) 'Search'를 추가한다(문자열 리소스 R.string.placeholder_search).
Alignment
- 텍스트의 기준선과 이미지의 기준선 사이의 간격은 24dp 이다.
- 텍스트의 기준선이란 문자가 '놓여' 있는 선을 가리킵니다. 디자이너들은 주로 상단이나 하단이 아닌 기준선을 기준으로 텍스트 요소를 정렬한다.
- 이 컴포저블을 구현하려면 Image 및 Text 컴포저블이 필요하다.
- 이 두 가지 컴포저블을 세로 방향으로 배치하려면 Column에 포함해야 한다.
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
) {
Image(
painter = painterResource(R.drawable.photo_0),
contentDescription = null
)
Text(
text = stringResource(R.string.ab1_inversions)
)
}
}
- 이 이미지는 순전히 장식용이므로
contentDescription
을null
로 설정하였다.
이 이미지는 우리가 원하는대로 사각형이 아니다. 이미지가 너무 크고 원형이 아니다.
size
및clip
Modifier 와contentScale
매개변수를 사용하여 Image 컴포저블을 조정할 수 있다.- size 수정자는 앞 단계에서 본
fillMaxWidth
및heightIn
수정자와 마찬가지로 특정 크기에 맞게 컴포저블을 조정한다. - clip 수정자는 이와 달리 컴포저블의 모양을 조정한다. 이 수정자를 원하는 Shape으로 설정하면 컴포저블의 콘텐츠가 이 도형에 맞춰 잘른다.
이미지의 크기도 올바르게 조정해야 합니다. Image의 contentScale 매개변수를 사용하면 됩니다. 여러 가지 옵션이 있다.
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
) {
Image(
painter = painterResource(R.drawable.ab1_inversions),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(88.dp)
.clip(CircleShape)
)
Text(
text = stringResource(R.string.ab1_inversions)
)
}
}
Column
의 경우 하위 요소를 가로로 정렬할 방법을 정해야 한다.
- Start
- CenterHorizontally
- End
Row
의 경우 세로 정렬을 설정해야 한다.
- Top
- CenterVertically
- Bottom
Box
의 경우 가로 및 세로 정렬을 결합하여 사용한다.
- TopStart
- TopCenter
- TopEnd
- CenterStart
- Center
- CenterEnd
- BottomStart
- BottomCenter
- BottomEnd
컨테이너의 모든 하위 요소가 동일한 정렬 패턴을 따른다. align
수정자를 추가하여 단일 하위 요소의 동작을 재정의할 수 있다.
- 이 디자인에서는 텍스트가 가로로 가운데 정렬되어야 한다.
- 이렇게 하려면
Column
의horizontalAlignment
가 가로로 가운데 정렬되도록 설정한다.
import androidx.compose.ui.Alignment
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
Image(
//..
)
Text(
//..
)
}
}
- 이미지와 텍스트를 동적으로 만들고. 컴포저블 함수에 인수로 전달하도록 해보자.
@Composable
fun AlignYourBodyElement(
@DrawableRes drawable: Int,
@StringRes text: Int,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(88.dp)
.clip(CircleShape)
)
Text(
text = stringResource(text),
style = MaterialTheme.typography.h3,
modifier = Modifier.paddingFromBaseline(
top = 24.dp, bottom = 8.dp
)
)
}
}
}
- 리소스를 컴포저블 함수의 인자로 넣기 위해
@DrawableRes
,@StringRes
로 설정해준다.
Material Surface
- 이 컨테이너에는 화면의 배경과 다른 배경색이 적용되어 있다.
- 모서리는 둥글게 처리되었다.
- 디자이너가 색상을 지정하지 않았으므로 색상이 테마에 의해 정의될 것이라고 가정할 수 있다.
이러한 컨테이너로는 Material의 Surface
컴포저블을 사용한다.
- Surface는 요소를 감싸는 컨테이너와 같은 역할을 한다.
- 배경색이나 배경 테두리 등을 설정할 수 있다.
- Surface는 매개변수와 수정자를 설정하여 필요에 맞게 조정할 수 있다.
- 여기서는 모서리를 둥글게 처리해야 한다.
- 이를 위해
shape
매개변수를 사용할 수 있다.
@Composable
fun FavoriteCollectionCard(
@DrawableRes drawable: Int,
@StringRes text: Int,
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.medium,
modifier = modifier
) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.width(192.dp)
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(56.dp)
.clip(CircleShape)
)
Text(
text = stringResource(text),
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(horizontal = 16.dp)
)
}
}
}
Arrangements
- Compose에서는
LazyRow
컴포저블을 사용하여 다음과 같이 스크롤 가능한 행을 구현할 수 있다. - 목록 문서에서
LazyRow
및LazyColumn
과 같은 Lazy 목록에 관한 훨씬 자세한 정보를 확인할 수 있다. LazyRow
는 모든 요소를 동시에 렌더링하는 대신 화면에 표시되는 요소만 렌더링하여 앱의 성능을 유지한다.
@Composable
fun AlignYourBodyRow(
modifier: Modifier = Modifier
) {
LazyRow(
modifier = modifier
) {
items(alignYourBodyData) { item ->
AlignYourBodyElement(item.drawable, item.text)
}
}
}
- 디자인에서 보았던 간격은 적용되어 있지 않다.
- 간격을 구현하려면 배치에 대해 알아야 한다.
Row
Column
- 이러한 배치 외에도
Arrangement.spacedBy()
메서드를 사용하여 각 하위 컴포저블 사이에 고정된 공간을 추가할 수 있다.
@Composable
fun AlignYourBodyRow(
modifier: Modifier = Modifier
) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp), // 사진들 사이에 8dp의 공간을 주었다.
modifier = modifier
) {
items(alignYourBodyData) { item ->
AlignYourBodyElement(item.drawable, item.text)
}
}
}
- 이제 디자인은 다음과 같다.
Lazy 그리드
- 이 컴포저블에는 단일 행이 아닌 그리드가 필요하다.
- 그리드 요소 매핑을 더 효과적으로 지원하는 LazyHorizontalGrid를 사용해보자.
@Composable
fun FavoriteCollectionsGrid(
modifier: Modifier = Modifier
) {
LazyHorizontalGrid(
rows = GridCells.Fixed(2),
modifier = modifier
) {
items(favoriteCollectionsData) { item ->
FavoriteCollectionCard(item.drawable, item.text)
}
}
}
- 앞 단계의 LazyRow를 LazyHorizontalGrid로 바꾸기만 한 것이다.
- 우리가 원하는 결과가 나오지 않는다.
- 그리드가 상위 요소의 전체 공간을 자치한다.
- 즉, 즐겨찾는 컬렉션 카드가 세로 방향으로 지나치게 늘어져 있는 것을 확인할 수 있다.
그리드 셀이 올바른 크기와 셀 간 간격을 갖도록 컴포저블을 조정해야 한다.
@Composable
fun FavoriteCollectionsGrid(
modifier: Modifier = Modifier
) {
LazyHorizontalGrid(
rows = GridCells.Fixed(2),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier.height(120.dp)
) {
items(favoriteCollectionsData) { item ->
FavoriteCollectionCard(
drawable = item.drawable,
text = item.text,
modifier = Modifier.height(56.dp)
)
}
}
}
Slot API
- 각 섹션에는 제목과 슬롯이 존재한다.
- 섹션은 섹션에 따라 달라지는 콘텐츠로 동적으로 채울 수 있다.
이렇게 유연한 섹션 컨테이너를 구현하려면 슬롯 API를 사용해야 한다.
슬롯 기반 레이아웃은 개발자가 원하는 대로 채울 수 있도록 UI에 빈 공간을 남겨 둔다. 슬롯 기반 레이아웃을 사용하면 보다 유연한 레이아웃을 만들 수 있다.
@Composable
fun HomeSection(
@StringRes title: Int,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Column(modifier) {
Text(stringResource(title))
content()
}
}
@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun HomeSectionPreview() {
MySootheTheme {
HomeSection(R.string.align_your_body) {
AlignYourBodyRow()
}
}
}
content
라는 이름을 지정한 이 람다표현식이 마지막 매개변수로 있는데, 후행 람다 구문을 사용하여 구조화된 방식으로 Column에 콘텐츠를 삽입할 수 있다.- 자신만의 컴포저블을 만들 때, 더욱 재사용 가능하게 만들기 위해 Slots API 패턴을 사용할 수 있다.
스크롤
- 대부분의 기기는 전체 UI를 나타내기에 부족하다.
- 따라서 스크롤 동작을 추가해야한다.
- verticalScroll 혹은 horizontalScroll 수정자를 사용한다.
- 또한 스크롤 상태를 저장하는데애 rememberScrollState를 사용한다.
@Composable
fun HomeScreen(modifier: Modifier = Modifier) {
Column(
modifier
.verticalScroll(rememberScrollState()) // 수직 스크롤을 설정하면서 rememberScrllState 설정
.padding(vertical = 16.dp)
) {
Spacer(Modifier.height(16.dp))
SearchBar(Modifier.padding(horizontal = 16.dp))
HomeSection(title = R.string.align_your_body) {
AlignYourBodyRow()
}
HomeSection(title = R.string.favorite_collections) {
FavoriteCollectionsGrid()
}
Spacer(Modifier.height(16.dp))
}
}
Bottom Navigation
- 이 컴포저블은 처음부터 구현하지 않아도 된다.
- Compose Material 라이브러리의 일부인 BottomNavigation 컴포저블을 사용하면 된다.
- BottomNavigation 컴포저블 내에서 하나 이상의 BottomNavigationItem 요소를 추가하면 Material 라이브러리에 의해 자동으로 스타일이 지정되게 된다.
@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
BottomNavigation(modifier) {
BottomNavigationItem( // 첫 번째 아이템
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_home))
},
selected = true,
onClick = {}
)
BottomNavigationItem( // 두 번째 아이템
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_profile))
},
selected = false,
onClick = {}
)
}
}
- 먼저 backgroundColor 매개변수를 설정하여 하단 탐색의 배경색을 업데이트한다.
- Material 테마의 배경색을 사용하면 된다.
- 배경색을 설정하면 아이콘과 텍스트의 색상이 테마의 onBackground 색상으로 자동 조정되는 것을 확인할 수 있다.
@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
BottomNavigation(
backgroundColor = MaterialTheme.colors.background,
modifier = modifier
) {
BottomNavigationItem(
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_home))
},
selected = true,
onClick = {}
)
BottomNavigationItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_profile))
},
selected = false,
onClick = {}
)
}
}
Scaffold
- Scaffold는 앱을 위한 구성 가능한 최상위 수준의 컴포저블이다.
TopAppBar
,BottomAppBar
,FloatingActionButton
및Drawer
와 같은 일반적인 최상위 머테리얼 컴포넌트에 대한 Slot을 제공한다.- Scaffold를 사용하면 이러한 컴포넌트들을 배치하고 올바르게 동작하도록 한다.
컴포즈에는 앱을 만드는데 필요한 개발자가 사용할 수 있는 머테리얼 컴포넌트 컴포저블이 딸려있다. 가장 높은 수준의 컴포저블은 Scaffold다.
@Composable
fun MySootheApp() {
MySootheTheme {
Scaffold(
bottomBar = { SootheBottomNavigation() }
) { padding ->
HomeScreen(Modifier.padding(padding))
}
}
}
- Scaffold안에 위에서 만들었던 바텀바와 메인 홈 화면이 들어가는 것을 확인할 수 있다.