Чтобы повысить производительность композиции интерактивных компонентов, использующих Modifier.clickable, мы добавили новые API. Эти API позволяют более эффективно реализовывать Indication, например рябь.
В androidx.compose.foundation:foundation:1.7.0+ и androidx.compose.material:material-ripple:1.7.0+ внесены следующие изменения API:
Поддержка прекращена |
Замена |
|---|---|
|
|
|
Вместо них в библиотеках Material Design появились новые API Примечание. В этом контексте под библиотеками Material Design подразумеваются |
|
Выполните одно из следующих действий:
|
На этой странице описано, как изменение поведения повлияет на работу, и приведены инструкции по переходу на новые API.
Изменение поведения
Изменение поведения пульсации включено в следующие версии библиотек:
androidx.compose.material:material:1.7.0+androidx.compose.material3:material3:1.3.0+androidx.wear.compose:compose-material:1.4.0+
В этих версиях библиотек Material больше не используется rememberRipple(). Вместо этого в них применяются новые API Ripple. В результате они не запрашивают LocalRippleTheme.
Поэтому, если вы зададите LocalRippleTheme в своем приложении, компоненты Material не будут использовать эти значения.
В разделах ниже описано, как перейти на новые API.
Переход с rememberRipple на ripple
Использование библиотеки Material
Если вы используете библиотеку Material, замените rememberRipple() вызовом ripple() из соответствующей библиотеки. Этот API создает пульсацию, используя значения, полученные из API тематического оформления Material Design. Затем передайте возвращенный объект в Modifier.clickable и/или другие компоненты.
Например, в следующем фрагменте кода используются устаревшие API:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Вам нужно изменить приведенный выше фрагмент следующим образом:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Обратите внимание, что ripple() больше не является компонуемой функцией и ее не нужно запоминать. Его также можно повторно использовать в нескольких компонентах, как и модификаторы, поэтому рекомендуется извлечь создание пульсации в значение верхнего уровня, чтобы сэкономить ресурсы.
Как реализовать собственную систему проектирования
Если вы реализуете собственную систему проектирования и ранее использовали rememberRipple() вместе с пользовательским RippleTheme для настройки пульсации, вам следует вместо этого предоставить собственный API пульсации, который делегирует API узла пульсации, представленные в material-ripple. Затем ваши компоненты могут использовать вашу собственную рябь, которая напрямую использует значения вашей темы. Дополнительную информацию можно найти в статье Перенос данных из RippleTheme.
Перенесите проекты из RippleTheme
Использование RippleTheme для отключения пульсации для данного компонента
Библиотеки material и material3 предоставляют доступ к RippleConfiguration и LocalRippleConfiguration, которые позволяют настраивать внешний вид пульсации в поддереве. Обратите внимание, что RippleConfiguration и LocalRippleConfiguration предназначены только для настройки отдельных компонентов. Глобальная настройка или настройка на уровне темы не поддерживается этими API. Дополнительную информацию об этом можно найти в разделе Использование RippleTheme для глобального изменения всех пульсаций в приложении.
Например, в следующем фрагменте кода используются устаревшие 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 { // ... } }
Вам нужно изменить приведенный выше фрагмент следующим образом:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
Использование RippleTheme для изменения цвета/альфа-канала пульсации для заданного компонента
Как описано в предыдущем разделе, RippleConfiguration и LocalRippleConfiguration предназначены только для настройки отдельных компонентов.
Например, в следующем фрагменте кода используются устаревшие API:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
Вам нужно изменить приведенный выше фрагмент следующим образом:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Использование RippleTheme для глобального изменения всех пульсаций в приложении
Раньше для определения поведения пульсации на уровне темы можно было использовать LocalRippleTheme. По сути, это была точка интеграции между локальными композициями пользовательской системы проектирования и пульсацией. Вместо общего примитива темы material-ripple теперь предоставляет функцию createRippleModifierNode(). Эта функция позволяет библиотекам систем проектирования создавать реализации wrapper более высокого порядка, которые запрашивают значения темы, а затем делегируют реализацию пульсации узлу, созданному этой функцией.
Это позволяет системам проектирования напрямую запрашивать то, что им нужно, и предоставлять любые необходимые настраиваемые пользователем слои тем поверх, не привязываясь к тому, что предоставляется на уровне material-ripple. Это изменение также делает более явным, какой теме/спецификации соответствует пульсация, поскольку именно API пульсации определяет этот контракт, а не неявно выводится из темы.
Для получения рекомендаций ознакомьтесь с реализацией Ripple API в библиотеках Material и при необходимости замените вызовы локальных композиций Material для вашей собственной системы проектирования.
Переход с Indication на IndicationNodeFactory
Проезжаем мимо города Indication
Если вы просто создаете Indication для передачи, например создаете пульсацию для передачи в Modifier.clickable или Modifier.indication, вам не нужно вносить никаких изменений. IndicationNodeFactory наследует от Indication, поэтому все будет компилироваться и работать.
Создание Indication
Если вы создаете собственную реализацию Indication, в большинстве случаев переход будет простым. Например, рассмотрим Indication, который применяет эффект масштабирования при нажатии:
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() } } }
Для этого выполните следующие действия:
Преобразуйте
ScaleIndicationInstanceвDrawModifierNode. APIDrawModifierNodeочень похож наIndicationInstance: он предоставляет функциюContentDrawScope#draw(), которая функционально эквивалентнаIndicationInstance#drawContent(). Вам нужно изменить эту функцию, а затем реализовать логикуcollectLatestнепосредственно в узле, а не вIndication.Например, в следующем фрагменте кода используются устаревшие 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() } } }
Вам нужно изменить приведенный выше фрагмент следующим образом:
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() } } }
Перейдите с
ScaleIndicationнаIndicationNodeFactory. Поскольку логика сбора данных теперь перенесена в узел, это очень простой объект фабрики, единственная задача которого – создать экземпляр узла.Например, в следующем фрагменте кода используются устаревшие 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 } }
Вам нужно изменить приведенный выше фрагмент следующим образом:
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 с помощью Indication
В большинстве случаев для показа Indication компонента следует использовать Modifier.indication. Однако в редких случаях, когда вы вручную создаете IndicationInstance с помощью rememberUpdatedInstance, вам нужно обновить реализацию, чтобы проверить, является ли Indication IndicationNodeFactory, чтобы вы могли использовать более легкую реализацию. Например, Modifier.indication будет внутренне делегировать созданному узлу, если это IndicationNodeFactory. В противном случае для вызова rememberUpdatedInstance будет использоваться Modifier.composed.