Modifier.clickable
를 사용하는 대화형 구성요소의 컴포지션 성능을 개선하기 위해 새 API를 도입했습니다. 이러한 API를 사용하면 물결 효과와 같은 더 효율적인 Indication
구현이 가능합니다.
androidx.compose.foundation:foundation:1.7.0+
및 androidx.compose.material:material-ripple:1.7.0+
에는 다음 API 변경사항이 포함되어 있습니다.
지원 중단됨 |
대체 |
---|---|
|
|
|
대신 새로운 참고: 이 컨텍스트에서 '머티리얼 라이브러리'는 |
|
다음 중 하나를 선택합니다.
|
이 페이지에서는 동작 변경사항이 미치는 영향과 새 API로 이전하기 위한 안내를 설명합니다.
동작 변경
다음 라이브러리 버전에는 물결 효과 동작 변경사항이 포함되어 있습니다.
androidx.compose.material:material:1.7.0+
androidx.compose.material3:material3:1.3.0+
androidx.wear.compose:compose-material:1.4.0+
이러한 버전의 Material 라이브러리는 더 이상 rememberRipple()
를 사용하지 않습니다. 대신 새 물결 효과 API를 사용합니다. 따라서 LocalRippleTheme
를 쿼리하지 않습니다.
따라서 애플리케이션에서 LocalRippleTheme
를 설정하면 Material 구성요소에서는 이러한 값을 사용하지 않습니다.
다음 섹션에서는 이전하지 않고 일시적으로 이전 동작으로 대체하는 방법을 설명합니다. 하지만 새 API로 이전하는 것이 좋습니다. 마이그레이션 안내는 rememberRipple
에서 ripple
로 마이그레이션 및 후속 섹션을 참고하세요.
이전하지 않고 Material 라이브러리 버전 업그레이드
라이브러리 버전 업그레이드를 차단 해제하려면 임시 LocalUseFallbackRippleImplementation CompositionLocal
API를 사용하여 Material 구성요소가 이전 동작으로 대체되도록 구성하면 됩니다.
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
LocalIndication
를 통해 이전 물결 효과를 제공할 수 있도록 MaterialTheme
외부에 이를 제공해야 합니다.
다음 섹션에서는 새 API로 이전하는 방법을 설명합니다.
rememberRipple
에서 ripple
로 이전
Material 라이브러리 사용
Material 라이브러리를 사용하는 경우 rememberRipple()
를 상응하는 라이브러리의 ripple()
호출로 직접 바꿉니다. 이 API는 Material 테마 API에서 파생된 값을 사용하여 물결 효과를 만듭니다. 그런 다음 반환된 객체를 Modifier.clickable
또는 기타 구성요소에 전달합니다.
예를 들어 다음 스니펫은 지원 중단된 API를 사용합니다.
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
위의 스니펫을 다음과 같이 수정해야 합니다.
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
ripple()
는 더 이상 구성 가능한 함수가 아니며 기억할 필요가 없습니다. 또한 수정자와 마찬가지로 여러 구성요소에 재사용할 수 있으므로 할당을 저장하려면 물결 효과 생성을 최상위 값으로 추출하는 것이 좋습니다.
맞춤 디자인 시스템 구현
자체 디자인 시스템을 구현하고 이전에 맞춤 RippleTheme
와 함께 rememberRipple()
를 사용하여 물결 효과를 구성한 경우 material-ripple
에 노출된 물결 효과 노드 API에 위임하는 자체 물결 효과 API를 대신 제공해야 합니다. 그러면 구성요소가 테마 값을 직접 사용하는 자체 물결 효과를 사용할 수 있습니다. 자세한 내용은 RippleTheme
에서 이전을 참고하세요.
RippleTheme
에서 이전
동작 변경사항을 일시적으로 선택 해제
Material 라이브러리에는 임시 CompositionLocal
인 LocalUseFallbackRippleImplementation
가 있으며, 이 임시 파일을 사용하여 rememberRipple
을 사용하도록 모든 Material 구성요소를 구성할 수 있습니다. 이렇게 하면 rememberRipple
가 LocalRippleTheme
를 계속 쿼리합니다.
다음 코드 스니펫은 LocalUseFallbackRippleImplementation CompositionLocal
API 사용 방법을 보여줍니다.
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Material을 기반으로 빌드된 맞춤 앱 테마를 사용하는 경우 앱 테마의 일부로 컴포지션 로컬을 안전하게 제공할 수 있습니다.
@OptIn(ExperimentalMaterialApi::class) @Composable fun MyAppTheme(content: @Composable () -> Unit) { CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme(content = content) } }
자세한 내용은 이전 없이 Material 라이브러리 버전 업그레이드 섹션을 참고하세요.
RippleTheme
를 사용하여 특정 구성요소의 물결 효과 사용 중지
material
및 material3
라이브러리는 하위 트리 내에서 물결 효과의 모양을 구성할 수 있는 RippleConfiguration
및 LocalRippleConfiguration
를 노출합니다. RippleConfiguration
및 LocalRippleConfiguration
는 실험용이며 구성요소별 맞춤설정에만 사용됩니다. 이러한 API에서는 전역/테마 전체 맞춤설정이 지원되지 않습니다. 이러한 사용 사례에 관한 자세한 내용은 RippleTheme
를 사용하여 애플리케이션의 모든 물결 효과를 전역적으로 변경을 참고하세요.
예를 들어 다음 스니펫은 지원 중단된 API를 사용합니다.
private object DisabledRippleTheme : RippleTheme { @Composable override fun defaultColor(): Color = Color.Transparent @Composable override fun rippleAlpha(): RippleAlpha = RippleAlpha(0f, 0f, 0f, 0f) } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleTheme) { Button { // ... } }
위의 스니펫을 다음과 같이 수정해야 합니다.
@OptIn(ExperimentalMaterialApi::class) private val DisabledRippleConfiguration = RippleConfiguration(isEnabled = false) // ... CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) { Button { // ... } }
RippleTheme
를 사용하여 특정 구성요소의 물결 효과 색상/알파 변경
이전 섹션에서 설명한 대로 RippleConfiguration
및 LocalRippleConfiguration
는 실험용 API이며 구성요소별 맞춤설정에만 사용됩니다.
예를 들어 다음 스니펫은 지원 중단된 API를 사용합니다.
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
위의 스니펫을 다음과 같이 수정해야 합니다.
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
RippleTheme
를 사용하여 애플리케이션의 모든 물결 효과를 전역적으로 변경
이전에는 LocalRippleTheme
를 사용하여 테마 전체 수준에서 물결 효과 동작을 정의할 수 있었습니다. 이는 기본적으로 맞춤 디자인 시스템 컴포지션 로컬과 물결 효과 간의 통합 지점이었습니다. 일반 테마 설정 프리미티브를 노출하는 대신 이제 material-ripple
가 createRippleModifierNode()
함수를 노출합니다. 이 함수를 사용하면 디자인 시스템 라이브러리가 테마 값을 쿼리하는 고차 wrapper
구현을 만든 다음 이 함수로 만든 노드에 물결 효과 구현을 위임할 수 있습니다.
이를 통해 디자인 시스템은 필요한 항목을 직접 쿼리하고 material-ripple
레이어에서 제공되는 항목을 준수하지 않고도 필요한 사용자 구성 테마 설정 레이어를 맨 위에 노출할 수 있습니다. 또한 이 변경으로 물결 효과는 어떤 테마/사양을 따라야 하는지 더 명확해집니다. 물결 효과 API가 테마에서 암시적으로 파생되는 것이 아니라 해당 계약을 정의하기 때문입니다.
자세한 내용은 Material 라이브러리의 물결 API 구현을 참고하고 자체 디자인 시스템에 필요한 대로 Material 컴포지션 로컬 호출을 바꿉니다.
Indication
에서 IndicationNodeFactory
로 이전
Indication
주변
Modifier.clickable
또는 Modifier.indication
로 전달할 물결 효과를 만드는 것과 같이 전달할 Indication
를 만드는 경우에는 아무것도 변경할 필요가 없습니다. IndicationNodeFactory
는 Indication
에서 상속되므로 모든 것이 계속 컴파일되고 작동합니다.
Indication
생성 중
자체 Indication
구현을 만드는 경우 대부분의 경우 이전은 간단해야 합니다. 예를 들어 누르기에 크기 조정 효과를 적용하는 Indication
를 생각해 보세요.
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } } private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
다음 두 단계로 이전할 수 있습니다.
ScaleIndicationInstance
를DrawModifierNode
로 이전합니다.DrawModifierNode
의 API 노출 영역은IndicationInstance
와 매우 유사합니다. 즉,IndicationInstance#drawContent()
와 기능적으로 동일한ContentDrawScope#draw()
함수를 노출합니다. 이 함수를 변경한 다음Indication
대신 노드 내에서 직접collectLatest
로직을 구현해야 합니다.예를 들어 다음 스니펫은 지원 중단된 API를 사용합니다.
private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
위의 스니펫을 다음과 같이 수정해야 합니다.
private class ScaleIndicationNode( private val interactionSource: InteractionSource ) : Modifier.Node(), DrawModifierNode { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) private suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } private suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun onAttach() { coroutineScope.launch { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> animateToPressed(interaction.pressPosition) is PressInteraction.Release -> animateToResting() is PressInteraction.Cancel -> animateToResting() } } } } override fun ContentDrawScope.draw() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@draw.drawContent() } } }
ScaleIndication
를 이전하여IndicationNodeFactory
를 구현합니다. 이제 컬렉션 로직이 노드로 이동되므로 이는 노드 인스턴스를 만드는 일만 담당하는 매우 간단한 팩토리 객체입니다.예를 들어 다음 스니펫은 지원 중단된 API를 사용합니다.
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } }
위의 스니펫을 다음과 같이 수정해야 합니다.
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
Indication
를 사용하여 IndicationInstance
만들기
대부분의 경우 Modifier.indication
를 사용하여 구성요소의 Indication
를 표시해야 합니다. 그러나 드물지만 rememberUpdatedInstance
를 사용하여 IndicationInstance
를 수동으로 만드는 경우에는 더 간단한 구현을 사용할 수 있도록 구현을 업데이트하여 Indication
가 IndicationNodeFactory
인지 확인해야 합니다. 예를 들어 Modifier.indication
는 IndicationNodeFactory
인 경우 생성된 노드에 내부적으로 위임합니다. 그렇지 않으면 Modifier.composed
를 사용하여 rememberUpdatedInstance
를 호출합니다.