Чтобы настроить запуск анимации перехода общего элемента, можно использовать несколько параметров, позволяющих изменить способ перехода общих элементов.
Параметры анимации
Чтобы изменить спецификацию анимации, используемую для перемещения размера и положения, вы можете указать другой параметр 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.
boundsTransformРежим изменения размера
При анимации между двумя общими границами вы можете установить для параметра resizeMode значение RemeasureToBounds или ScaleToBounds. Этот параметр определяет, как общий элемент переходит между двумя состояниями. ScaleToBounds сначала измеряет дочерний макет с ограничениями предварительного просмотра (или целевыми). Затем стабильный макет дочернего элемента масштабируется, чтобы поместиться в общие границы.
ScaleToBounds можно представить как графическую шкалу между состояниями.
В отличие от него, RemeasureToBounds повторно измеряет и перекомпоновывает дочерний макет sharedBounds с анимированными фиксированными ограничениями на основе целевого размера. Повторное измерение запускается изменением размера границ, которое потенциально может происходить в каждом кадре.
Для composable-функций Text рекомендуется использовать ScaleToBounds, чтобы избежать изменения макета и переноса текста на другие строки. RemeasureToBounds рекомендуется для границ с разным соотношением сторон и если вы хотите обеспечить плавную непрерывность между двумя общими элементами.
Разница между двумя режимами изменения размера показана в примерах ниже.
|
|
|---|---|
Как динамически включать и отключать общие элементы
По умолчанию sharedElement() и sharedBounds() настроены на анимацию изменений макета всякий раз, когда в целевом состоянии обнаруживается соответствующий ключ. Однако вы можете отключить эту анимацию динамически в зависимости от конкретных условий, таких как направление навигации или текущее состояние пользовательского интерфейса.
Чтобы управлять тем, происходит ли переход общего элемента, вы можете настроить SharedContentConfig, переданный в rememberSharedContentState(). Свойство isEnabled определяет, активен ли общий элемент.
В следующем примере показано, как определить конфигурацию, которая включает общий переход только при навигации между определенными экранами (например, только от A к B), и отключает его для других.
SharedTransitionLayout { val transition = updateTransition(currentState) transition.AnimatedContent { targetState -> // Create the configuration that depends on state changing. fun animationConfig() : SharedTransitionScope.SharedContentConfig { return object : SharedTransitionScope.SharedContentConfig { override val SharedTransitionScope.SharedContentState.isEnabled: Boolean // For this example, we only enable the transition in one direction // from A -> B and not the other way around. get() = transition.currentState == "A" && transition.targetState == "B" } } when (targetState) { "A" -> Box( modifier = Modifier .sharedElement( rememberSharedContentState( key = "shared_box", config = animationConfig() ), animatedVisibilityScope = this ) // ... ) { // Your content } "B" -> { Box( modifier = Modifier .sharedElement( rememberSharedContentState( key = "shared_box", config = animationConfig() ), animatedVisibilityScope = this ) // ... ) { // Your content } } } } }
По умолчанию, если общий элемент отключен во время текущей анимации, он по-прежнему завершает текущую выполняющуюся анимацию, чтобы предотвратить случайное удаление выполняющихся анимаций. Если вам нужно удалить элемент во время анимации, вы можете переопределить shouldKeepEnabledForOngoingAnimation в интерфейсе SharedContentConfig, чтобы возвращалось значение false.
Перейти к окончательному макету
По умолчанию при переходе между двумя макетами размер макета анимируется между его начальным и конечным состоянием. Это может быть нежелательным поведением при анимации контента, например текста.
В примере ниже показано, как текст описания Lorem Ipsum появляется на экране двумя разными способами. В первом примере текст переформатируется по мере того, как он вводится, а контейнер увеличивается в размере. Во втором примере текст не переносится по мере увеличения. Добавление Modifier.skipToLookaheadSize() предотвращает перекомпоновку по мере роста.
Нет |
|
|---|---|
Клипы и наложения
Чтобы общие элементы могли использоваться в разных composable-функциях, рендеринг composable-функции переносится в оверлей слоя, когда начинается переход к соответствующему элементу в целевом объекте. В результате он выйдет за границы родительского элемента и его преобразований слоя (например, альфа-канала и масштаба).
Оно будет отображаться поверх других элементов интерфейса, не относящихся к общему экрану. После завершения перехода элемент будет удален из оверлея и помещен в собственный 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 нужно разместить поверх общего элемента, пока экран не станет видимым. Добавление модификатора к composable-функции позволяет сохранить ее приподнятой.
Не указана |
Билеты: |
|---|---|
Вы можете захотеть, чтобы ваша необщая composable-функция анимировалась, а также оставалась поверх других composable-функций до перехода. В таких случаях используйте renderInSharedTransitionScopeOverlay().animateEnterExit(), чтобы анимировать composable-функцию, пока выполняется переход общего элемента:
JetsnackBottomBar( modifier = Modifier .renderInSharedTransitionScopeOverlay( zIndexInOverlay = 1f, ) .animateEnterExit( enter = fadeIn() + slideInVertically { it }, exit = fadeOut() + slideOutVertically { it } ) )
В редких случаях, когда вы не хотите, чтобы общий элемент отображался в оверлее, вы можете установить для renderInOverlayDuringTransition в sharedElement() значение false.
Уведомлять макеты одного уровня об изменениях размера общего элемента
По умолчанию sharedBounds() и sharedElement() не уведомляют родительский контейнер об изменении размера при переходе макета.
Чтобы изменения размера передавались родительскому контейнеру во время перехода, измените параметр placeholderSize на PlaceholderSize.AnimatedSize. Объект увеличится или уменьшится. Все остальные элементы макета реагируют на изменения.
|
(Обратите внимание, как другие элементы в списке перемещаются вниз в ответ на рост одного элемента) |
|---|---|