Чтобы повысить производительность интерактивных компонентов, в которых используется тег Modifier.clickable, мы добавили новые API. Эти API позволяют более эффективно реализовывать Indication, например создавать эффекты волны.
androidx.compose.foundation:foundation:1.7.0+ и androidx.compose.material:material-ripple:1.7.0+ включают следующие изменения API:
Столбец больше не доступен. |
Замена |
|---|---|
|
|
|
Новые API Примечание. В этом контексте под библиотеками материалов подразумеваются |
|
Выполните одно из следующих действий:
|
На этой странице описаны последствия изменения и приведены инструкции по переходу на новые 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 эффекта пульсации. Поэтому они не отправляют запросы к LocalRippleTheme.
Поэтому, если вы зададите значение LocalRippleTheme в приложении, компоненты Material не будут использовать эти значения.
В следующих разделах описано, как перейти на новые API.
Как перейти с rememberRipple на ripple
Как использовать библиотеку материалов
Если вы используете библиотеку Material, замените rememberRipple() на вызов ripple() из соответствующей библиотеки. Этот API создает эффект волны, используя значения, полученные от API тем Material. Затем передайте возвращенный объект в 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 эффекта, а не неявно выводится из темы.
Инструкции по реализации 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. API дляDrawModifierNodeочень похож на API для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.