[Android] Jetpack Compose 기초 - 1

안세홍·2024년 8월 27일
post-thumbnail

먼저 Jetpasck Compose 가 무엇인지 알아봤습니다.

Jetpack Compose는 UI 개발을 간소화하기 위해 설계된 최신 툴킷입니다. 반응형 프로그래밍 모델을 Kotlin 프로그래밍 언어의 간결함 및 사용 편의성과 결합합니다. 이는 완전히 선언적인 접근 방식으로, 데이터를 UI 계층 구조로 변환하는 일련의 함수를 호출하여 UI를 설명합니다. 기본 데이터가 변경되면 프레임워크가 이러한 함수를 자동으로 다시 실행하여 UI 계층 구조를 업데이트합니다.

Compose 앱은 구성 가능한 함수로 구성됩니다. 구성 가능한 함수는 @Composable 이라고 표시된 일반 함수이며 다른 구성 가능한 함수를 호출할 수 있습니다. 새로운 UI 구성요소를 만들기 위해서는 함수만 있으면 됩니다. 주석은 지속적으로 UI를 업데이트하고 유지관리하기 위해 함수에 특수 지원을 추가하도록 Compose에 알려주는 역할을 합니다. Compose를 사용하면 코드를 작은 청크로 구성할 수 있습니다. 대개 구성 가능한 함수를 줄여서 '컴포저블'이라고 합니다.

재사용이 가능한 작은 컴포저블을 만들면 앱에 사용하는 UI 요소의 라이브러리를 쉽게 빌드할 수 있습니다. 각 요소는 화면의 한 부분을 담당하며 독립적으로 수정할 수 있습니다.

위 내용은 Android 공식 문서에서 다시 확인해보실 수 있습니다.

Compose는 선언형(Declarative) UI 프레임워크로서, ‘무엇을 보여줄 것인가?’를 기술하고 구현의 세부 사항은 시스템이나 프레임워크에 위임하는 선언형 프로그래밍 방식을 따릅니다. Kotlin을 사용해 컴포저블(Composable) 함수라는 독립적인 단위로 UI를 구성하므로 직관적이며 재사용성이 높은 코드를 만들 수 있습니다. 또한, 복잡한 UI도 이 함수들을 조합해 만들 수 있으므로 개발 시간도 단축시켜줍니다.

위와 같은 내용을 보며 이전 Android를 개발하였을 때는 xml 파일을 통해 UI 를 구성했던걸로 기억했는데 새로 바뀐 것을 보고 스터디 후 블로그에 그에 대한 내용을 기록하게 되었습니다.

프로젝트를 시작하려면 Android Studio 를 열고 메뉴바에서 File > New > New Project를 선택합니다.

새 프로젝트의 경우 제공되는 템플릿에서 Empty Activity를 선택합니다.

어플명은 편하신 어플명을 선택하신 후,
Minimum SDK 는 API 21 ("Lollipop", Android 5.0) 을 선택합니다.
이후 Finish 를 클릭하시면 프로젝트가 생성됩니다.

동기화 하신 후 build.gradle.kts (Module :app) 파일에 들어갑니다.

Android BuildFeatures 내에서 compose 플래그를 true로 설정 블록은 Android 스튜디오에서 Compose 기능을 사용 설정합니다.

	buildFeatures {
        compose = true
    }

Compose BOM과 Compose 라이브러리 종속 항목의 하위 집합을 추가합니다. 다음 블록의 종속 항목을 추가해야 합니다.

  dependencies {
      implementation("androidx.core:core-ktx:1.13.1")
      implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4")
      implementation("androidx.activity:activity-compose:1.9.1")
      implementation(platform("androidx.compose:compose-bom:2024.08.00"))
      implementation("androidx.compose.ui:ui")
      implementation("androidx.compose.ui:ui-graphics")
      // Android Studio Preview support
      implementation("androidx.compose.ui:ui-tooling-preview")
      implementation("androidx.compose.foundation:foundation")

      // Material Design 3
      implementation("androidx.compose.material3:material3")
      testImplementation("junit:junit:4.13.2")
      androidTestImplementation("androidx.test.ext:junit:1.2.1")
      androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
      // compose
      androidTestImplementation(platform("androidx.compose:compose-bom:2024.08.00"))

      debugImplementation("androidx.compose.ui:ui-tooling")
      // UI Tests
      androidTestImplementation("androidx.compose.ui:ui-test-junit4")
      debugImplementation("androidx.compose.ui:ui-test-manifest")
      implementation("androidx.compose.material:material-icons-extended")
  }

기본적인 설정을 마치셨다면 MainActivity.kt 파일에 가서 확인해보시면 기본적으로 설정된 아래와 같은 화면이 나오게 됩니다.

참고 : Code 가 선택되어 있는 경우 미리보기가 표시되지 않을 수 있습니다. 미리보기를 보려면 Split 클릭하세요.

