Compose에 대해 기초학습을 진행하였다.
기존 XML기반 UI개발방식과 달리 Kotlin코드를 사용하여 UI를 직접 정의할 수 있는 최신 툴킷이다.
첫 액티비티를 생성하면 기본으로 나오는 화면이다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}
Surface -> Compose에서 사용하는 기본 컨테이너로, 배경색상과 모양을 설정할 수 있다. 여기서는 fillMaxSize()를 사용해서 전체 화면을 채우고, 테마에서 정의한 배경색상을 사용해준다.
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greeting("Android")
}
}
@Composable -> 이 함수가 Composable함수임을 나타낸다. UI요소를 정의하는 함수이다.
modifier -> UI 요소의 크기, 레이아웃, 스타일 등을 지정할 때 사용하는 매개변수다. 기본값으로 Modifier를 사용한다.
@Preview -> 이 함수가 미리보기 함수임을 나타낸다.
showBackground = true -> 미리보기 시 배경화면을 표시할지 여부를 결정하는 매개변수다. true설정시 배경화면을 볼 수 있다.

Greeting에 다른 배경색을 설정하는법이다.
기존 Greeting
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
여기서 Text컴포저블을 Surface로 래핑하면 된다.
Surface는 색상을 사용하므로 MaterialTheme.colorScheme.primary를 사용한다.
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
}

여기서 중요한건 배경색을 바꿧을뿐인데 알아서 그에맞춰 글자색까지 바뀐거다.
androidx.compose.material3.Surface와 같은 Material 구성요소는 앱에 넣고자 하는 공통 기능(예: 텍스트에 적절한 색상 선택)을 처리하여 더 나은 환경을 만들도록 빌드된다고 한다.
Surface및 Text와 같은 대부분의 Compose UI요소는 modifier매개변수를 선택적으로 허용한다. 예를들어 padding수정자는 수정자가 데코레이션 하는 요소 주변의 공간을 나타낸다. Modifier.padding()으로 패딩 수정자를 만들 수 있다.
위 화면의 padding을 추가보자.
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Text(
text = "Hello $name!",
modifier = modifier.padding(24.dp)
)
}
}
결과 -> 패딩이 잘 들어갔다.

Hello, Android를 2줄로 만들려면 Column을 사용해주면 된다.
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Column(modifier = modifier.padding(24.dp)) {
Text(text = "Hello")
Text(text = name)
}
}
}
이렇게 Surface안에 Column을 넣고 두개의 Text로 나눠주면 된다.

@Composable
fun MyApp(modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
이렇게 MyApp을 새롭게 만들어주고 onCreate에서
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
}
}
}
이렇게 사용해주면 가독성을 높여줄 수 있다.
구성 가능한 함수는 Kotlin의 다른 함수처럼 사용할 수 있다. 이는 UI가 표시되는 방식에 영향을 주는 구문을 추가할 수 있으므로 매우 강력한 UI를 제작할 수 있게 해준다.
예를 들어, for 루프를 사용하여 Column에 요소를 추가할 수 있다.
@Composable
fun MyApp(
modifier: Modifier = Modifier,
names: List<String> = listOf("World", "Compose")
) {
Column(modifier) {
for (name in names) {
Greeting(name = name)
}
}
}
여기서 미리보기의 크기를 소형 스마트폰의 일반적인 크기인 320으로 맞춰주고 보면
Preview에서
@Preview(showBackground = true, widthDp = 320)
@Composable
fun MyApp(
modifier: Modifier = Modifier,
names: List<String> = listOf("World", "Compose")
){
Column(modifier = modifier.padding(vertical = 4.dp)) {
for(name in names){
Greeting(name = name)
}
}
}
MyApp의 Column에서 padding 설정을 vertical로 설정해주면 아래사진처럼 위아래 여백이 생긴다.

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Column(modifier = modifier.padding(24.dp)) {
Text(text = "Hello ")
Text(text = name)
}
}
}
그리고 Greeting함수의 Surface에 modifier에 padding설정을 추가로 해준다면 아래사진처럼 여백이 늘어난다.

