코틀린 & Java/컴포즈 Compose

컴포즈(Compose) 앱 빌드

코딩하는후운 2023. 8. 16. 17:51
반응형

젯팩 컴포즈 공부를 시작하였습니다.

책 : 젯팩 컴포즈로 개발하는 안드로이드 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 (같은회사 바틀이 답변 해주심!)

  1. Compose에는 ViewGroup이 없는 것인가?

Compose에는 ViewGroup이 없는 것인가?

  • Android에서의 ViewGroup이란?

자식 뷰로 여러 컴포넌트를 담을 수 있는 컨테이너를 이야기 하며, View를 상속받아 구현됩니다.
또한 우리가 자주 사용하고 있는 ViewGroup으로는 FrameLayout, LinearLayout, ConstraintLayout, RelativeLayout 등 여러 레이아웃이 있습니다.

그렇다면 위의 질문을 다시 되새겨 보겠습니다. Compose에는 ViewGroup이 없는가?
Android에서 정의하고 있는 ViewGroup의 개념으로는 '있다' 라고 답변을 하는게 맞을 것 같습니다'
조금 더 상세히 들어가자면, 우리가 추후에 사용하게될, 즉 Kotlin Code로 작성하는 Composable은 ViewGroup이 없습니다.

하지만, xml 레이아웃에 Compose를 이식할 수 있도록 만들어주는 ComposeViewAbstractComposeView를 상속받고 있으며, 이는 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

반응형