간단하게 Android 스튜디오에서 생성한 Compose 관련 클래스 및 메서드를 살펴보겠습니다.

구성 가능한 함수

구성 가능한 함수는 @Composable이라는 주석이 달린 일반 함수입니다. 이렇게 하면 함수가 내부에서 다른 @Composable 함수를 호출할 수 있습니다. Greeting 함수를 @Composable로 어떻게 표시하는지 확인할 수 있습니다. 이 함수는 지정된 입력(String)을 표시하는 UI 계층 구조를 생성합니다. Text는 라이브러리에서 제공하는 구성 가능한 함수입니다.

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

Android 앱의 Compose

Compose를 사용하면 Activity가 Android 앱의 진입점으로 유지됩니다. 이 프로젝트에서는 AndroidManifest.xml 파일에 지정된 대로 사용자가 앱을 열 때 MainActivity가 실행됩니다. setContent를 사용하여 레이아웃을 정의하지만, 기존 뷰 시스템에서 하던 것처럼 XML 파일을 사용하는 대신 이 함수에서 구성 가능한 함수를 호출합니다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            BasicsCodelabTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                  modifier = Modifier.fillMaxSize(),
                  color = MaterialTheme.colorScheme.background
                ) {
                    Greeting("Android")
                }
            }
        }
    }
}

BasicsCodelabTheme은 구성 가능한 함수의 스타일을 지정하는 방법입니다. 이에 관한 자세한 내용은 앱 테마 설정 섹션을 참고하세요. 텍스트가 화면에 어떻게 표시되는지 확인하려면 에뮬레이터나 기기에서 앱을 실행하거나 Android 스튜디오 미리보기를 사용하면 됩니다.

Android 스튜디오 미리보기를 사용하려면 매개변수가 없는 구성 가능한 함수 또는 기본 매개변수를 포함하는 함수를 @Preview 주석으로 표시하고 프로젝트를 빌드하기만 하면 됩니다. MainActivity.kt 파일에 이미 Preview Composable 함수가 있는 것을 볼 수 있습니다. 동일한 파일에 미리보기를 여러 개 만들고 이름을 지정할 수 있습니다.

@Preview(showBackground = true, name = "Text preview")
@Composable
fun GreetingPreview() {
    BasicsCodelabTheme {
        Greeting(name = "Android")
    }
}

UI 조정

먼저 Greeting에 다른 배경 색상을 설정해 보겠습니다. 이렇게 하려면 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
        )
    }
}

Surface 내부에 중첩된 구성요소는 배경 색상 위에 그려집니다.

참고 :
중요한 사항을 놓쳤을 수도 있습니다. 지금 보이는 텍스트는 흰색입니다. 언제 텍스트를 흰색으로 정의했나요?

정의하지 않았습니다. androidx.compose.material3.Surface와 같은 Material 구성요소는 앱에 넣고자 하는 공통 기능(예: 텍스트에 적절한 색상 선택)을 처리하여 더 나은 환경을 만들도록 빌드됩니다. Material은 대부분의 앱에 공통으로 적용되는 적절한 기본값과 패턴을 제공하므로 유연성이 낮은 편입니다. Compose의 Material 구성요소는 androidx.compose.foundation의 다른 기본 구성요소를 기반으로 빌드되며 이러한 구성요소는 더 많은 유연성이 필요한 경우 앱 구성요소에서 액세스할 수도 있습니다.

이 경우, Surface는 배경이 primary 색상으로 설정될 때 배경 위의 모든 텍스트가 테마에도 정의된 onPrimary 색상을 사용해야 한다는 것을 인식합니다. 이에 관한 자세한 내용은 앱 테마 설정 섹션을 참고하세요.

Modifier

SurfaceText와 같은 대부분의 Compose UI 요소는 modifier 매개변수를 선택적으로 허용합니다. 수정자는 상위 요소 레이아웃 내에서 UI 요소가 배치되고 표시되고 동작하는 방식을 UI 요소에 알려줍니다. 이미 알겠지만 Greeting 컴포저블에는 이미 기본 수정자가 있으며 이 기본 수정자는 Text에 전달됩니다.

예를 들어, padding 수정자는 수정자가 데코레이션하는 요소 주변의 공간을 나타냅니다. Modifier.padding() 으로 패딩 수정자를 만들 수 있습니다. 체이닝을 통해 여러 수정자를 추가할 수도 있으므로 여기서는 패딩 수정자를 기본 수정자 modifier.padding(24.dp) 에 추가할 수 있습니다.

이제 화면의 Text 에 패딩을 추가합니다.

import androidx.compose.foundation.layout.padding
import androidx.compose.ui.unit.dp
// ...

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Surface(color = MaterialTheme.colorScheme.primary) {
        Text(
            text = "Hello $name!",
            modifier = modifier.padding(24.dp)
        )
    }
}