여기서 추가로 Column안의 modifier를 아래와같이 수정해준다면 fillMaxWidth()가 적용되서 가로로 가득 채워진다.
modifier = Modifier.fillMaxWidth().padding(24.dp)
Button은 material3 패키지에서 제공하는 컴포저블로, 컴포저블을 마지막 인수로 사용한다. 후행 람다는 괄호 밖으로 이동할 수 있으므로 모든 콘텐츠를 버튼에 하위 요소로 추가할 수 있다.
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)){
Column(modifier = Modifier.weight(1f)) {
Text(text = "Hello")
Text(text = name)
}
ElevatedButton(
onClick = { /*TODO*/ }
) {
Text(text = "Show more")
}
}
}
}
이렇게 기존 Column에 있던 padding속성을 Row로 빼주고, Column에 weight설정을 해준다. weight 수정자는 요소를 유연하게 만들기 위해 가중치가 없는 요소들을 효과적으로 밀어내서 요소의 사용 가능한 모든 공간을 채운다.

여기서 버튼을 사용자 변경사항에 반응하여 화면과 상호작용할 수 있다.
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val expanded = remember { mutableStateOf(false) }
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier.weight(1f)) {
Text(text = "Hello ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
val expanded = remember { mutableStateOf(false) } 를 사용해서 버튼의 클릭 상태를 기억하고 저장한다.
onClick에서 expanded의 value값을 클릭할때마다 바꿔주고, 그에따른 Text를 다르게 해준다면

버튼 클릭시 아래에 내용을 펼쳐보려고 한다.
Greeting안에 extraPadding을 만들어주고 Column안에 padding을 추가로 넣어주면 버튼 클릭이 된다면 bottom padding이 늘어나게 된다.
val extraPadding = if (expanded.value) 48.dp else 0.dp
...
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding))
구성 가능한 함수에서 여러 함수가 읽거나 수정하는 상태는 공통의 상위 항목에 위치해야 한다. 이 프로세스를 상태 호스팅이라고 한다. 상태를 호스팅할 수 있게 만들면 상태가 중복되지 않고 버그가 발생하는 것을 방지할 수 있으며 컴포저블을 재사용할 수 있고 훨씬 쉽게 테스트할 수 있다.
@Composable
fun OnboardingScreen(
onContinueClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = onContinueClicked
) {
Text("Continue")
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
ComposeReviewTheme {
OnboardingScreen( onContinueClicked = {})
}
}
onboardingScreen이라는 Composable함수를 추가로 만들고 Preview도 추가로 만들어서 볼 수 있게 해준다.
shouldShowOnboarding은 =대신 by를 사용해서 매번 .value를 사용하지 않도록 해준다.
추가로 MyApp을 Greetings로 나눠준다.
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by remember { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen( onContinueClicked = {shouldShowOnboarding = false})
} else {
Greetings()
}
}
}
@Composable
fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = listOf("World", "Compose")
){
Column(modifier = modifier.padding(vertical = 4.dp)) {
for(name in names){
Greeting(name = name)
}
}
}
버튼을 클릭하면 shouldShowOnboarding=false가 되지만 아직 어디에서도 이 상태를 읽고있지 않다.
이제 시작시 이 화면을 보여주고 사용자가 continue를 누르면 숨겨줄거다.

지금까지 Column에 두 개의 인사말을 표시했다. 하지만 수천 개의 인사말을 처리하려면?
names: List<String> = List(1000) { "$it" }
이렇게 리스트에 해당값을 넣어주면 된다.
스크롤이 가능한 열을 표시하기 위해서는 그냥 Column이 아닌 LazyColumn을 사용해야한다.
@Composable
fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = List(1000) {"$it"}
){
LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
items(items = names){ name ->
Greeting(name = name)
}
}
}
이렇게 List를 원하는 개수만큼 만들고, Column을 LazyColumn으로 바꾸고, 안에 내용을 items로 바꿔서 만들어준다면

