일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
- 백준
- Thread vs Coroutine
- 코틀린 에러 핸들링
- 코루틴 공식문서
- power menu
- Sticky Header RecyclerView
- Hilt 에러
- RecyclerView Sticky Header
- Android Compose Navigation
- 힐트
- Coroutine
- Android Custom View
- 백준 2615
- 코루틴 공식 문서
- Android Compose
- runCatching
- hilt
- 안드로이드 컴포즈
- viewmodel
- BOJ
- 코루틴
- android orbit
- 안드로이드 커스텀 뷰
- 안드로이드 무한 스크롤
- Kotlin Serialization
- android compose orbit
- Unsupported metadata version. Check that your Kotlin version is >= 1.0: java.lang.IllegalStateException
- power menu 라이브러리
- 스레드 vs 코루틴
- AAC ViewModel
- Today
- Total
Beeeam
컴포즈 공부 2일차 본문
컴포저블 함수
컴포즈로 사용자 인터페이스를 만들 때 사용하는 특수한 코틀린 함수이다. @composable 애너테이션을 붙여서 선언한다.
컴포저블 함수에서는 일반 코틀린 함수를 호출할 수 있지만 일반 코틀린 함수에서는 컴포저블 함수를 호출하는 것이 불가능하다.
상태 컴포저블, 비상태 컴포저블 함수로 구분된다. 여기서 상태는 앱 실행 중에 변경될 수 있는 모든 값들을 의미한다. ex) 텍스트 필드에 입력된 문자열, 슬라이더의 위치 값, 체크 박스의 현재 설정 상태 등등…
상태
컴포즈 같은 선언형 언어에서 “상태”는 시간에 따라 변경될 수 있는 값을 의미한다. 우리가 일반적으로 사용하는 표준 변수와 다를게 없다고 생각할 수 있지만 2가지 차이점이 있다.
- 컴포저블 함수의 상태 변수는 “기억”되어야 한다. 함수를 호출할 때마다 초기화되는 표준 변수와 달리 상태 변수는 이전에 호출되었을 때의 상태 값을 기억해야한다.
- 상태 변수의 변경은 컴포저블 함수 계층 트리 전체에 영향을 미친다.
상태 변수의 값은 MutableState 객체로 감싸야 한다. MutableState 객체는 옵저블 타입이라서 관찰이 가능하다.
var textState by remember { mutableStateOf("") }
상태 호이스팅
@Composable
fun MyTextField() {
var textState by remember { mutableStateOf("") }
val onTextChange = { text: String ->
textState = text
}
TextField(
value = textState,
onValueChange = { onTextChange(it) }
)
}
위의 MyTextField 컴포저블은 상태, 값 변경을 위한 람다, TextField 컴포저블을 포함하고 있다. 이 컴포저블은 재사용성이 떨어지는데 TextField를 통해 입력 받은 값을 다른 함수들로 전달할 수 없기 때문이다. 이런 문제를 해결하기 위해 “상태 호이스팅”을 사용한다.
상태 호이스팅을 사용하면 위의 컴포저블을 다음과 같이 정의할 수 있다.
@Composable
fun DemoScreen() {
var textState by rememberSaveable { mutableStateOf("") }
val onTextChange = { text: String ->
textState = text
}
MyTextField(textState = textState, onTextChange = onTextChange)
}
@Composable
fun MyTextField(textState: String, onTextChange: (String) -> Unit) {
TextField(
value = textState,
onValueChange = { onTextChange(it) }
)
}
환경 설정 변경을 통한 상태 저장
앱 실행 중 화면이 가로 ↔ 세로 돌아가거나 시스템 전체 폰트 설정 변경 등의 상황이 발생하면 액티비티를 삭제하고 다시 생성해야 한다. 이런 경우 remember 키워드로 저장한 상태들은 다 지워지는데 rememberSaveable 키워드를 사용하면 이를 막을 수 있다.
var textState by rememberSaveable { mutableStateOf("") }
CompositionLocal
위와 같은 계층의 컴포저블이 있을 때 Composable1에 정의되어 있는 상태가 Composable8에서만 필요한 경우가 있다. 그러면 이를 다른 컴포저블들을 통해서 전달해야한다. 이런 번거로움을 해결할 수 있는 것이 CompositionLocal이다. CompositionLocal을 사용하면 가장 높은 노드에 선언되어 있는 상태를 중간 노드를 거치지 않고도 사용할 수 있다.
CompositionLocal은 compositionLocalOf 함수 또는 staticCompositionLocalOf 함수를 호출해서 얻을 수 있다. compositionLocalOf 는 변경이 잦은 상태를 다룰 때, staticCompositionLocalOf 는 자주 변경되지 않는 상태를 다룰 때 사용하면 된다.
그 다음 CompositionLocalProvider를 사용하여 하위 컴포저블로 전달하면된다.
ex)
val LocalColor = staticCompositionLocalOf { Color(0xFFffdbcf) }
@Composable
fun Composable1() {
var color = if (isSystemInDarkTheme()) {
Color(0xFFa08d87)
} else {
Color(0xFFffdbcf)
}
Column {
Composable2()
// 하위 컴포저블에서 배경 색 상태를 사용할 수 있게 CompositionLocal 블록에서 실행
CompositionLocalProvider(LocalColor provides color) {
Composable3()
}
}
}
...
@Composable
fun Composable8() {
Text("Composable8", modifier = Modifier.background(LocalColor.current))
}
Slot API
@Composable
fun SlotDemo() {
Column {
Text(text = "Top")
Text(text = "Mid")
Text(text = "Bottom")
}
}
위와 같은 컴포저블은 3개의 Text를 하나의 Column으로 묶어서 화면에 보여준다. 근데 만약에 중간에 보여지는 컴포저블을 비워뒀다가 특정 시점에 보여주려면 어떻게 해야 할까?
이때 사용하는 것이 Slot API다.
@Composable
fun SlotDemo(
midContent: @Composable () -> Unit
) {
Column {
Text(text = "Top Content")
midContent()
Text(text = "Bottom Content")
}
}
@Composable
fun SlotButton() {
Button(onClick = { }) {
Text("Click Me!!")
}
}
ComposePracticeTheme {
SlotDemo(
midContent = { SlotButton() }
)
}
midContent 인자가 Slot이고 이를 통해 외부에서 인자로 받은 컴포저블을 화면에 보여줄 수 있다.
다음과 같이 컴포저블을 인자로 전달할 때 상태에 따라 조건문을 거쳐 각각 다른 컴포저블을 전달하게 되면 상태에 따라 다른 컴포저블을 화면에 보여줄 수 있다.
@Composable
fun MainScreen() {
...
ScreenContent(
linearSelected = linearSelected,
imageSelected = imageSelected,
onLinearClick = onLinearClick,
onImageClick = onImageClick,
titleContent = {
if (imageSelected) {
TitleImage(drawing = R.drawable.baseline_cloud_download_24)
} else {
Text(text = "DownLoading", modifier = Modifier.padding(30.dp))
}
},
progressContent = {
if (linearSelected) {
LinearProgressIndicator(Modifier.height(40.dp))
} else {
CircularProgressIndicator(Modifier.height(200.dp), strokeWidth = 18.dp)
}
}
)
}
@Composable
fun ScreenContent(
linearSelected: Boolean,
imageSelected: Boolean,
onLinearClick: (Boolean) -> Unit,
onImageClick: (Boolean) -> Unit,
titleContent: @Composable () -> Unit,
progressContent: @Composable () -> Unit,
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceBetween
) {
titleContent()
progressContent()
CheckBoxes(
linearSelected = linearSelected,
imageSelected = imageSelected,
onLinearClick = onLinearClick,
onImageClick = onImageClick
)
}
}
위의 코드를 실행하면 각 체크박스의 값을 클릭할 때마다 같은 위치에 뷰가 다른 뷰로 바뀌어 나타나는 것을 확인할 수 있다.
Modifier
모디파이어는 컴포저블에 적용할 수 있는 설정들을 저장한다. ex) 테두리, 패딩, 배경, 크기, 이벤트 핸들러 등…
val modifier = Modifier.border(width = 2.dp, color = Color.Black).padding(10.dp)
위와 같이 만들 수 있다. (border는 테두리, padding은 패딩)
만들어진 모디파이어를 컴포저블에 넣으면 해당 속성이 적용되는 것을 확인할 수 있다.
fun DemoScreen() {
val modifier = Modifier.border(width = 2.dp, color = Color.Black).padding(10.dp)
Text(
text = "Hello Compose",
modifier = modifier,
fontSize = 40.sp,
fontWeight = FontWeight.Bold
)
}
근데 모디파이어를 생성할 때 연결 순서도 중요하다. 만약 위의 코드의 모디파이어 연결 순서를 반대로 바꾸면 다음과 같은 결과를 확인할 수 있다.
커스텀 컴포저블을 만들 때 모디파이어를 지원하면 해당 컴포저블을 더 다양하게 설정할 수 있다.
커스텀 컴포저블에 모디파이어를 지원하게 할 때 주의해야 할 점은 모디파이어가 없어도 해당 함수가 실행될 수 있게 해야 한다는 점이다. 그래서 선택적인 파라미터로 만들어야 한다.
@Composable
fun customImage(drawing: Int, modifier: Modifier = Modifier) {
Image(
painter = painterResource(drawing),
modifier = modifier,
contentDescription = null
)
}
모디파이어를 선택적인 파라미터로 만들기 위해서 파라미터의 기본 값으로 빈 Modifier를 넣어서 구현하였다.
'Android' 카테고리의 다른 글
컴포즈 공부 4일차 (code lab) (0) | 2023.12.01 |
---|---|
컴포즈 공부 3일차 (1) | 2023.11.28 |
컴포즈 공부 1일차 (코드 형식 및 연습) (1) | 2023.11.26 |
RecyclerView 마스터하기 (1) | 2023.11.24 |
Android CountDownTimer (0) | 2023.11.07 |