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 Indicationimplementacje, np. efekt falowania.
androidx.compose.foundation:foundation:1.7.0+ i androidx.compose.material:material-ripple:1.7.0+ obejmują te zmiany w interfejsie API:
Wycofano |
Urządzenie zamienne |
|---|---|
|
|
|
Nowe interfejsy API Uwaga: w tym kontekście „biblioteki materiałów” oznaczają |
|
Wykonaj jedną z tych czynności:
|
Na tej stronie opisujemy wpływ zmiany w działaniu i podajemy instrukcje migracji do nowych interfejsów API.
Zmiana zachowania
Zmiana w działaniu efektu fali 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(), ale nowych interfejsów API efektu falowania. W związku z tym nie wysyłają zapytania 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, bezpośrednio zastąp rememberRipple() wywołaniem ripple() z odpowiedniej biblioteki. Ten interfejs API tworzy efekt fali
za pomocą wartości pochodzących z interfejsów API motywu Material. Następnie przekaż zwrócony obiekt do funkcji 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ą złożoną i nie musisz o niej pamiętać. Można go też ponownie wykorzystać w wielu komponentach, podobnie jak modyfikatory, więc warto wyodrębnić tworzenie efektu fali do wartości najwyższego poziomu, aby zaoszczędzić przydziały.
Wdrażanie niestandardowego systemu projektowania
Jeśli wdrażasz własny system projektowania i wcześniej używałeś elementu rememberRipple() wraz z niestandardowym elementem 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 zRippleTheme.
Przenoszenie z RippleTheme
Wyłączanie efektu fali w przypadku danego komponentu za pomocą atrybutu RippleTheme
Biblioteki material i material3 udostępniają RippleConfiguration i LocalRippleConfiguration, które umożliwiają skonfigurowanie wyglądu efektów falowania w poddrzewie. Pamiętaj, że parametry 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 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 przezroczystości efektu fali dla danego komponentu
Jak opisano w poprzedniej sekcji, symbole 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 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 definiowania 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 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 falowania do węzła utworzonego przez tę funkcję.
Pozwala to systemom projektowania bezpośrednio wysyłać zapytania o potrzebne informacje i udostępniać wszelkie wymagane warstwy motywów konfigurowane 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 efektu falowania w bibliotekach Material. W razie potrzeby zastąp wywołania lokalnych kompozycji Material w swoim systemie projektowania.
Przenoszenie z Indication do IndicationNodeFactory
Przejazd około Indication
Jeśli tworzysz tylko Indication do przekazania dalej, np. tworzysz falę do przekazania do Modifier.clickable lub Modifier.indication, nie musisz wprowadzać żadnych zmian. IndicationNodeFactory dziedziczy po Indication, więc wszystko będzie nadal się kompilować i działać.
Tworzę Indication
Jeśli tworzysz własną implementację Indication, migracja w większości przypadków powinna być prosta. Weźmy na przykład Indication, który po naciśnięciu powoduje 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. Powierzchnia interfejsu APIDrawModifierNodejest bardzo podobna doIndicationInstance: udostępnia funkcjęContentDrawScope#draw(), która jest funkcjonalnie równoważna zIndicationInstance#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żywać Modifier.indication, aby wyświetlić Indication dla 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żywać prostszej implementacji. Na przykład Modifier.indication wewnętrznie przekaże zadanie utworzonemu węzłowi, jeśli jest on węzłem IndicationNodeFactory. W przeciwnym razie użyje Modifier.composed do wywołania rememberUpdatedInstance.