컴포저블 재사용

UI에 추가하는 구성요소가 많을수록 생성되는 중첩 레벨이 더 많아집니다. 함수가 매우 커지면 가독성에 영향을 줄 수 있습니다. 재사용할 수 있는 작은 구성요소를 만들면 앱에서 사용하는 UI 요소의 라이브러리를 쉽게 만들 수 있습니다. 각 요소는 화면의 작은 부분을 담당하며 독립적으로 수정할 수 있습니다.

함수는 기본적으로 빈 수정자가 할당되는 수정자 매개변수를 포함하는 것이 좋습니다. 함수 내에서 호출하는 첫 번째 컴포저블로 이 수정자를 전달합니다. 이렇게 하면 호출 사이트가 구성 가능한 함수 외부에서 레이아웃 안내와 동작을 조정할 수 있습니다.

인사말이 포함된 MyApp이라는 컴포저블을 만듭니다.

@Composable
fun MyApp(modifier: Modifier = Modifier) {
    Surface(
        modifier = modifier,
        color = MaterialTheme.colorScheme.background
    ) {
        Greeting("Android")
    }
}
...
@Preview(showBackground = true, name = "Text preview")
@Composable
fun GreetingPreview() {
    AndroidComposeStudyTheme {
        MyApp()
    }
}

전체 코드입니다.

...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            AndroidComposeStudyTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

@Composable
fun MyApp(modifier: Modifier = Modifier) {
    Surface(
        modifier = modifier,
        color = MaterialTheme.colorScheme.background
    ) {
        Greeting("Android")
    }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Surface(color = MaterialTheme.colorScheme.primary) {
        Text(
            text = "Hello $name!",
            modifier = modifier.padding(24.dp)
        )
    }
}

@Preview(showBackground = true, name = "Text preview")
@Composable
fun GreetingPreview() {
    AndroidComposeStudyTheme {
        MyApp()
    }
}

열과 행 만들기

이러한 요소는 컴포저블 콘텐츠를 사용하는 구성 가능한 함수이므로 내부에 항목을 배치할 수 있습니다. 예를 들어 Column 내부의 각 하위 요소는 세로로 배치됩니다.

import androidx.compose.foundation.layout.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)
        }
    }
}

Compose와 Kotlin

구성 가능한 함수는 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)
        }
    }
}

아직 크기를 설정하지 않았거나 컴포저블의 크기에 제약사항을 추가하지 않았으므로 각 행은 사용할 수 있는 최소 공간을 차지하며 미리보기도 같은 작업을 실행합니다. 미리보기를 변경하여 소형 스마트폰의 일반적인 너비인 320dp로 에뮬레이션해 보겠습니다. 다음과 같이 @Preview 주석에 widthDp 매개변수를 추가합니다.

@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
    BasicsCodelabTheme {
        MyApp()
    }
}

수정자는 Compose에서 광범위하게 사용되므로 좀 더 고급 과정의 실습을 해보겠습니다. fillMaxWidthpadding 수정자를 사용하여 다음 레이아웃을 복제해 보세요.

import androidx.compose.foundation.layout.fillMaxWidth

@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)
        }
    }
}

@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.fillMaxWidth().padding(24.dp)) {
            Text(text = "Hello ")
            Text(text = name)
        }
    }
}

버튼 추가

다음 단계에서는 Greeting을 펼치는 클릭 가능한 요소를 추가하므로 이 버튼을 먼저 추가해야 합니다. 목표는 다음과 같은 레이아웃을 만드는 것입니다.

Button은 material3 패키지에서 제공하는 컴포저블로, 컴포저블을 마지막 인수로 사용합니다. 후행 람다는 괄호 밖으로 이동할 수 있으므로 모든 콘텐츠를 버튼에 하위 요소로 추가할 수 있습니다. 예를 들어, 다음과 같이 Text를 추가할 수 있습니다.

// Don't copy yet
Button(
    onClick = { } // You'll learn about this callback later
) {
    Text("Show less")
}

이렇게 하려면 컴포저블을 행 끝에 배치하는 방법을 알아야 합니다. alignEnd 수정자가 없으므로 시작 시 컴포저블에 약간의 weight을 제공합니다. weight 수정자는 요소를 유연하게 만들기 위해 가중치가 없는 다른 요소(유연성 부족이라고 함)를 효과적으로 밀어내어 요소의 사용 가능한 모든 공간을 채웁니다. 이 수정자는 fillMaxWidth 수정자와 중복되기도 합니다.

이제 버튼을 추가하고 이전 이미지에 표시된 것처럼 버튼을 배치합니다.

import androidx.compose.foundation.layout.Row
import androidx.compose.material3.ElevatedButton
// ...

@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("Show more")
            }
        }
    }
}

참고 - https://developer.android.com/codelabs/jetpack-compose-basics

profile
나만의 개발 일기

0개의 댓글