λ μΌμ΄μ€λ‘ λΉκ΅νλ€. μμ€: src/main/kotlin/.../compare/.
- Case A β
accentOverlay: CompositionLocal νλ μ½μ΄μ draw. (μ νμ composedμ¬μ©) - Case B β
pressScale:InteractionSourceꡬλ + μ λλ©μ΄μ . (무거μ΄composedμ¬μ©)
μμΌλ‘ μ΄ μ½λ μ€ μλ§ μΌλ€(μ£Όμ μ μΈ, λλ΅μΉ). μμ± μ½λλ 0μΌλ‘ μΉλ€.
| composed | Node(μ) | codegen μμ΄ Node(μ) | μμ±λ¬Ό | |
|---|---|---|---|---|
| Case A | ~7 | ~10 | ~55 | ~46 |
| Case B | ~10 | ~35 | ~85 | ~53 |
fun Modifier.accentOverlayComposed(alpha: Float): Modifier = composed {
val color = LocalAccentColor.current
drawWithContent {
drawContent()
drawRect(color.copy(alpha = alpha))
}
}μ¨μ λΉμ©: λ°ν Modifier equals λΆμμ β λΆλͺ¨ μ¬κ΅¬μ± μ νμ μ¬κ΅¬μ±, composed λλ€ λ§€λ²
re-materialize, composition λ° μ¬μ© λΆκ°, layout inspector μ μ΄λ¦ μ λΈ.
@ModifierNodeFactory(name = "accentOverlay")
internal class AccentOverlayNode(
@Invalidates(Draw) var alpha: Float,
) : Modifier.Node(), DrawModifierNode, CompositionLocalConsumerModifierNode {
override fun ContentDrawScope.draw() {
val color = currentValueOf(LocalAccentColor) // draw μμ μ½μΌλ©΄ κ΄μ°°λ¨ β λ‘컬 λ³κ²½ μ μλ redraw
drawContent()
drawRect(color.copy(alpha = alpha))
}
}μμ±: fun Modifier.accentOverlay(alpha) + AccentOverlayElement(create/update/equals/hashCode/inspector).
- μ μ½λ 7 β 10μ€. λμ΄λ 3μ€μ μ λΆ "μ€μ μ 보"(λ¬΄μ¨ λ
ΈλμΈμ§ μ μΈ +
varμ μΈ). - codegen μμΌλ©΄ 7 β ~55μ€. μ΄ 55μ€μ΄ μ«μ΄μ λ€λ€
composedμ λ¨Έλ¬Έλ€. codegen μ΄ μ νν κ·Έ λ²½μ μμ€λ€. - λ€: μμ equals, inspector μ΄λ¦, composition λ° μ¬μ© κ°λ₯,
@Invalidates(Draw)βDrawModifierNodeμ ν©μ± μ»΄νμΌ μ²΄ν¬.
fun Modifier.pressScaleComposed(
interactionSource: InteractionSource,
pressedScale: Float,
): Modifier = composed {
val pressed by interactionSource.collectIsPressedAsState()
val scale by animateFloatAsState(if (pressed) pressedScale else 1f, label = "pressScale")
graphicsLayer { scaleX = scale; scaleY = scale }
}composed κ° κ³΅μ§λ‘ ν΄μ£Όλ κ²: ꡬλ
lifecycle, μ λλ©μ΄μ
μν remember,
interactionSource ν€κ° λ°λλ©΄ μ¬κ΅¬λ
.
@ModifierNodeFactory(name = "pressScale")
internal class PressScaleNode(
@Invalidates(Draw) var pressedScale: Float,
@Invalidates(None) @OnChange var interactionSource: InteractionSource,
) : Modifier.Node(), DrawModifierNode {
private val scaleAnim = Animatable(1f)
private var collectJob: Job? = null
override fun onAttach() = subscribe()
override fun onDetach() { collectJob?.cancel(); collectJob = null }
fun onInteractionSourceChanged() = subscribe() // μμ±λ update() κ° νΈμΆ
private fun subscribe() {
collectJob?.cancel()
collectJob = coroutineScope.launch {
val presses = ArrayList<PressInteraction.Press>()
interactionSource.interactions.collect { i ->
when (i) {
is PressInteraction.Press -> presses.add(i)
is PressInteraction.Release -> presses.remove(i.press)
is PressInteraction.Cancel -> presses.remove(i.press)
}
launch { scaleAnim.animateTo(if (presses.isNotEmpty()) pressedScale else 1f) }
}
}
}
override fun ContentDrawScope.draw() {
val s = scaleAnim.value // snapshot μν β νλ μλ§λ€ μλ redraw
scale(s, s, center) { this@draw.drawContent() }
}
}- μ μ½λ 10 β ~35μ€. Element(~50μ€) + μ¬κ΅¬λ
dedup(
boundSourceνλ,draw()μrebind()) μ΄ μ¬λΌμ‘λ€. λ¨μ μ₯ν©ν¨μAnimatable/ press μΉ΄μ΄ν β μ§μ§ λ Έλ νλ μ½λλ€. @OnChange var interactionSourceβ μμ±λupdate()κ° νλ κ°±μ νonInteractionSourceChanged()λ₯Ό νΈμΆνλ€.composedμremember(key)μ¬κ΅¬λ μ λμ.Animatable/ press μΉ΄μ΄ν 보μΌλ¬νλ μ΄νΈλ μ¬μ ν codegen λ²μ λ° (λ Έλ μ νΈμ΄ μμΌλ©΄ λ μ€κ² μ§λ§ λ³κ°).
| λͺ¨λνμ΄μ΄ λΆλ₯ | codegen ν¨κ³Ό |
|---|---|
| κ°(νλΌλ―Έν°/CompositionLocal) μ½κ³ draw/measure μμ λ°μ β 컀μ€ν λͺ¨λνμ΄μ΄μ λ€μ | κ²°μ μ . 7β10 vs 7β55. composed λ₯Ό λ²μ΄λκ² ν΄μ€ |
| interaction / coroutine / animation | @OnChange λ‘ μ¬κ΅¬λ
격차 μΆμ(10β35). λ¨μ μ₯ν©ν¨(Animatable/μΉ΄μ΄ν
)μ μ§μ§ λ
Έλ μ½λ |
| λͺ¨λ λΆλ₯ κ³΅ν΅ | μμ equalsΒ·inspectorΒ·@Invalidates μ ν©μ± 체ν¬Β·ABI νλ©΄ μΆμΒ·@SkipWhen* κ°λ |
- κ° μ½κΈ° λΆλ₯: κ²°μ μ .
fadingEdge(composition-free 체μΈ)λcomposedμΌμ΄μ€κ° μλμκ³ raw λ Έλλ‘ λ΄λ €μ μν΄ β../ARCHITECTURE.mdΒ§7. - interaction λΆλ₯:
@OnChangeμ΄ν κ·Όμ (κ° μ½κΈ°λ§νΌ κ²°μ μ μ΄μ§ μμ). - νλΌλ―Έν° λ¨μ invalidation μ΄ μ€μ λ‘ μλνλμ§(κ·Έλ¦¬κ³ μ±λ₯ μ΄λμ΄ μ μ’μμ§)λ
../ARCHITECTURE.mdΒ§4Β·Β§5.