Jetpack Compose Modifier 심층 분석
Default Parameter
@Composable
fun PaddedColumn(modifier: Modifier = Modifier) {
Column(modifier.padding(10.dp)) {
// ...
}
}
modifier: Modifier = Modifier
와 같은 코드는 어떻게 만들어지는 것일까? 해당 식에서 처음으로 나타나는 Modifier
는 타입이고 두 번째로 나타나는 Modifier
는 객체이다.
Modifier
Source Code를 확인해 보면 다음과 같다.
interface Modifier {
fun <R> foldIn(initial: R, operation: (R, Element) -> R): R
fun <R> foldOut(initial: R, operation: (Element, R) -> R): R
fun any(predicate: (Element) -> Boolean): Boolean
fun all(predicate: (Element) -> Boolean): Boolean
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
/* ... */
companion object : Modifier {
override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R = initial
override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R = initial
override fun any(predicate: (Element) -> Boolean): Boolean = false
override fun all(predicate: (Element) -> Boolean): Boolean = true
override infix fun then(other: Modifier): Modifier = other
override fun toString() = "Modifier"
}
}
Source Code에서 Modifier
는 interface
이며 Modifier
interface
를 상속하는 companion object
의 형태로 구현이 되어 있다. 이 방법을 통해 Modifier라는 이름으로 타입과 객체의 역할을 동시에 하고 있음을 알 수 있다.
Modifier의 순서
Modifier는 순서에 따라 그 결과가 달라지는 모습을 보인다. 이러한 특징이 UI를 만들기 위해 달성해야 하는 목표를 쉽게 달성할 수 있고 기존 Android View System보다 더 강력한 도구로서의 기능을 제공해준다.
Modifier의 순서를 부여하기 위해 Modifier.xxx()
의 Source Code를 확인해 보면 다음과 같다.
fun Modifier.background(
color: Color,
shape: Shape = RectangleShape
) = this.then(
Background(
color = color,
shape = shape,
inspectorInfo = debugInspectorInfo {
name = "background"
value = color
properties["color"] = color
properties["shape"] = shape
}
)
)
위 코드의 Modifier.background(...)
는 Composable에 배경을 지정해 준다. 위 코드에서 볼 수 있는 몇 가지의 특징은
- 확장 함수로 구현됨
this.then
Background
DrawModifier
implementation 으로 볼 수 있을 것이다. 그 중this.then
의 코드를 확인해 보면// interface Modifier in Modifier.kt infix fun then(other: Modifier): Modifier = if (other === Modifier) this else CombinedModifier(this, other)
Modifier
interface
에 해당 함수가 정의되어 있다.other
가Modifier
기본 객체이면 현재 Modifier를 내보내고 그렇지 않을 경우 현재 Modifier와 다른 Modifier를 조합하는CombinedModifier
를 반환한다.
CombinedModifier
의 정의는 다음과 같다.
class CombinedModifier(
private val outer: Modifier,
private val inner: Modifier
) : Modifier {
override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
inner.foldIn(outer.foldIn(initial, operation), operation)
override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R =
outer.foldOut(inner.foldOut(initial, operation), operation)
override fun any(predicate: (Modifier.Element) -> Boolean): Boolean =
outer.any(predicate) || inner.any(predicate)
override fun all(predicate: (Modifier.Element) -> Boolean): Boolean =
outer.all(predicate) && inner.all(predicate)
override fun equals(other: Any?): Boolean =
other is CombinedModifier && outer == other.outer && inner == other.inner
override fun hashCode(): Int = outer.hashCode() + 31 * inner.hashCode()
override fun toString() = "[" + foldIn("") { acc, element ->
if (acc.isEmpty()) element.toString() else "$acc, $element"
} + "]"
}
CombinedModifier
는 outer
, inner
Modifier 정보를 유지하고 foldIn
과 foldOut
을 이용하여 정해진 순서를 유지한다.
Modifier의 범위
Row {
Text(
text = "Test",
modifier = Modifier.align(Alignment.CenterVertically)
)
}
Column {
Text(
text = "Test",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
Modifier.align
은 같은 기능을 하는 것처럼 보이지만 CenterVertically
와 CenterHorizontally
를 서로 바꾸면 컴파일되지 않는다. Modifier.align
은 RowScope
, ColumnScope
interface
에 각각 다른 형태로 정의되어 있다.
interface RowScope {
fun Modifier.align(alignment: Alignment.Vertical): Modifier
}
interface ColumnScope {
fun Modifier.align(alignment: Alignment.Horizontal): Modifier
}
그리고 Row
Composable 함수는 다음과 같이 정의되어 있다.
@Composable
inline fun Row(
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable RowScope.() -> Unit
)
@Composable
inline fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit
)
content: @Composable RowScope.() -> Unit
, content: @Composable ColumnScope.() -> Unit
을 통해 Lambda Composable(하위 컴포저블 호출에 사용)이 각각 RowScope
, ColumnScope
위에서 동작하도록 만든다.
Modifier Implementation
Modifier를 구현하기 위해서는 Modifier.Element
또는 Modifier.Element
에 일부 기능을 미리 추가해 정의된 인터페이스(ex. DrawModifier
)와 InspectorValueInfo
추상 클래스를 상속하여 만든다.
Predefined interfaces
Modifier.Element
외에 기능을 미리 추가하여 정의해 놓은 interface가 존재한다.
DrawModifier
: Composable의 surface에 그림을 그릴 수 있는 함수를 추가한 ModifierLayoutModifier
: size manipulation을 위한 함수를 추가한 Modifier