Aby poprawić wydajność kompozycji interaktywnych komponentów, które używają Modifier.clickable
, wprowadziliśmy nowe interfejsy API. Te interfejsy API umożliwiają bardziej wydajne implementacje Indication
, takie jak echo.
androidx.compose.foundation:foundation:1.7.0+
i androidx.compose.material:material-ripple:1.7.0+
zawierają te zmiany w interfejsie API:
Wycofano |
Zamiennik |
---|---|
|
|
|
Zamiast nich udostępniliśmy nowe interfejsy API Uwaga: w tym kontekście „biblioteki materiałów” odnoszą się do nazw |
|
Możesz:
|
Na tej stronie opisujemy wpływ zmian w działaniu oraz instrukcje migracji do nowych interfejsów API.
Zmiana działania
Te wersje biblioteki zawierają zmianę działania:
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()
; zamiast tego korzystają z nowych interfejsów API Ripple. W związku z tym nie wysyłają zapytań o LocalRippleTheme
.
Dlatego jeśli ustawisz w aplikacji właściwość LocalRippleTheme
, Komponenty Material Design nie będą używać tych wartości.
W tej sekcji dowiesz się, jak tymczasowo wrócić do starego sposobu działania bez migracji, ale zalecamy przejście na nowe interfejsy API. Instrukcje migracji znajdziesz w artykule Migracja z rememberRipple
do ripple
i kolejnych sekcjach.
Uaktualnij wersję biblioteki Material bez migracji
Aby odblokować uaktualnianie wersji biblioteki, możesz za pomocą tymczasowego interfejsu API LocalUseFallbackRippleImplementation CompositionLocal
skonfigurować komponenty Material Design, aby wrócić do starego działania:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Pamiętaj, aby udostępnić go poza MaterialTheme
, tak aby stare echa mogły być dostarczane przez LocalIndication
.
W sekcjach poniżej dowiesz się, jak przejść na nowe interfejsy API.
Migracja 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 echo za pomocą wartości uzyskanych z interfejsów API motywu Material Design. Następnie przekaż zwrócony obiekt do komponentu Modifier.clickable
lub innych komponentów.
Na przykład ten fragment kodu korzysta z wycofanych interfejsów API:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Zmodyfikuj ten fragment kodu, 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 trzeba jej pamiętać. Można go też używać wielokrotnie w wielu komponentach, podobnie jak w przypadku modyfikatorów, więc zastanów się nad wyodrębnieniem tworzonego echa z wartością najwyższego poziomu w celu zapisania przydziałów.
Wdrożenie niestandardowego systemu projektowania
Jeśli wdrażasz własny system projektowania, a do konfigurowania echa były wcześniej używane interfejs rememberRipple()
i niestandardowy RippleTheme
, musisz udostępnić własny interfejs ripple API, który przekazuje dostęp do interfejsów API węzła ripple ujawnionych w material-ripple
. Następnie komponenty mogą korzystać z własnej echa, która bezpośrednio korzysta z wartości motywu. Więcej informacji znajdziesz w artykule Migracja z domenyRippleTheme
.
Migracja z RippleTheme
Tymczasowo zrezygnuj ze zmiany działania
Biblioteki materiałów zawierają tymczasowy plik CompositionLocal
,
LocalUseFallbackRippleImplementation
, którego możesz użyć do skonfigurowania wszystkich komponentów Material, które mają wrócić do korzystania z rememberRipple
. Dzięki temu rememberRipple
będzie nadal wysyłać zapytania do witryny LocalRippleTheme
.
Ten fragment kodu pokazuje, jak korzystać z interfejsu API LocalUseFallbackRippleImplementation CompositionLocal
:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Jeśli używasz niestandardowego motywu aplikacji opartego na Material, możesz bezpiecznie dodać 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 sekcji Uaktualnij bibliotekę Material bez migracji.
Używanie funkcji RippleTheme
do wyłączania echa w danym komponencie
Biblioteki material
i material3
udostępniają RippleConfiguration
i LocalRippleConfiguration
, które umożliwiają skonfigurowanie wyglądu echa w obrębie drzewa podrzędnego. Pamiętaj, że funkcje RippleConfiguration
i LocalRippleConfiguration
są eksperymentalne i służą tylko do dostosowywania poszczególnych komponentów. Te interfejsy API nie obsługują dostosowywania globalnych ani ogólnych. Więcej informacji na ten temat znajdziesz w artykule na temat globalnego zmieniania wszystkich echa w aplikacji za pomocą RippleTheme
.
Na przykład ten fragment kodu korzysta z 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 ten fragment kodu, aby:
@OptIn(ExperimentalMaterialApi::class) private val DisabledRippleConfiguration = RippleConfiguration(isEnabled = false) // ... CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) { Button { // ... } }
Używanie funkcji RippleTheme
do zmiany koloru/alfa fali dla danego komponentu
Jak opisano w poprzedniej sekcji, RippleConfiguration
i LocalRippleConfiguration
to eksperymentalne interfejsy API, przeznaczone tylko do dostosowywania w poszczególnych składnikach.
Na przykład ten fragment kodu korzysta z 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 ten fragment kodu, aby:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Używanie RippleTheme
do globalnej zmiany wszystkich echa w aplikacji
Wcześniej do definiowania echa na poziomie motywu można było używać właściwości LocalRippleTheme
. Ogólnie był to punkt integracji między lokalnym
kompozycją systemu a falami. Zamiast ujawniać ogólny element podstawowy motywu, material-ripple
udostępnia teraz funkcję createRippleModifierNode()
. Ta funkcja umożliwia bibliotekom systemowym projektowania tworzenie implementacji wrapper
wyższego rzędu, które wysyła zapytania do wartości motywu, a następnie przekazuje implementację echa do węzła utworzonego przez tę funkcję.
Dzięki temu systemy projektowania mogą bezpośrednio wysyłać zapytania dotyczące ich potrzeb i wyświetlać na wierzchu wszystkie wymagane warstwy motywów konfigurowanych przez użytkownika bez konieczności spełniania wymagań określonych w warstwie material-ripple
. Ta zmiana dokładniej określa też motyw lub specyfikację, do której dostosowuje się Echo, ponieważ to właśnie interfejs Ripple API określa tę umowę, a nie pośrednio wywodzi się z motywu.
Wskazówki znajdziesz w artykule o implementacji interfejsu Ripple API w bibliotekach Material Design. W razie potrzeby zastąp wywołania lokalnych składów Material Design w swoim systemie projektowania.
Migracja z Indication
do IndicationNodeFactory
Mija Indication
Jeśli tylko tworzysz obiekt Indication
do przekazywania, na przykład tworzysz echa, która przekazuje do Modifier.clickable
lub Modifier.indication
, nie musisz wprowadzać żadnych zmian. IndicationNodeFactory
dziedziczy dyrektywę z Indication
, więc wszystko będzie nadal kompilować i działać.
Tworzę regułę zasad Indication
Jeśli tworzysz własną implementację Indication
, w większości przypadków migracja powinna być prosta. Weźmy na przykład Indication
, który stosuje efekt skalowania w przypadku prasy:
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:
Zmień rolę
ScaleIndicationInstance
naDrawModifierNode
. Powierzchnia interfejsu APIDrawModifierNode
jest bardzo podobna doIndicationInstance
: udostępnia funkcjęContentDrawScope#draw()
, która jest odpowiednikiemIndicationInstance#drawContent()
. Musisz zmienić tę funkcję, a potem zaimplementować logikęcollectLatest
bezpośrednio w węźle, a nie wIndication
.Na przykład ten fragment kodu korzysta z 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 ten fragment kodu, 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() } } }
Aby wdrożyć komponent
IndicationNodeFactory
, przeprowadź migracjęScaleIndication
. Logika zbierania 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 korzysta z 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 ten fragment kodu, 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 }
IndicationInstance
zostanie utworzony za pomocą Indication
W większości przypadków, aby wyświetlić Indication
dla komponentu, należy użyć metody Modifier.indication
. W rzadkich przypadkach, gdy ręcznie tworzysz element IndicationInstance
za pomocą rememberUpdatedInstance
, musisz zaktualizować implementację, aby sprawdzić, czy Indication
to IndicationNodeFactory
. Pozwoli Ci to korzystać z lżejszej implementacji. Na przykład Modifier.indication
będzie wewnętrznie delegować dostęp do utworzonego węzła, jeśli jest to węzeł IndicationNodeFactory
. W przeciwnym razie użyje metody Modifier.composed
do wywołania metody rememberUpdatedInstance
.