Для повышения производительности композиции интерактивных компонентов, использующих 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 Ripple. В результате они не запрашивают 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()
больше не является компонуемой функцией и не требует запоминания. Её также можно использовать повторно в нескольких компонентах, подобно модификаторам, поэтому рассмотрите возможность извлечения создания ripple в значение верхнего уровня для экономии ресурсов.
Внедрение системы индивидуального дизайна
Если вы реализуете собственную систему дизайна и ранее использовали rememberRipple()
вместе с пользовательской темой RippleTheme
для настройки Ripple, вам следует предоставить собственный API Ripple, который делегирует функции API узла Ripple, представленные в material-ripple
. Тогда ваши компоненты смогут использовать вашу собственную 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 и предназначены только для настройки отдельных компонентов.
Например, в следующем фрагменте используются устаревшие 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
. Это изменение также делает более явным соответствие темы/спецификации Ripple, поскольку сам API Ripple определяет этот контракт, а не выводится неявно из темы.
Для получения руководства ознакомьтесь с реализацией API Ripple в библиотеках материалов и замените вызовы локальных переменных композиции материалов по мере необходимости в вашей собственной системе проектирования.
Миграция из 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
очень похож на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 }
Использование Indication
для создания IndicationInstance
В большинстве случаев для отображения Indication
компонента следует использовать Modifier.indication
. Однако в редких случаях, когда вы вручную создаёте экземпляр IndicationInstance
с помощью rememberUpdatedInstance
, необходимо обновить реализацию, чтобы проверить, является ли Indication
фабрикой IndicationNodeFactory
, чтобы использовать более лёгкую реализацию. Например, Modifier.indication
будет делегировать полномочия созданному узлу, если он является фабрикой IndicationNodeFactory
. В противном случае он будет использовать Modifier.composed
для вызова rememberUpdatedInstance
.