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

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

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

Чтобы изменить спецификацию анимации, используемую для перемещения размера и положения, вы можете указать другой boundsTransform в Modifier.sharedElement() . Это обеспечивает начальную позицию Rect и целевую позицию 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» сохраняет свое окончательное состояние в начале анимации.

Клип и наложения

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

Он будет отображаться поверх других элементов пользовательского интерфейса, не являющихся общими, после завершения перехода элемент будет перенесен из наложения в собственный 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

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