Aby zwiększyć wydajność komponentów interaktywnych, które korzystają z Modifier.clickable, wprowadziliśmy nowe interfejsy API. Te interfejsy API umożliwiają bardziej wydajne implementacje Indication, takie jak efekty falowania.
androidx.compose.foundation:foundation:1.7.0+ iandroidx.compose.material:material-ripple:1.7.0+ obejmują te zmiany w API:
Wycofano |
Wymiana |
|---|---|
|
|
|
Nowe interfejsy API Uwaga: w tym kontekście „biblioteki materiałów” odnoszą się do |
|
Wykonaj jedną z tych czynności:
|
Na tej stronie opisujemy wpływ zmiany działania i podajemy instrukcje migracji do nowych interfejsów API.
Zmiana zachowania
Zmiana działania efektu falowania została wprowadzona w tych wersjach biblioteki:
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 nie używają już rememberRipple(), tylko nowych interfejsów API efektu falowania. W efekcie nie wysyłają zapytania do LocalRippleTheme.
Dlatego jeśli w aplikacji ustawisz LocalRippleTheme, komponenty Material nie będą używać tych wartości.
W sekcjach poniżej znajdziesz opis migracji do nowych interfejsów API.
Przenoszenie z rememberRipple do ripple
Korzystanie z biblioteki materiałów
Jeśli używasz biblioteki Material, zastąp bezpośrednio rememberRipple() wywołaniem ripple() z odpowiedniej biblioteki. Ten interfejs API tworzy efekt fali
na podstawie wartości pochodzących z interfejsów API motywu Material. Następnie przekaż zwrócony obiekt do 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 kodu w ten sposób:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Pamiętaj, że ripple() nie jest już funkcją kompozycyjną i nie trzeba jej zapamiętywać. Można go też używać w wielu komponentach, podobnie jak modyfikatorów, więc warto wyodrębnić tworzenie efektu fali do wartości najwyższego poziomu, aby zaoszczędzić alokacje.
Wdrażanie niestandardowego systemu projektowania
Jeśli wdrażasz własny system projektowania i wcześniej używałeś(-aś) rememberRipple() wraz z niestandardowym RippleTheme do konfigurowania efektu fali, zamiast tego podaj własny interfejs API efektu fali, który przekazuje wywołania do interfejsów API węzła efektu fali udostępnianych w material-ripple. Dzięki temu komponenty mogą używać własnego efektu fali, który bezpośrednio wykorzystuje wartości motywu. Więcej informacji znajdziesz w artykule Migracja z RippleTheme.
Przenoszenie z RippleTheme
Wyłączanie efektu fali dla danego komponentu za pomocą RippleTheme
Biblioteki material i material3 udostępniają RippleConfiguration i LocalRippleConfiguration, które pozwalają skonfigurować wygląd efektów falowania w poddrzewie. Pamiętaj, że RippleConfiguration i LocalRippleConfiguration to funkcje eksperymentalne, które są przeznaczone wyłącznie do dostosowywania poszczególnych komponentów. Te interfejsy API nie obsługują dostosowywania globalnego ani w ramach motywu. Więcej informacji o tym przypadku użycia znajdziesz w artykule Używanie RippleTheme do globalnej zmiany wszystkich efektów falowania w aplikacji.
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 kodu w ten sposób:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
Używanie RippleTheme do zmiany koloru lub wartości alfa efektu falowania w przypadku danego komponentu
Jak opisano w poprzedniej sekcji, interfejsy API RippleConfiguration i LocalRippleConfiguration są eksperymentalne i służą wyłącznie do dostosowywania poszczególnych komponentów.
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 kodu w ten sposób:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Używanie RippleTheme do globalnej zmiany wszystkich efektów falowania w aplikacji
Wcześniej można było używać LocalRippleTheme do określania zachowania efektu fali na poziomie całego motywu. Był to w zasadzie punkt integracji między lokalnymi kompozycjami niestandardowego systemu projektowania a biblioteką Ripple. Zamiast udostępniać ogólny element tematyczny, material-ripple udostępnia teraz funkcję createRippleModifierNode(). Ta funkcja umożliwia bibliotekom systemu projektowania tworzenie implementacji wrapper wyższego rzędu, które wysyłają zapytania o wartości motywu, a następnie przekazują implementację efektu fali do węzła utworzonego przez tę funkcję.
Umożliwia to systemom projektowania bezpośrednie wysyłanie zapytań o potrzebne informacje i udostępnianie wymaganych warstw motywów konfigurowanych przez użytkownika bez konieczności dostosowywania się do tego, co jest dostępne w warstwie material-ripple. Ta zmiana sprawia też, że bardziej wyraźnie widać, do jakiego motywu lub specyfikacji należy efekt fali, ponieważ to sam interfejs Ripple API definiuje ten kontrakt, a nie jest on niejawnie wywodzony z motywu.
Więcej informacji znajdziesz w implementacji interfejsu API ripple w bibliotekach Material. W razie potrzeby zastąp wywołania lokalnych funkcji kompozycji Material w swoim systemie projektowania.
Przenoszenie z Indication do IndicationNodeFactory
Przejazd około Indication
Jeśli tworzysz Indication, aby przekazać go dalej, np. tworzysz falę, którą chcesz przekazać Modifier.clickable lub Modifier.indication, nie musisz wprowadzać żadnych zmian. IndicationNodeFactory dziedziczy po Indication, więc wszystko będzie nadal kompilowane i działać.
Tworzę Indication
Jeśli tworzysz własną implementację Indication, w większości przypadków migracja powinna być prosta. Rozważmy na przykład Indication, który po naciśnięciu wywołuje efekt skalowania:
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:
Przenieś
ScaleIndicationInstance, aby stał sięDrawModifierNode. Interfejs APIDrawModifierNodejest bardzo podobny do interfejsuIndicationInstance: udostępnia funkcjęContentDrawScope#draw(), która jest funkcjonalnie równoważna funkcjiIndicationInstance#drawContent(). Musisz zmienić tę funkcję, a następnie zaimplementować logikęcollectLatestbezpośrednio w węźle, a nie wIndication.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 kodu w ten sposób:
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() } } }
Przenieś
ScaleIndication, aby wdrożyćIndicationNodeFactory. Logika kolekcji została przeniesiona do węzła, więc jest to bardzo prosty obiekt fabryczny, 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 kodu w ten sposób:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
Tworzenie IndicationInstance za pomocą Indication
W większości przypadków należy użyć Modifier.indication, aby wyświetlić Indication w przypadku komponentu. Jeśli jednak ręcznie tworzysz element IndicationInstance za pomocą funkcji rememberUpdatedInstance, musisz zaktualizować implementację, aby sprawdzić, czy element Indication jest elementem IndicationNodeFactory. Dzięki temu możesz użyć prostszej implementacji. Na przykład Modifier.indication wewnętrznie przekieruje żądanie do utworzonego węzła, jeśli jest on węzłem IndicationNodeFactory. W przeciwnym razie użyje Modifier.composed, aby zadzwonić pod numer rememberUpdatedInstance.