Чтобы настроить запуск анимации перехода общего элемента, можно использовать несколько параметров, позволяющих изменить способ перехода общих элементов.
Параметры анимации
Чтобы изменить спецификацию анимации, используемую для перемещения размера и положения, вы можете указать другой параметр 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 значение SharedTransitionScope.ResizeMode.RemeasureToBounds или SharedTransitionScope.ResizeMode.scaleToBounds(). Этот параметр определяет,
как общий элемент переходит между двумя состояниями. scaleToBounds()
сначала измеряет дочерний макет с ограничениями упреждающего просмотра (или целевыми ограничениями).
Затем стабильный макет дочернего элемента масштабируется, чтобы соответствовать общим границам.
scaleToBounds() можно представить как графическую шкалу между состояниями.
В отличие от него, RemeasureToBounds повторно измеряет и перекомпоновывает дочерний макет sharedBounds с анимированными фиксированными ограничениями на основе целевого размера. Повторное измерение запускается изменением размера границ, которое потенциально может происходить в каждом кадре.
Для composable-функций Text рекомендуется использовать scaleToBounds(), чтобы избежать изменения макета и переноса текста на другие строки. RemeasureToBounds рекомендуется для границ с разными соотношениями сторон и если вы хотите обеспечить плавную непрерывность между двумя общими элементами.
Разница между двумя режимами изменения размера показана в примерах ниже.
|
|
|---|---|
Динамически включайте и отключайте общие элементы
По умолчанию sharedElement() и sharedBounds() настроены на анимацию изменений макета всякий раз, когда в целевом состоянии обнаруживается соответствующий ключ. Однако вы можете отключить эту анимацию динамически в зависимости от определенных условий, таких как направление навигации или текущее состояние пользовательского интерфейса.
Чтобы управлять тем, происходит ли переход общего элемента, вы можете настроить SharedContentConfig, переданный в rememberSharedContentState(). Свойство isEnabled определяет, активен ли общий элемент.
В примере ниже показано, как настроить общий переход только между определенными экранами (например, от А к Б) и отключить его для остальных.
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. Объект будет увеличиваться или уменьшаться. Все остальные элементы макета реагируют на изменения.
|
(Обратите внимание, как другие элементы в списке перемещаются вниз в ответ на рост одного элемента) |
|---|---|