이렇게 1000개가 생성된다.
따로 리사이클러뷰를 만들필요없이 스크롤가능한 리스트가 생성이 가능하다.
기기에서 앱을 실행하고 버튼을 클릭한 다음 회전하면 온보딩 화면이 다시 표기된다. remember함수는 컴포저블이 컴포지션에 유지되는 동안에만 작동한다. 기기를 회전하면 전체 활동이 다시 시작되므로 모든 상태가 손실된다.
remember를 사용하는 대신 rememberSaveable을 사용하면 상태가 유지된다.
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
Compose에서는 여러 가지 방법으로 UI에 애니메이션을 지정할 수 있다. 간단한 애니메이션을 위한 상위 수준의 API에서 전체 제어 및 복잡한 전환을 위한 하위 수준의 메서드까지 다양한 방법이 있다.
참조문서
이를 위해 animateDpAsState 컴포저블을 사용한다다. 이 컴포저블은 애니메이션이 완료될 때까지 애니메이션에 의해 객체의 value가 계속 업데이트되는 상태 객체를 반환한다. 유형이 Dp인 '목표 값'을 사용.
펼쳐진 상태에 따라 달라지는 extraPadding을 만들고 애니메이션을 적용한다.
val extraPadding by animateDpAsState(
if(expanded) 48.dp else 0.dp
)
Greeting함수에서 extraPadding을 animateDpAsState로 설정해주면 버튼 클릭시 bottomPadding이 더 자연스럽게 늘어나는 애니메이션이 적용된다.
animateDpAsState는 애니메이션을 맞춤설정할 수 있는 animationSpec 매개변수를 선택적으로 사용한다. 스프링 기반의 애니메이션 추가와 같이 더 재미있는 작업을 할 수 있다.
바로위에 만든 extraPadding대신
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
이걸 사용하고, Column부분에 extraPadding뒤에 .coerceAtLeast(0.dp)를 붙여주면 더 생동감있는 애니메이션이 적용된다.
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp)))
지금까지 컴포저블의 스타일을 지정하지 않았지만 어두운 모드 지원을 비롯한 적절한 기본값을 얻었다. 이제 BasicsCodelabTheme과 MaterialTheme가 무엇인지 살펴보겠다.
ui/theme/Theme.kt 파일을 열면 BasicsCodelabTheme이 구현에서 MaterialTheme을 사용하는 것을 확인할 수 있다.
여기서 MaterialTheme는 3가지 속성을 제공하는데 colorScheme, typography, shapes이다.
애플리케이션의 색상 팔레트를 정의한다. 팔레트는 기본색상(primary), 보조 색상(secondary), 배경 색상(background), 표면 색상(surface) 등 다양한 색상 속성으로 구성된다.
애플리케이션의 텍스트 스타일을 정의한다. 여기에는 폰트 패밀리, 폰트 크기, 폰트 가중치 등 텍스트 관련 속성이 포함된다.
Shapes는 애플리케이션의 UI 요소들의 모양(모서리 반경 등)을 정의한다. 예를 들어, 버튼, 카드, 시트 등의 모서리 반경을 설정할 수 있습니다.
Text(text = name, style = MaterialTheme.typography.headlineMedium)
여기서 style을 위와같이 typography를 사용해서 추가해주면 text의 글씨가 style에맞게 커진다.
font도 바꿀수있다.
Text(
text = name,
style = MaterialTheme.typography.headlineMedium.copy(
ontWeight = FontWeight.ExtraBold
))
@Preview(
showBackground = true,
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES,
name = "GreetingPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
ComposeReviewTheme {
Greetings()
}
}
기존 Preview위에 uiMode를 포함한 새로운 Preview를 만들어준다.