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

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

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

Чтобы изменить спецификацию анимации, используемую для перемещения размера и положения, вы можете указать другой параметр 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 сначала измеряет дочерний макет с помощью ограничений lookahead (или target). Затем стабильный макет дочернего элемента масштабируется, чтобы вписаться в общие границы. 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

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

,

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

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

Чтобы изменить спецификацию анимации, используемую для перемещения размера и положения, вы можете указать другой параметр 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 сначала измеряет дочерний макет с помощью ограничений lookahead (или target). Затем стабильный макет дочернего элемента масштабируется, чтобы вписаться в общие границы. 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

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