Настройте переход общего элемента

Чтобы настроить анимацию перехода общих элементов, можно использовать несколько параметров, которые изменяют способ перехода общих элементов.

Спецификация анимации

Чтобы изменить спецификацию анимации, используемую для изменения размера и положения, можно указать другой параметр boundsTransform в Rect Modifier.sharedElement() . Это задаст начальное и конечное положение объекта Rect .

Например, чтобы текст в предыдущем примере двигался по дуге, укажите параметр boundsTransform для использования спецификации keyframes :

val textBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
    keyframes {
        durationMillis = boundsAnimationDurationMillis
        initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
        targetBounds at boundsAnimationDurationMillis
    }
}
Text(
    "Cupcake", fontSize = 28.sp,
    modifier = Modifier.sharedBounds(
        rememberSharedContentState(key = "title"),
        animatedVisibilityScope = animatedVisibilityScope,
        boundsTransform = textBoundsTransform
    )
)

Вы можете использовать любой AnimationSpec . В этом примере используется спецификация keyframes .

Рисунок 1. Пример, демонстрирующий различные параметры boundsTransform

Режим изменения размера

При анимации между двумя общими границами можно задать параметр resizeMode равным RemeasureToBounds или ScaleToBounds . Этот параметр определяет, как общий элемент переходит между двумя состояниями. ScaleToBounds сначала измеряет дочерний макет с помощью ограничений, заданных в прогнозируемом (или целевом) виде. Затем стабильный макет дочернего элемента масштабируется, чтобы уместиться в общих границах. ScaleToBounds можно рассматривать как «графическую шкалу» между состояниями.

В отличие от этого, RemeasureToBounds перемеряет и перерисовывает дочерний макет sharedBounds с анимированными фиксированными ограничениями на основе целевого размера. Переизмерение запускается при изменении размера границ, что потенциально может происходить в каждом кадре.

Для Text компонуемых элементов рекомендуется использовать ScaleToBounds , так как это позволяет избежать перекомпоновки и переформатирования текста на разные строки. RemeasureToBounds рекомендуется использовать для границ с разными соотношениями сторон, а также если требуется плавная связь между двумя общими элементами.

Разницу между двумя режимами изменения размера можно увидеть в следующих примерах:

ScaleToBounds

RemeasureToBounds

Перейти к окончательному макету

По умолчанию при переходе между двумя макетами размер макета анимируется от начального до конечного состояния. Это может быть нежелательным при анимации такого контента, как текст.

В следующем примере показано, как текст описания «Lorem Ipsum» попадает на экран двумя способами. В первом примере текст перестраивается по мере увеличения размера контейнера. Во втором примере перестраивается по мере увеличения размера. Добавление Modifier.skipToLookaheadSize() предотвращает перестроение по мере увеличения размера.

Нет Modifier.skipToLookahead() — обратите внимание на перекомпоновку текста «Lorem Ipsum».

Modifier.skipToLookahead() — обратите внимание, что текст «Lorem Ipsum» сохраняет своё конечное состояние в начале анимации.

Клип и накладки

Чтобы общие элементы могли совместно использоваться разными компонуемыми объектами, рендеринг компонуемого объекта поднимается до уровня наложения слоёв при начале перехода к его соответствующему объекту в целевом объекте. Это позволяет избежать ограничений родительского объекта и его преобразований слоёв (например, альфа-канала и масштаба).

Он будет отображаться поверх других необщих элементов пользовательского интерфейса. После завершения перехода элемент будет перемещен из области наложения в свою собственную DrawScope .

Чтобы обрезать общий элемент по форме, используйте стандартную функцию Modifier.clip() . Поместите её после sharedElement() :

Image(
    painter = painterResource(id = R.drawable.cupcake),
    contentDescription = "Cupcake",
    modifier = Modifier
        .size(100.dp)
        .sharedElement(
            rememberSharedContentState(key = "image"),
            animatedVisibilityScope = this@AnimatedContent
        )
        .clip(RoundedCornerShape(16.dp)),
    contentScale = ContentScale.Crop
)

Если вам нужно гарантировать, что общий элемент никогда не будет отображаться за пределами родительского контейнера, вы можете установить свойство clipInOverlayDuringTransition для sharedElement() . По умолчанию для вложенных общих границ clipInOverlayDuringTransition использует путь обрезки из родительского sharedBounds() .

Чтобы поддерживать отображение определённых элементов пользовательского интерфейса, таких как нижняя панель или плавающая кнопка действия, поверх других элементов во время перехода общего элемента, используйте Modifier.renderInSharedTransitionScopeOverlay() . По умолчанию этот модификатор сохраняет содержимое в оверлее во время активного перехода общего элемента.

Например, в Jetsnack элемент BottomAppBar необходимо разместить поверх общего элемента до тех пор, пока экран не станет невидимым. Добавление модификатора к компонуемому элементу сохраняет его приподнятым.

Без Modifier.renderInSharedTransitionScopeOverlay()

С Modifier.renderInSharedTransitionScopeOverlay()

Возможно, вам понадобится, чтобы ваш необщий составной элемент анимировался и исчезал, оставаясь поверх других составных элементов до перехода. В таких случаях используйте renderInSharedTransitionScopeOverlay().animateEnterExit() для анимации исчезновения составного элемента во время выполнения перехода общего элемента:

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

Рисунок 2. Нижняя панель приложения, появляющаяся и исчезающая при смене анимации.

В редком случае, когда вы не хотите, чтобы ваш общий элемент отображался в наложении, вы можете задать для свойства renderInOverlayDuringTransition в sharedElement() значение false.

Уведомлять родственные макеты об изменениях размера общего элемента

По умолчанию sharedBounds() и sharedElement() не уведомляют родительский контейнер о каких-либо изменениях размера при переходе макета.

Чтобы изменения размера передавались родительскому контейнеру при его перемещении, измените параметр placeHolderSize на PlaceHolderSize.animatedSize . Это приведет к увеличению или уменьшению размера элемента. Все остальные элементы макета отреагируют на это изменение.

PlaceholderSize.contentSize (по умолчанию)

PlaceholderSize.animatedSize

(Обратите внимание, как другие элементы в списке перемещаются вниз в ответ на рост одного элемента)