Indication 및 Ripple API로 이전

인코더-디코더 아키텍처를 사용하는 대화형 구성요소의 컴포지션 성능을 개선하기 위해 Modifier.clickable에 새로운 API를 도입했습니다. 이러한 API를 사용하면 효율적인 Indication 구현(예: 물결 효과)

androidx.compose.foundation:foundation:1.7.0+androidx.compose.material:material-ripple:1.7.0+에는 다음 API가 포함됩니다. 변경사항:

지원 중단됨

대체

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

대신 새로운 ripple() API가 Material 라이브러리에 제공됩니다.

참고: 이 컨텍스트에서 'Material 라이브러리'는 androidx.compose.material:material, androidx.compose.material3:material3, androidx.wear.compose:compose-material, androidx.wear.compose:compose-material3.를 나타냄

RippleTheme

다음 중 하나를 선택합니다.

  • Material 라이브러리 RippleConfiguration 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을 설정하는 경우 자료는 구성요소는 이러한 값을 사용하지 않습니다.

다음 섹션에서는 일시적으로 이전 동작으로 돌아가는 방법을 설명합니다. 마이그레이션하지 않고 새 API로 이전하는 것이 좋습니다. 대상 이전 안내는 rememberRipple에서 ripple로 이전을 참고하세요. 확인할 수 있습니다.

이전 없이 Material 라이브러리 버전 업그레이드

라이브러리 버전 업그레이드를 차단 해제하려면 임시 구성할 LocalUseFallbackRippleImplementation CompositionLocal API Material 구성요소가 이전 동작으로 대체됩니다.

CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
    MaterialTheme {
        App()
    }
}

이전 물결 효과가 발생할 수 있도록 MaterialTheme 외부에 제공해야 합니다. LocalIndication를 통해 제공됩니다.

다음 섹션에서는 새 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()는 더 이상 구성 가능한 함수가 아니며 다음과 같을 필요가 없습니다. 있습니다. 또한 다음과 같이 여러 구성요소에서 재사용할 수 있습니다. 따라서 물결 효과 생성을 최상위 값으로 추출하여 할당을 저장합니다.

맞춤 디자인 시스템 구현

자체 디자인 시스템을 구현하며 이전에 rememberRipple()를 맞춤 RippleTheme와 함께 사용하여 물결 효과를 구성합니다. 대신 물결 효과 노드에 위임하는 자체 물결 효과 API를 제공해야 합니다. material-ripple에 노출된 API입니다. 그런 다음 구성요소가 자체 물결 효과를 사용하여 테마 값을 직접 사용합니다. 자세한 내용은 마이그레이션 제공:RippleTheme

RippleTheme에서 이전

동작 변경을 일시적으로 선택 해제

Material 라이브러리에는 임시 CompositionLocal가 있습니다. LocalUseFallbackRippleImplementation를 사용하여 Material 구성요소는 rememberRipple 사용으로 대체됩니다. 이렇게 하면 rememberRippleLocalRippleTheme를 계속 쿼리합니다.

다음 코드 스니펫은 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를 사용하여 지정된 구성요소의 물결 효과 사용 중지

materialmaterial3 라이브러리는 RippleConfigurationLocalRippleConfiguration를 사용하면 하위 트리 내 물결 효과의 범위를 확인할 수 있습니다. RippleConfigurationLocalRippleConfiguration는 실험용이며 구성요소별로만 사용할 수 있습니다. 맞춤설정할 수 있습니다. 다음과 같은 경우 전역/테마 전체 맞춤설정이 지원되지 않습니다. 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 {
            // ...
        }
    }

위의 스니펫을 다음과 같이 수정해야 합니다.

CompositionLocalProvider(LocalRippleConfiguration provides null) {
    Button {
        // ...
    }
}

RippleTheme를 사용하여 지정된 구성요소의 물결 효과 색상/알파 변경

이전 섹션에서 설명한 것처럼 RippleConfigurationLocalRippleConfiguration는 실험용 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-ripplecreateRippleModifierNode()를 노출합니다. 함수를 사용하세요. 이 함수를 사용하면 디자인 시스템 라이브러리가 테마 값을 쿼리한 다음 위임하는 wrapper 구현을 주문 물결 효과 구현을 이 함수로 만든 노드에 반환합니다.

이를 통해 디자인 시스템에서 필요한 항목을 직접 쿼리하고 상단에 사용자 구성 가능한 필수 테마 레이어가 material-ripple 레이어에서 제공되는 기능을 정의합니다. 또한 이러한 변경을 통해 물결 효과의 기본 테마/사양을 명시해야 합니다. 리플 API 자체는 명시적으로 사용되지 않습니다. 테마에서 파생됩니다.

자세한 내용은 Material의 물결 효과 API 구현을 참고하세요. 필요한 경우 머티리얼 컴포지션 로컬에 대한 호출을 자체 디자인 시스템을 구축할 수 있습니다.

Indication에서 IndicationNodeFactory로 이전

Indication 주변 통과

다음과 같이 전달할 Indication를 만드는 경우 Modifier.clickable 또는 Modifier.indication에 전달할 물결 효과의 경우 변경할 수 있습니다 IndicationNodeFactoryIndication에서 상속됩니다. 모든 것이 계속 컴파일되고 작동합니다.

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()
        }
    }
}

다음 두 단계를 통해 이전할 수 있습니다.

  1. ScaleIndicationInstanceDrawModifierNode로 이전합니다. API 노출 영역 DrawModifierNodeIndicationInstance와 매우 유사합니다. 다음과 기능적으로 동일한 ContentDrawScope#draw() 함수 IndicationInstance#drawContent()입니다. 이 함수를 변경한 다음 collectLatest 로직을 직접 구현하는 대신 Indication

    예를 들어 다음 스니펫은 지원 중단된 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()
            }
        }
    }

  2. 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 구성요소를 사용합니다. 하지만 드문 경우이기 때문에 IndicationInstance에서 rememberUpdatedInstance 앱을 사용하는 경우 다음을 업데이트해야 합니다. 구현을 확인하여 IndicationIndicationNodeFactory인지 확인하므로 간단한 구현을 사용할 수 있습니다 예를 들어 Modifier.indication는 다음과 같이 작동합니다. 생성된 노드가 IndicationNodeFactory인 경우 내부적으로 위임합니다. 만약 Modifier.composed를 사용하여 rememberUpdatedInstance를 호출합니다.