Migracja do interfejsów Indication and Ripple API

Aby poprawić kompozycję komponentów interaktywnych, które korzystają z funkcji Modifier.clickable, wprowadziliśmy nowe interfejsy API. Te interfejsy API dają większe możliwości efektywne implementacje Indication, takie jak Echo.

androidx.compose.foundation:foundation:1.7.0+ i androidx.compose.material:material-ripple:1.7.0+ obejmuje ten interfejs API zmiany:

Wycofano

Zamiennik

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

Nowe interfejsy API ripple() zostały udostępnione w bibliotekach Material.

Uwaga: w tym kontekście „Biblioteki materiałów” odnoszą się do: androidx.compose.material:material, androidx.compose.material3:material3, androidx.wear.compose:compose-material i androidx.wear.compose:compose-material3.

RippleTheme

Wykonaj jedną z tych czynności:

  • użyć interfejsów API RippleConfiguration z biblioteki Material Design,
  • Utwórz własną implementację fali systemu projektowego

Na tej stronie opisano wpływ zmiany działania i instrukcje migracji do nowych interfejsów API.

Zmiana w działaniu

W tych wersjach biblioteki występują zmiany w działaniu fal:

  • androidx.compose.material:material:1.7.0+
  • androidx.compose.material3:material3:1.3.0+
  • androidx.wear.compose:compose-material:1.4.0+

Te wersje bibliotek Material Design nie korzystają już z narzędzia rememberRipple(). zamiast korzystają z nowych interfejsów API Echo. W rezultacie nie wysyła zapytania do: LocalRippleTheme. Dlatego, jeśli ustawisz w aplikacji LocalRippleTheme, Material nie będą z nich korzystać.

W tej sekcji opisano, jak tymczasowo wrócić do starego sposobu działania. bez migracji; zalecamy jednak przejście na nowe interfejsy API. Dla: instrukcje migracji znajdziesz w artykule Migracja z rememberRipple do ripple i kolejnych.

Uaktualnij wersję biblioteki Material Design bez migracji

Aby odblokować uaktualnienie wersji biblioteki, możesz użyć LocalUseFallbackRippleImplementation CompositionLocal interfejs API do skonfigurowania Komponenty, z których należy przywrócić poprzedni sposób działania:

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

Pamiętaj, by umieścić go poza obszarem MaterialTheme, aby stare Echo muszą być dostarczane za pośrednictwem firmy LocalIndication.

W poniższych sekcjach opisano, jak przejść na nowe interfejsy API.

Migracja z usługi rememberRipple do środowiska ripple

Korzystanie z biblioteki Material

Jeśli używasz biblioteki Material Design, zastąp rememberRipple() bezpośrednio ripple() z odpowiedniej biblioteki. Ten interfejs API tworzy falę za pomocą wartości pozyskanych z interfejsów API motywu Material. Następnie prześlij zwrócony obiektu Modifier.clickable lub innych komponentów.

Na przykład ten fragment kodu używa wycofanych interfejsów API:

Box(
    Modifier.clickable(
        onClick = {},
        interactionSource = remember { MutableInteractionSource() },
        indication = rememberRipple()
    )
) {
    // ...
}

Zmodyfikuj powyższy fragment, aby:

@Composable
private fun RippleExample() {
    Box(
        Modifier.clickable(
            onClick = {},
            interactionSource = remember { MutableInteractionSource() },
            indication = ripple()
        )
    ) {
        // ...
    }
}

Pamiętaj, że ripple() nie jest już funkcją kompozycyjną i nie musi być zapamiętanych informacji. Można go również używać w wielu komponentach, podobnie jak modyfikatory, więc rozważ wyodrębnienie kreacji Echo do wartości najwyższego poziomu, zapisz alokacje.

Wdrażanie systemu projektowania niestandardowego

Jeśli wdrażasz własny system projektowania, a wcześniej korzystasz z rememberRipple() wraz z niestandardowym RippleTheme do skonfigurowania Echa, należy zamiast tego udostępnić własny interfejs API Ripple, który przekazuje dostęp do tego węzła Interfejsy API udostępnione w material-ripple. Następnie Twoje komponenty mogą używać własnych fal który bezpośrednio wykorzystuje wartości motywu. Więcej informacji znajdziesz w artykule Migracja od RippleTheme.

Migracja z usługi RippleTheme

Tymczasowo zrezygnuj ze zmiany w działaniu

Biblioteki materiałów mają tymczasowy CompositionLocal, LocalUseFallbackRippleImplementation, za pomocą których możesz skonfigurować wszystkie Komponenty materiałowe, które mają zostać użyte w zastępstwie z użyciem rememberRipple. W ten sposób rememberRipple nadal wysyła zapytanie do: LocalRippleTheme.

Fragment kodu poniżej pokazuje, jak korzystać z funkcji Interfejs API LocalUseFallbackRippleImplementation CompositionLocal:

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

Jeśli korzystasz z niestandardowego motywu aplikacji opartego na Material, możesz możesz bezpiecznie umieścić kompozycję lokalną jako część motywu aplikacji:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
        MaterialTheme(content = content)
    }
}

Więcej informacji znajdziesz w artykule o uaktualnianiu biblioteki Material Design bez migracji.

Używanie RippleTheme do wyłączania echa dla danego komponentu

