Rendering
컴퓨터 그래픽스에서 3차원 모델, 2차원 이미지, 혹은 비디오 프레임 등의 시각적인 요소들을 화면에 출력하기 위해, 컴퓨터의 CPU나 GPU에 의해 그리는 과정
Android에서의 Rendering
화면에 표시할 뷰를 계산하고, 뷰의 변경사항을 반영해 화면에 그리는 과정
@Composable
fun MyCardView() {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.elevation(8.dp)
) {
// 카드뷰 내용
}
}
Compose remember
Compose에서 'remember'는 상태를 보존하고 재사용하기 위한 함수
'remember' 함수를 통해 상태를 유지할 수 있으므로 구성 요소가 다시 렌더링 될 때마다 상태를 새로 고치는 대신 이전에 계산된 값을 다시 사용할 수 있음.
Compose typography
Compose에서 타이포그래피(Typography)는 앱에서 사용되는 글꼴, 글자 크기, 글꼴 두께 등의 속성을 정의하는 방법을 제공한다.
Text("Hello, World!", style = MaterialTheme.typography.h1)
위 코드에서 MaterialTheme.typography.h1은 Material Design 표준에 따라 미리 정의된 H1 타이포그래피를 나타낸다. Text()함수는 이 타이포그래피를 적용하여 "Hello, World!" 텍스트를 표시.
Lambda expression
람다식(Lambda expression)은 함수를 간단하게 표현하는 방법 중 하나.
일반적인 함수와는 달리 이름이 없으며, 함수의 매개변수, 함수 본문, 반환값 등을 간결하게 표현 가능
{ 매개변수 ->
// 함수 본문
반환값
}
val sum = { x: Int, y: Int ->
x + y
}
위 코드에서 val sum은 변수명이며, { x: Int, y: Int -> x + y } 부분이 람다식.
이 람다식은 두 개의 Int형 매개변수 x, y를 받아 덧셈 연산을 수행한 결과를 반환.
람다식은 함수를 인자로 전달하거나 반환값으로 사용할 수 있어, 함수형 프로그래밍 패러다임에서 자주 사용.
예를 들어, map, filter, reduce와 같은 함수형 연산을 수행하는 함수들은 람다식을 매개변수로 받아 사용한다.
fun someFunction(x: Int, y: Int, z: () -> Unit) {
// some logic
}
someFunction(1, 2, {
println("This is a lambda expression")
})
위 예시에서 someFunction의 마지막 인자는 람다식이다. 하지만 이 람다식이 길어지면 코드의 가독성이 떨어질 수 있기에 아래 예시처럼 후행 람다를 사용해 코드를 간략하게 작성한다.
someFunction(1, 2) {
println("This is a lambda expression")
}
즉, 함수 호출에서 람다식을 제일 마지막에 작성하면 괄호 밖으로 빼서 사용할 수 있는 개념이 Trailing lambda.
아처럼 후행 람다는 코드 가독성을 높여주는 장점이 있다.
@Composable
fun MyApp() {
val moneyCounter = remember {
mutableStateOf(0)
}
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(),
color = Color(0xFF546E7A)
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "$${moneyCounter.value}", style = TextStyle(
color = Color.White,
fontSize = 35.sp,
fontWeight = FontWeight.ExtraBold
))
Spacer(modifier = Modifier.height(130.dp))
/*
람다식인
{ moneyCounter.value = it +1 }은 '(Int) -> Unit' 타입의 함수.
이 람다식이 updateMoneyCounter 함수를 대체할 수 있게 되는 것이다.
따라서, CreateCircle 함수가 호출될 때,
첫 번째 파라미터 moneyCounter는 moneyCounter.value로 할당되며,
두 번째 파라미터 updateMoneyCounter는 람다식 { moneyCounter.value = it + 1 }로 할당된다.
CreateCircle 함수 내에서 updateMoneyCounter를 호출하면
람다식 { moneyCounter.value = it + 1 }이 실행되는 원리.
*/
CreateCircle(moneyCounter = moneyCounter.value) { newCounter ->
moneyCounter.value = newCounter
}
if (moneyCounter.value > 25) {
Text("Lots of Money !")
}
}
}
}
//@Preview
@Composable
// updateMoneyCounter는 Int타입의 값을 받아서 반환값이 없는 함수
fun CreateCircle(moneyCounter: Int = 0,
updateMoneyCounter: (Int) -> Unit) {
// Card의 패딩은 3dp
Card(modifier = Modifier
.padding(3.dp)
// 높이, 너비 모두 45dp
.size(105.dp)
.clickable {
// moneyCounter.value += 1
updateMoneyCounter(moneyCounter + 1)
Log.d("Counter", "CreateCircle: $moneyCounter")
},
shape = CircleShape,
elevation = 4.dp
) {
Box(contentAlignment = Alignment.Center) {
Text(text = "Tap", modifier = Modifier)
}
}
}
Compose Hosting
Composable 함수를 호출하여 View를 렌더링하는 기능을 수행하는 Android 애플리케이션의 구성 요소.
Composable 함수를 호스팅하는 것은 Compose UI의 핵심 개념 중 하나이며, Android 애플리케이션에서 UI 레이아웃을 작성하는 데 사용됨.
Hosting은 Composable 함수를 호출하는 코드 블록이다.
이러한 코드 블록에서 Composable 함수에 대한 호출을 작성하여 UI 구성 요소를 생성하는데, 이러한 호스팅 함수에는 setContent, Scaffold 및 Column 등이 있음.
Hosting 함수는 UI 레이아웃의 계층 구조와 Composable 함수가 렌더링 되는 방식을 정의한다.
Hosting 함수에서는 Composable 함수의 인자를 전달할 수 있다.
Hosting 함수는 애플리케이션의 상태를 나타내는 객체를 Composable 함수에 전달하거나, Composable 함수에서 사용되는 애니메이션, 트랜지션 등을 정의할 수도 있다.
Compose Modifier
Composable 함수의 특성과 레이아웃을 수정하는 역할을 수행.
Composable 함수 내에서 Modifier를 사용하여 뷰의 속성을 변경하거나 레이아웃을 변경한다.
Modifier에는 여러 메서드가 있으며, 각 메서드는 해당하는 속성을 변경한다.
예를 들어, Modifier.size(width = 100.dp, height = 100.dp)를 사용하여 크기를 100dp로 설정할 수 있으며, Modifier.background(color = Color.Red)를 사용하여 배경 색상을 빨간색으로 설정할 수도 있다.
Modifier는 체인 형태로 사용할 수 있으며, 체인의 마지막에는 항상 Composable 함수가 위치해야 한다.
이러한 Modifier 체인은 읽기 쉽고 유지 보수가 용이하며, 뷰를 조작하는데 매우 편리.
Modifier는 레이아웃 변경 또는 뷰 속성 변경과 같이 뷰의 변화를 캡슐화하여 뷰 계층 구조를 더욱 깨끗하게 유지할 수 있도록 한다.
Modifier는 새로운 Composable 함수를 만들어 뷰 계층 구조를 구성하는데 사용할 수도 있음.
Compose Widget
Compose에서는 레이아웃과 위젯이 개념적으로 구분되지는 않음.
대신, 모든 UI 요소는 위젯이라는 개념으로 표현됨.
Compose에서 위젯은 앱의 UI 구성 요소를 나타낸다.
예를 들어, 버튼, 텍스트, 이미지 등 모든 UI 구성 요소는 위젯.
위젯은 각각의 고유한 상태(state)를 가지고 있으며, Compose는 이러한 위젯의 상태를 변경하고 조작하는 데 사용됨.
위젯은 또한, 다른 위젯의 조합으로 만들어질 수 있는데 이러한 조합을 통해 Compose에서는 다양한 레이아웃 및 UI 디자인을 생성할 수 있게 됨.
Compose에서는 모든 위젯이 함수이며, Composable 함수라는 특별한 종류의 함수를 사용하여 UI를 작성한다.
Composable 함수는 인자를 받아들이고 UI를 생성하는 데 사용함.
Composable 함수 내에서는 다른 Composable 함수를 호출하거나 조합하여 UI를 구성할 수 있음.
이렇게 Compose에서는 모든 위젯이 함수로 표현되기 때문에, 함수의 재사용성과 유연성이 향상됨.
Compose State
Compose에서의 State는 값이 변경될 수 있는 데이터를 나타내기 위한 개념.
State는 Composable 함수 내에서 변경 가능한 값을 저장하고, 이 값이 변경되면 Composable 함수가 다시 실행되어 UI가 업데이트됨.
State는 Compose에서 변경 가능한 데이터를 나타내기 위한 주요한 개념 중 하나이며, 다양한 타입의 데이터를 저장할 수 있다.
예를 들어 IntState, StringState, BooleanState와 같은 State 타입이 존재.
이러한 State 타입은 값이 변경될 때마다 Composable 함수를 다시 실행하도록 보장한다. 즉, Recomposition을 수행될 수 있게 함.
State는 일반적으로 변수처럼 사용되며, 변수에 접근하거나 값을 변경할 때 value 속성을 사용한다.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
위 코드에서 count는 mutableStateOf(0)으로 초기화된 IntState.
count 값이 변경될 때마다 Composable 함수가 다시 실행되므로, Button에 표시되는 텍스트가 업데이트 즉, Recomposition 된다. Button을 클릭할 때마다 count 값이 1씩 증가하고, 이 값이 변경되면 Text에 표시되는 내용이 변경되는 로직 구조.
Compose Recomposition
Compose에서 Recomposition(재구성)은 UI 상태 변화에 따라 Composable 함수들이 호출되고 다시 그려지는 과정.
즉, UI 상태 변화가 있을 때마다 Composable 함수들이 다시 실행되어 UI를 업데이트하는 과정.
Recomposition은 Compose의 핵심 기능 중 하나이며, 이를 통해 선언적 UI 프로그래밍의 장점을 최대한 활용할 수 있게 된다.
Recomposition을 통해 Composable 함수들은 상태를 가지지 않는 순수 함수로 작성될 수 있는 것이며, UI 상태 변화에 따라 재구성됨으로써 항상 최신 UI 상태를 보장할 수 있다.
Recomposition은 Compose 엔진에 의해 자동으로 처리되기 때문에 개발자가 수동으로 호출하거나 관리할 필요가 없다.
대신, Composable 함수에서 상태나 프로퍼티를 변경하는 것만으로 Recomposition이 자동으로 발생하기 때문이다.
이를 통해 개발자는 UI 업데이트에 집중할 수 있으며, 불필요한 UI 업데이트를 최소화하여 성능을 향상시킬 수 있다.
Compose Container function
Compose에서 Container 함수는 다양한 레이아웃 기능을 제공하는 함수 중 하나.
Container 함수를 사용하여 자식 요소를 포함하는 컨테이너를 만들 수 있다.
Compose Surface
Compose에서 Surface는 일반적으로 다른 구성요소를 포함하는 컨테이너.
Surface는 주어진 모양과 색상으로 화면을 렌더링하고, 그림자, 경계선 및 기타 시각적 요소를 추가하여 디자인 요소를 강화할 수 있다.
Compose에서 Surface 컴포넌트는 박스, 버튼, 카드 등의 구성 요소를 배치할 수 있는 가장 기본적인 컨테이너.
Surface의 주요 기능 중 하나는 그림자를 추가하는 것. 그림자를 통해 UI 구성 요소 간의 계층을 표현할 수 있으며, 사용자가 인터랙션할 때 그림자가 애니메이션화될 수도 있음.
Surface의 기본 속성에는 color, contentColor, elevation, onClick 등이 있으며, 이를 통해 UI 요소의 배경색, 전경색, 그림자, 클릭 핸들러 등을 제어할 수 있습니다. Surface 컴포넌트는 여러 종류의 모양을 가질 수 있으며, Material Design에서는 Card, Dialog, Drawer 등의 구성 요소로 사용됨.
Surface는 주로 콘텐츠를 그룹화하고 배경을 렌더링하기 위해 사용됨.
Surface는 다음과 같은 속성을 포함한다.
ex ) 빨간색 배경과 8dp 크기의 그림자를 가진 Surface
Surface(
modifier = Modifier
.size(200.dp)
.padding(16.dp),
color = Color.Red,
elevation = 8.dp,
) {
// content
}
Surface는 또한 자식 요소를 포함할 수 있기에, Surface 내부에서 다른 구성요소들을 구성 및 배치 가능.
Compose Column
Compose의 Column은 세로 방향으로 UI 요소를 배치하기 위해 사용되는 컨테이너.
ex) Column을 사용하여 두 개의 Text 요소를 세로로 나란히 배치
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Hello")
Text("Compose")
}
위의 코드는 전체 화면을 fillMaxSize()로 채우며, verticalArrangement 및 horizontalAlignment 속성을 사용하여 Text 요소들이 수직 및 수평 방향으로 중앙에 배치되도록 설정.
Compose Lazy Column
데이터를 동적으로 로드하고 화면에 렌더링하는 데 사용되는 선형 레이아웃.
LazyColumn(
modifier = Modifier.verticalScroll(rememberScrollState())
) {
// items to be displayed vertically
}
LazyColumn(
modifier = Modifier.horizontalScroll(rememberScrollState())
) {
// items to be displayed horizontally
}
LazyColumn {
itemsIndexed(items = listOf("apple", "banana", "cherry", "date")) { index, item ->
Text("$index - $item")
}
}
LazyColumn은 RecyclerView와 달리 뷰홀더를 생성하거나 바인딩할 필요가 없음.
대신 Compose는 필요할 때 새로운 뷰를 생성하고 필요하지 않은 뷰를 버리는 방식으로 동작한다.
이러한 방식으로 Lazy Column은 필요한 항목만 렌더링하여 앱의 성능을 크게 향상시키게 됨.
ex) LazyColumn을 사용하여 RecyclerView를 구현하려면 다음과 같이 작성할 수 있음.
@Composable
fun MyList(dataList: List<String>) {
LazyColumn {
items(dataList) { data ->
Text(data)
}
}
}
LazyColumn을 사용하여 리스트를 만들고 items 함수를 사용하여 각 항목을 표시.
items 함수는 데이터 목록과 각 데이터 항목에 대한 콜백 함수를 인자로 받는다.
LazyRow를 사용하면 수평 스크롤 가능한 RecyclerView를 만들 수 있다.
또한, LazyColumn과 LazyRow에는 데이터를 동적으로 로드할 수 있는 기능이 내장되어 있으므로 대규모 데이터 세트를 처리하는 데도 유용
ex) items 매개변수에 List나 LazyListScope를 넘겨주어 그리드 뷰를 구성
@Composable
fun GridRecyclerView(gridItems: List<GridItem>) {
LazyVerticalGrid(
cells = GridCells.Fixed(2), // 2개의 열을 갖는 그리드 뷰
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
) {
items(gridItems) { item ->
// 각각의 그리드 아이템을 표시할 UI Composable
GridItem(item)
}
}
}
@Composable
fun GridItem(item: GridItem) {
// 각각의 그리드 아이템 UI
Text(
text = item,
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.height(48.dp)
.border(1.dp, Color.Gray)
.padding(16.dp),
textAlign = TextAlign.Center
)
}
LazyVerticalGrid의 cells 매개변수는 그리드 뷰에서 몇 개의 열을 보여줄 것인지를 설정한다. 위 예제에서는 2개의 열을 갖는 그리드 뷰를 생성.
items 매개변수에는 그리드 뷰에 표시할 List를 전달합니다. 그리드 뷰를 더욱 효율적으로 구성하기 위해서는 items 매개변수에 LazyListScope를 사용하여 아이템을 동적으로 로딩하는 방법도 존재.
Compose에서 Modifier의 clip() 함수는 해당 Composable의 클리핑 모양을 지정할 때 사용됨.
클리핑 모양은 Composable의 영역을 어떻게 자를 것인지를 결정.
clip() 함수에 전달할 수 있는 인자는 크게 세 가지.
1. Shape
Shape 인자를 통해 Composable의 영역을 자를 모양을 지정.
MaterialTheme 클래스에서 제공하는 Shape or 사용자가 직접 만든 Shape을 사용.
2. Rectangle size
사각형 모양으로 자를 때 사용.
3. Circle radius
원 모양으로 자를 때 사용.
ex) 50dp 크기의 원 모양으로 클리핑된 Surface 생성
Surface(
modifier = Modifier
.size(100.dp)
.clip(CircleShape)
) {
// ...
}
Modifier의 clip() 함수를 사용하여 Composable의 클리핑 모양을 지정하면 해당 Composable이 지정된 모양에 맞게 잘려서 표시됨.
응용하여 ImageView 등의 이미지를 원 모양이나 사각형 모양으로 클리핑하여 표시할 수 있음.
Compose imeAction
Compose의 TextField에서 imeAction은 사용자가 키보드에서 엔터 키를 눌렀을 때 실행되는 동작을 지정하는 데 사용.
이를 통해 사용자 인터페이스를 보다 쉽게 구성할 수 있으며, 사용자 경험을 향상시킬 수 있음.