Чтобы улучшить производительность композиции интерактивных компонентов, использующих 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+
Эти версии библиотек материалов больше не используют rememberRipple()
; вместо этого они используют новые API-интерфейсы Ripple. В результате они не запрашивают LocalRippleTheme
. Поэтому, если вы установите LocalRippleTheme
в своем приложении, компоненты Material не будут использовать эти значения .
В следующем разделе описывается, как временно вернуться к старому поведению без миграции; однако мы рекомендуем перейти на новые API. Инструкции по миграции см. в разделе «Миграция с rememberRipple
на ripple
и последующих разделах.
Обновите версию библиотеки материалов без миграции
Чтобы разблокировать обновление версий библиотеки, вы можете использовать временный API LocalUseFallbackRippleImplementation CompositionLocal
, чтобы настроить компоненты Material для возврата к старому поведению:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Обязательно укажите это вне MaterialTheme
, чтобы старые пульсации можно было предоставить через LocalIndication
.
В следующих разделах описывается, как перейти на новые API.
Переход с rememberRipple
на ripple
Использование библиотеки материалов
Если вы используете библиотеку материалов, замените 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
для настройки Ripple, вам следует вместо этого предоставить свой собственный API Ripple, который делегирует API-интерфейсы узла Ripple, представленные в material-ripple
. Затем ваши компоненты смогут использовать собственную пульсацию, которая напрямую использует значения вашей темы. Дополнительную информацию см. в разделе Миграция из RippleTheme
.
Миграция с RippleTheme
Временно отказаться от изменения поведения
Библиотеки материалов имеют временный CompositionLocal
, LocalUseFallbackRippleImplementation
, который вы можете использовать для настройки всех компонентов Material для возврата к использованию rememberRipple
. Таким образом, rememberRipple
продолжает запрашивать LocalRippleTheme
.
В следующем фрагменте кода показано, как использовать API LocalUseFallbackRippleImplementation CompositionLocal
:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Если вы используете собственную тему приложения, созданную на основе Material, вы можете безопасно предоставить локальную композицию как часть темы вашего приложения:
@OptIn(ExperimentalMaterialApi::class) @Composable fun MyAppTheme(content: @Composable () -> Unit) { CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme(content = content) } }
Дополнительные сведения см. в разделе «Обновление версии библиотеки материалов без миграции» .
Использование 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
для определения поведения пульсации на уровне всей темы. По сути, это была точка интеграции между местными компонентами системы индивидуального проектирования и Ripple. Вместо предоставления общего примитива темы, material-ripple
теперь предоставляет функцию createRippleModifierNode()
. Эта функция позволяет библиотекам системы проектирования создавать реализацию wrapper
более высокого порядка, которая запрашивает значения своей темы, а затем делегирует реализацию пульсации узлу, созданному этой функцией.
Это позволяет системам проектирования напрямую запрашивать то, что им нужно, и отображать любые необходимые настраиваемые пользователем слои тем сверху без необходимости соответствовать тому, что предоставляется на слое material-ripple
. Это изменение также делает более ясным, какой теме/спецификации соответствует Ripple, поскольку именно сам API Ripple определяет этот контракт, а не является неявным производным от темы.
Инструкции см. в реализации Ripple API в библиотеках материалов и замените вызовы локальных элементов композиции материалов, если это необходимо для вашей собственной системы дизайна.
Миграция с 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
В большинстве случаев вам следует использовать Modifier.indication
для отображения Indication
компонента. Однако в том редком случае, когда вы вручную создаете IndicationInstance
с помощью rememberUpdatedInstance
, вам необходимо обновить свою реализацию, чтобы проверить, является ли Indication
IndicationNodeFactory
, чтобы вы могли использовать более легкую реализацию. Например, Modifier.indication
будет внутренне делегировать созданному узлу, если это IndicationNodeFactory
. В противном случае он будет использовать Modifier.composed
для вызова rememberUpdatedInstance
.