젯팩 컴포즈 공부를 시작하였습니다.
책 : 젯팩 컴포즈로 개발하는 안드로이드 UI
안드로이드 개발당시 처음에는 앱이 작았고 소수의 디바이스만 지원하면 됐기 때문에 문제없이 잘 동작했다.
점점 새 기능 및 화면 크기등 폼 팩터가 각각 다르게 출시되며 복잡성이 급격히 증가.
대부분의 문제는 명령적 접근 방식 Ui툴킷 방식에서 발생.
해결책은 패러다임을 전환하는 것.
- 웹 프레임워크 : 리액트 (선언적 접근 방식)
- 플러터 또는 SwiftUI도 뒤 따랐다.
잿팩 컴포즈는 구글이 안드로이드용으로 만든 선언적 UI 프레임워크다.
- 코틀린 전용
컴포저블 함수와 인사
@Composable
fun Welcome() {
Text(
text = stringResource(id = R.string.welcome),
style = MaterialTheme.typography.subtitle1
)
}
- 컴포저블 함수는 @Composable 어노테이션으로 쉽게 식별
- 반환 타입을 가질 필요가 없으며 UI 요소를 내보낸다.
- 일반적으로 다른 컴포저블 함수를 호출하는 것으로 끝난다.
Text()
Text() 는 기본 내장된 컴포즈 함수로 androidx.compose.material 패키지에 포함돼 있다.
임포트를 추가해야함.
import androidx.compose.material.Text
Text()나 다른 머터리얼 디자인 요소를 사용하려면 build.gradle 파일에
androidx.compose.material:material 의존성을 포함 해야 한다.
text, style이라는 두개의 매개변수로 이루어짐.
text : 어떠한 문구 보여줄지 명시
stringResource()는 미리 정의된 컴포저블 함수 → androidx.compose.ui.res 패키지에 포함
stringResource(id = R.string.hello, name), 으로도 가능
style : 텍스트 외형 변경
androidx.compose.ui.res 패키지에 포함
textAlign : 가로로 어떻게 배치할지 명시
열, 텍스트 필드, 버튼 사용
Row()
동일선상에 두 요소를 위치 시킬 때 사용
androidx.compose.foundation.layout 패키지에 포함
@Composable
fun TextAndButton(name: MutableState<String>,
nameEntered: MutableState<Boolean>) {
Row(modifier = Modifier.padding(top = 8.dp)) {
...
}
}
Row함수는 modifier라는 매개변수를 받는다.
변경자(Modifier)는 잿팩 컴포즈의 핵심 기술 : 컴포저블 함수의 외형과 행위에 영향을 준다.
padding(top = 8.dp) : 열 상단에 밀도 독립 픽셀(.dp) 값을 8만큼 설정
TextField()
TextField(
value = name.value,
onValueChange = {
name.value = it
},
placeholder = {
Text(text = stringResource(id = R.string.hint))
},
modifier = Modifier
.alignByBaseline()
.weight(1.0F),
singleLine = true,
keyboardOptions = KeyboardOptions(
autoCorrect = false,
capitalization = KeyboardCapitalization.Words,
),
keyboardActions = KeyboardActions(onAny = {
nameEntered.value = true
})
)
androidx.compose.material 패키지에 포함
Row에서 전달된 name과 nameEntered 변수를 사용. 이들 타입은 MutableState다.
- 변경할 수 있는 값
- .value 방식으로 값에 접근
.value가 값 제공 부분과, 값 변경 사항 전달받는 부분 두 부분에서 사용 되는 이유?
- 상태와 함께 자주 사용되기 때문 (?)
onValueChange - 사용자가 입력하거나 삭제하는 경우 호출
💡 재구성(Recomposition) (완전히 올바른 내용이 아님. 3장에 나옴)
- 컴포저블 함수를 다시 그리는 것으로 생각(지금은)
- MutableState가 이러한 타입에 해당.
- 값을 변경하면 TextField() 컴포저블 함수는 다시 그려지거나 다시 채색 된다.
alignByBaseline() : 특정 Row() 내부에서 다른 컴포지션 함수들의 기준선을 정렬할 수 있다.
placeHolder : 입력 전 보여줄 텍스트
singleLine : 여러 줄 입력할 수 있을지 제어
keyboardOptions, keyboardActions : 키보드의 동작
ex) 특정 동작을 수행하면 nameEntered.value가 true로 설정.
Button()
androidx.compose.material 패키지에 포함
Button(modifier = Modifier
.alignByBaseline()
.padding(8.dp),
onClick = {
nameEntered.value = true
}) {
Text(text = stringResource(id = R.string.done))
}
onClick : 버튼 클릭시 해야할 일 명시
인사말 출력
Hello() 함수는 위에 작성한 함수를 포함하는 컴포저블 함수인 Column()중 하나를 갖는 Box()함수를 반환한다.
Colomn
- 내부 요소를 세로로 정렬
- Box()와 마찬가지로 androidx.compose.foundation.layout 패키지에 포함
Box
- 한 개 이상의 자식 컴포저블 함수를 포함
@Composable
fun Hello() {
val name = remember { mutableStateOf("") }
val nameEntered = remember { mutableStateOf(false) }
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
if (nameEntered.value) {
Greeting(name.value)
} else {
Column(horizontalAlignment = Alignment.Centerhorizontally) {
Welcome()
TextAndButton(name, nameEntered)
}
}
}
}
remember, mutableStateOf : 상태를 생성하고 관리하는 역할
Welcome()함수는 상태를 갖지 않는다.(stateless)
- 재구성을 유발할 수 있는 모든 값은 한동안 그대로 유지
Hello()는 상태를 가진다.(stateful)
- name과 nameEntered변수를 사용하기 때문(remember, mutableStateOf)
- 이 변수들은 시간이 지남에 따라 값이 변함
- TextAndButton() 내부에서 변경됨.
- mutableStateStateOff()를 사용해 상태를 생성하고 remember를 사용해 상태를 기억한다.
- 상태를 다른 컴포저블 함수로 전달 하는데, 이를 상태 호이스팅(state hoisting)이라 부른다.
미리 보기 사용
컴포저블 함수에 @Preview 어노테이션 추가
- androidx.compose.ui.tooling.preview 패키지에 포함
- build.gradle에 androidx.compose.ui:ui-tooling-preview 의존성 필요
그냥 컴포저블 함수에 @preview를 추가하면 에러가 난다.
- 내부에서 필요한 매개변수와 함께 기존 함수를 호출해야 한다.
컴포저블 함수에 기본값을 추가할 수 있다.
@Composable
fun AltGreeting(name: String = "Jetpack Compose")
- 이렇게 하면 컴포저블 함수를 호출하는 방식이 변경된다.(매개변수를 넣지않아도 호출 됨)
@PreviewParameter를 사용하면 미리 보기에만 영향을 주면서 컴포저블 함수에 값을 전달할 수 있다.
- 하지만 이 기능은 새로운 클래스를 작성해야 한다.
class HelloProvider : PreviewParameterProvider<String> {
override val values: Sequence<String>
get() = listof("PreviewParameterProvider").asSequence()
}
- 클래스는 반드시 androidx.compose.ui.tooling.preview.PreviewParameterProvider를 확장 구현해야함.
- 그러면 PreviewParameterProvider가 미리보기에 매개 변수를 전달한다.
@Composable
@Preview
fun AltGreeting2(@PreviewParameter(HelloProvider::class) name: String) {
방법 선택은 개인 취향!
미리 보기 설정
@Preview(showBackground = true, backgroundColor = 0xffff0000)
// 미리보기의 배경색 설정
- 기본적으로 미리 보기 면적은 자동으로 선택
- 명시적으로 설정 할 경우 : heightDp와 widthDp를 전달
@Preview(widthDp = 100, heightDp = 100)
- locale 매겨변수
@Preview(locale = "de-rDE") // 독일어
// 문자열은 values- 뒤에 있는 디렉터리 이름과 같아야 함.
- 상태바나 액션바
@Preview(showSystemUi = true)
미리보기 그룹화
컴포저블 함수의 개수에 따라 미리보기 창이 혼란스러워 보일 수 있다.
group 매개변수를 추가해 그룹에 추가하면 된다.
@Preview(group = "my-group-1")
컴포즈 앱 실행
- 컴포저블 함수 배포
- 특정 컴포저블 함수에 집중하려는 경우 유용
- 전체 앱 배포하는 시간보다 짧을 수 있다
- 방법 : 미리 보기 우측 상단에 있는 Deploy Preview 버튼 클릭
- 앱 실행
액티비티에서 컴포저블 함수 사용
- 매니페스트 파일에 정의 그대로
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Hello() //컴포저블 함수
}
}
}
Ui는 setContent라는 함수가 호출 되면서 화면에 표시
- androidx.activity.ComponentActivity의 확장 함수
- androidx.activity.compose 패키지에 포함
- 컴포저블 함수를 렌더링 하고자 액티비티는 반드시 ComponentActivity나 ComponentActivity를 직접 또는 간접적으로 부모클래스로 갖는 다른 클래스를 상속 해야한다.
- 예) FragmentActivity, AppCompatActivity
컴포즈 앱 뷰 기반 앱
setContent() 호출 | setContentView() |
레이아웃 아이디 or 루트 뷰 자체 전달 | |
UI 컴포넌트 트리나 참조를 유지할 필요가 없다 | |
→ 유지보수가 쉽고 오류를 덜 발생시킨다 | UI 컴포넌트 트리나 참조를 유지할 필요가 있다 |
setContent
- parent와 content 매개 변수를 받음
- parent
- androidx.compose.runtime.CompositionContext의 인스턴스
- 두 컴포지션을 논리적으로 연결하는데 사용
Q&A (같은회사 바틀이 답변 해주심!)
- Compose에는 ViewGroup이 없는 것인가?
Compose에는 ViewGroup이 없는 것인가?
- Android에서의 ViewGroup이란?
자식 뷰로 여러 컴포넌트를 담을 수 있는 컨테이너를 이야기 하며, View를 상속받아 구현됩니다.
또한 우리가 자주 사용하고 있는 ViewGroup으로는 FrameLayout, LinearLayout, ConstraintLayout, RelativeLayout 등 여러 레이아웃이 있습니다.그렇다면 위의 질문을 다시 되새겨 보겠습니다. Compose에는 ViewGroup이 없는가?
Android에서 정의하고 있는 ViewGroup의 개념으로는 '있다' 라고 답변을 하는게 맞을 것 같습니다'
조금 더 상세히 들어가자면, 우리가 추후에 사용하게될, 즉 Kotlin Code로 작성하는 Composable은 ViewGroup이 없습니다.하지만, xml 레이아웃에 Compose를 이식할 수 있도록 만들어주는 ComposeView는 AbstractComposeView를 상속받고 있으며, 이는 ViewGroup을 상속받아 구현 되어 있습니다.
따라서, 질문의 원문만 놓고 본다면
'Compose에는 ViewGroup이 있다'
라고 말할 수 있을 것 같습니다.
2. 명령형 UI 에서도 Tree구조의 View가 선언되는데, 결국 선언형 UI에서도 Tree구조이지 않은가?
이 질문에 많은 생각을 해보았지만, 두 UI 구조에 대해서는 Tree구조가 맞기 때문에
'왜 Tree구조에 대한 이야기가 나오는 것인가' 에 대한 내용을 조금 살펴보았는데,명령형 UI, 즉 xml의 경우에는 Tree구조로 UI를 설계하였기 때문에 상태에 따른 View가 Tree에 계속 남아있어야 하지만(보이지 않더라도 View Tree에는 남아있음),
선언형 UI, 즉 compose의 경우에는 상태값에 따라 View를 그려줄지 말지를 결정하기 때문에 보이지 않는 View는 그리지 않도록 하여 View Tree에 남아있지 않다 에 대하여 이야기를 하려고 Tree에 대한 내용이 나온 것 같다.. 정도로 이해를 하였습니다.
해당 부분 관련해서는 추후 Compose 스터디 중 명확한 내용이 나오길 바래봅니다
3. @Composable Annotation만 설정하면 나머지는 다 Compiler 에서 해주는 것인가?
@Composable Annotaion은 해당 funtion이 Compose Compiler를 사용한다는 것을 프레임워크에 알려주는 역할을 하고, 이후에는 Compose Compiler에서 @Composable 어노테이션이 붙어있는 function을 UI로 그리는 처리를합니다.
참조 : 젯팩 컴포즈로 개발하는 안드로이드 UI
'코틀린 & Java > 컴포즈 Compose' 카테고리의 다른 글
컴포즈(compose) 앱 스타일링 (0) | 2024.01.29 |
---|---|
컴포즈(compose) 컴포저블 함수 상태 관리 (0) | 2023.09.05 |
컴포즈(Compose) UI 요소 배치 (0) | 2023.08.23 |
컴포즈(Compose) 컴포즈 핵심 원칙 자세히 알아보기 (0) | 2023.08.16 |
컴포즈(Compose) 선언적 패러다임 이해 (0) | 2023.08.16 |