Aby zwiększyć wydajność tworzenia komponentów interaktywnych, które używają Modifier.clickable, wprowadziliśmy nowe interfejsy API. Te interfejsy API umożliwiają bardziej wydajne implementacje Indication, takie jak fale.
androidx.compose.foundation:foundation:1.7.0+ i androidx.compose.material:material-ripple:1.7.0+ obejmują te zmiany interfejsu API:
Wycofane |
Wymiana |
|---|---|
|
|
|
Zamiast tego w bibliotekach Material dostępne są nowe interfejsy API Uwaga: w tym kontekście „biblioteki Material” oznaczają |
|
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
Te wersje biblioteki zawierają zmianę zachowania efektu fali:
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(), lecz nowych interfejsów API efektu fali. W związku z tym nie wysyłają zapytań do LocalRippleTheme.
Dlatego jeśli ustawisz LocalRippleTheme w aplikacji, komponenty Material Design nie będą używać tych wartości.
W sekcjach poniżej opisujemy, jak przejść na nowe interfejsy API.
Przenoszenie z rememberRipple do ripple
Korzystanie z biblioteki Material
Jeśli używasz biblioteki Material, zastąp rememberRipple() bezpośrednio wywołaniem ripple() z odpowiedniej biblioteki. Ten interfejs API tworzy efekt fali, korzystając z wartości pochodzących z interfejsów API Material Theme. 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 następujący sposób:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Zwróć uwagę, że ripple() nie jest już funkcją typu „composable” i nie trzeba jej zapamiętywać. Można go też używać w wielu komponentach, podobnie jak modyfikatorów, więc warto rozważyć wyodrębnienie tworzenia 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, musisz podać własny interfejs API efektu fali, który przekazuje dane do interfejsów API węzła efektu fali udostępnianych w material-ripple. Następnie 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.
Przenieś z RippleTheme
Używanie RippleTheme do wyłączania efektu fali w przypadku danego komponentu
Biblioteki material i material3 udostępniają RippleConfiguration i LocalRippleConfiguration, które umożliwiają skonfigurowanie wyglądu efektu fali w poddrzewie. Pamiętaj, że RippleConfiguration i LocalRippleConfiguration są przeznaczone tylko 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 fali 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 następujący sposób:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
Używanie RippleTheme do zmiany koloru/alfa efektu fali w przypadku danego komponentu
Jak opisano w poprzedniej sekcji, RippleConfiguration i LocalRippleConfiguration są przeznaczone tylko 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 następujący 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 fali w aplikacji
Wcześniej można było używać LocalRippleTheme do definiowania zachowania efektu fali na poziomie całego motywu. Był to zasadniczo punkt integracji między niestandardowymi elementami lokalnymi systemu projektowania a efektem fali. Zamiast udostępniać ogólny element podstawowy motywu, 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ę.
Dzięki temu systemy projektowania mogą bezpośrednio wysyłać zapytania o to, czego potrzebują, i udostępniać wymagane warstwy motywów, które użytkownik może skonfigurować, bez konieczności dostosowywania się do tego, co jest dostępne w warstwie material-ripple. Ta zmiana sprawia, że bardziej widoczny jest motyw lub specyfikacja, do których dostosowuje się efekt fali, ponieważ to interfejs API efektu fali definiuje tę umowę, a nie jest ona domyślnie wywodzona z motywu.
Wskazówki znajdziesz w implementacji interfejsu API efektu fali w bibliotekach Material. W razie potrzeby zastąp wywołania lokalnych kompozycji Material na potrzeby własnego systemu projektowania.
Przenoszenie z Indication do IndicationNodeFactory
Mijasz: Indication
Jeśli tworzysz tylko obiekt Indication, który ma być przekazywany, np. efekt fali, który ma być przekazywany do Modifier.clickable lub Modifier.indication, nie musisz wprowadzać żadnych zmian. IndicationNodeFactory dziedziczy po Indication, więc wszystko będzie się kompilować i działać.
Tworzę Indication
Jeśli tworzysz własną implementację Indication, migracja powinna być w większości przypadków prosta. Weźmy na przykład element Indication, który stosuje efekt skalowania 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:
Przenieś
ScaleIndicationInstance, aby stał sięDrawModifierNode. Interfejs APIDrawModifierNodejest bardzo podobny doIndicationInstance: 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 następujący 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() } } }
Przejdź z
ScaleIndicationnaIndicationNodeFactory. Ponieważ logika zbierania została przeniesiona do węzła, 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 następujący 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 do wyświetlania Indication komponentu należy używać Modifier.indication. Jeśli jednak w rzadkich przypadkach ręcznie tworzysz obiekt IndicationInstance za pomocą rememberUpdatedInstance, musisz zaktualizować implementację, aby sprawdzić, czy Indication to IndicationNodeFactory, i móc użyć lżejszej implementacji. Na przykład Modifier.indication będzie wewnętrznie przekazywać uprawnienia do utworzonego węzła, jeśli jest to IndicationNodeFactory. Jeśli nie, użyje Modifier.composed, aby zadzwonić do rememberUpdatedInstance.