Biblioteki material i material3 udostępniają biblioteki RippleConfiguration i LocalRippleConfiguration, która pozwala skonfigurować wygląd i fale w poddrzewie. Pamiętaj, że RippleConfiguration i LocalRippleConfiguration mają charakter eksperymentalny i są przeznaczone wyłącznie dla poszczególnych komponentów i personalizacji reklam. Dostosowanie globalne/tematyczne nie jest obsługiwane w tych interfejsy API; Więcej informacji: Używanie RippleTheme do globalnej zmiany wszystkich echo w , aby dowiedzieć się więcej o tym przypadku użycia.

Na przykład ten fragment kodu używa wycofanych interfejsów 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 {
            // ...
        }
    }

Zmodyfikuj powyższy fragment, aby:

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

Użycie funkcji RippleTheme do zmiany koloru/alfa fali danego komponentu

Jak opisano w poprzedniej sekcji RippleConfiguration i LocalRippleConfiguration to eksperymentalne interfejsy API, które są przeznaczone tylko dla z każdym komponentem.

Na przykład ten fragment kodu używa wycofanych interfejsów API:

private object DisabledRippleThemeColorAndAlpha : RippleTheme {

    @Composable
    override fun defaultColor(): Color = Color.Red

    @Composable
    override fun rippleAlpha(): RippleAlpha = MyRippleAlpha
}

// ...
    CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) {
        Button {
            // ...
        }
    }

Zmodyfikuj powyższy fragment, aby:

@OptIn(ExperimentalMaterialApi::class)
private val MyRippleConfiguration =
    RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha)

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

Użycie RippleTheme do globalnej zmiany wszystkich echo w aplikacji

Wcześniej można było użyć parametru LocalRippleTheme, aby definiować zachowanie fal przy całą tematykę. Był to zasadniczo punkt integracji danych niestandardowych lokalnych kompozycji systemu projektowania i fali. Zamiast udostępniać ogólną wersję podstawowego motywu, material-ripple udostępnia teraz createRippleModifierNode() . Ta funkcja pozwala projektować biblioteki systemowe na tworzenie zamówić implementację wrapper, wykonać zapytanie o wartości motywu, a następnie przekazać implementacji fali w węźle utworzonym przez tę funkcję.

Dzięki temu systemy projektowe mogą bezpośrednio wysyłać zapytania dotyczące potrzeb i udostępniać wymagane konfigurowalne przez użytkownika warstwy tematyczne bez konieczności co znajduje się w warstwie material-ripple. Ta zmiana sprawi też, że wyraźnie określić motyw lub specyfikację, do której pasuje fala. Ripple API, który definiuje tę umowę, a nie w sposób niejawny wywodzących się z motywu.

Wskazówki znajdziesz w artykule o implementacji interfejsu Ripple API w artykule Material Design. bibliotek. W razie potrzeby zastąpimy wywołania z własnym systemem projektowania.

Migracja z usługi Indication do środowiska IndicationNodeFactory

Od okolicy Indication

Jeśli tylko tworzysz Indication do przekazywania, np. faluje, przechodząc do Modifier.clickable lub Modifier.indication, nie musisz wprowadzić zmiany. Funkcja IndicationNodeFactory dziedziczy dane z zakresu Indication, więc wszystko będzie się nadal kompilować i działać.

Tworzę: Indication

Jeśli tworzysz własną implementację Indication, migracja powinna w większości przypadków będzie prosta. Weźmy na przykład właściwość Indication, która stosuje: efekt skali po naciśnięciu:

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

Możesz to zrobić w 2 krokach:

  1. Zmień status z ScaleIndicationInstance na DrawModifierNode. Interfejs API dla DrawModifierNode jest bardzo podobny do IndicationInstance: ujawnia Funkcja ContentDrawScope#draw(), która jest funkcjonalnie równoważna funkcji IndicationInstance#drawContent() Trzeba zmienić tę funkcję, a następnie zaimplementuj logikę collectLatest bezpośrednio w węźle, zamiast Indication

    Na przykład ten fragment kodu używa wycofanych interfejsów 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()
            }
        }
    }

    Zmodyfikuj powyższy fragment, aby:

    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. Przenieś ScaleIndication, aby wdrożyć IndicationNodeFactory. Ponieważ mechanizm zbierania danych został przeniesiony do węzła. To bardzo prosta fabryka, którego jedynym zadaniem jest utworzenie instancji węzła.

    Na przykład ten fragment kodu używa wycofanych interfejsów 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
        }
    }

    Zmodyfikuj powyższy fragment, aby:

    object ScaleIndicationNodeFactory : IndicationNodeFactory {
        override fun create(interactionSource: InteractionSource): DelegatableNode {
            return ScaleIndicationNode(interactionSource)
        }
    
        override fun hashCode(): Int = -1
    
        override fun equals(other: Any?) = other === this
    }

Użycie Indication do utworzenia IndicationInstance

W większości przypadków należy użyć parametru Modifier.indication, aby wyświetlić element Indication . W rzadkich przypadkach zdarza się jednak, że ręcznie tworzysz IndicationInstance za pomocą usługi rememberUpdatedInstance. Musisz zaktualizować swoje do sprawdzenia, czy Indication to IndicationNodeFactory, dzięki czemu które są prostsze. Na przykład Modifier.indication będzie wewnętrznie przekazać dostęp do utworzonego węzła, jeśli jest to IndicationNodeFactory. Jeśli nie, użyjemy funkcji Modifier.composed do wywołania funkcji rememberUpdatedInstance.