Personalizar a transição de elementos compartilhados

Para personalizar a execução da animação de transição de elemento compartilhado, há alguns parâmetros que podem ser usados para mudar a transição dos elementos compartilhados.

Especificação de animação

Para mudar a especificação de animação usada no movimento de tamanho e posição, especifique um parâmetro boundsTransform diferente em Modifier.sharedElement(). Isso fornece a posição inicial Rect e a posição de destino Rect.

Por exemplo, para fazer com que o texto no exemplo anterior se mova com um movimento em arco, especifique o parâmetro boundsTransform para usar uma especificação 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
    )
)

Você pode usar qualquer AnimationSpec. Este exemplo usa uma especificação keyframes.

Figura 1. Exemplo mostrando diferentes parâmetros boundsTransform

Modo de redimensionamento

Ao animar entre dois limites compartilhados, é possível definir o parâmetro resizeMode como RemeasureToBounds ou ScaleToBounds. Esse parâmetro determina como o elemento compartilhado faz a transição entre os dois estados. O ScaleToBounds primeiro mede o layout filho com as restrições de lookahead (ou destino). Em seguida, o layout estável da criança é dimensionado para caber nos limites compartilhados. ScaleToBounds pode ser considerado uma "escala gráfica" entre os estados.

Por outro lado, RemeasureToBounds mede e reestrutura o layout filho de sharedBounds com restrições fixas animadas com base no tamanho desejado. A nova medição é acionada pela mudança no tamanho dos limites, o que pode acontecer a cada frame.

Para elementos combináveis Text, recomendamos ScaleToBounds, porque ele evita o relayout e o reflow do texto em linhas diferentes. RemeasureToBounds é recomendado para limites com proporções diferentes e se você quiser uma continuidade fluida entre os dois elementos compartilhados.

A diferença entre os dois modos de redimensionamento pode ser vista nos exemplos a seguir:

ScaleToBounds

RemeasureToBounds

Pular para o layout final

Por padrão, ao fazer a transição entre dois layouts, o tamanho do layout é animado entre o estado inicial e o final. Esse pode ser um comportamento indesejável ao animar conteúdo, como texto.

O exemplo a seguir ilustra o texto de descrição "Lorem Ipsum" entrando na tela de duas maneiras diferentes. No primeiro exemplo, o texto é reformatado à medida que entra e o contêiner aumenta de tamanho. No segundo exemplo, o texto não é reformatado à medida que cresce. Adicionar Modifier.skipToLookaheadSize() evita o reflow à medida que ele cresce.

Sem Modifier.skipToLookahead(): observe o texto "Lorem Ipsum" sendo reformatado

Modifier.skipToLookahead(): observe que o texto "Lorem Ipsum" mantém o estado final no início da animação.

Clipes e sobreposições

Para que os elementos compartilhados sejam compartilhados entre diferentes elementos combináveis, a renderização do elemento combinável é elevada a uma sobreposição de camada quando a transição é iniciada para a correspondência no destino. O efeito disso é que ele vai escapar dos limites do elemento pai e das transformações de camada (por exemplo, o alfa e a escala).

Ele será renderizado sobre outros elementos da interface que não são compartilhados. Quando a transição terminar, o elemento será solto da sobreposição para o próprio DrawScope.

Para cortar um elemento compartilhado em uma forma, use a função padrão Modifier.clip(). Coloque-o depois de 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
)

Se você precisar garantir que um elemento compartilhado nunca seja renderizado fora de um contêiner pai, defina clipInOverlayDuringTransition em sharedElement(). Por padrão, para limites compartilhados aninhados, clipInOverlayDuringTransition usa o caminho de recorte do sharedBounds() pai.

Para manter elementos específicos da interface, como uma barra de navegação inferior ou um botão de ação flutuante, sempre na frente durante uma transição de elemento compartilhado, use Modifier.renderInSharedTransitionScopeOverlay(). Por padrão, esse modificador mantém o conteúdo na sobreposição durante o período em que a transição compartilhada está ativa.

Por exemplo, no Jetsnack, o BottomAppBar precisa ser colocado em cima do elemento compartilhado até que a tela não esteja mais visível. Adicionar o modificador ao elemento combinável o mantém elevado.

Sem Modifier.renderInSharedTransitionScopeOverlay()

Com Modifier.renderInSharedTransitionScopeOverlay()

Talvez você queira que o elemento combinável não compartilhado também seja animado e permaneça acima dos outros elementos combináveis antes da transição. Nesses casos, use renderInSharedTransitionScopeOverlay().animateEnterExit() para animar o combinável à medida que a transição de elemento compartilhado é executada:

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

Figura 2. A barra de apps inferior desliza para dentro e para fora conforme a animação faz a transição.

Em casos raros, se você não quiser que o elemento compartilhado seja renderizado em uma sobreposição, defina renderInOverlayDuringTransition em sharedElement() como "false".

Notificar layouts irmãos sobre mudanças no tamanho do elemento compartilhado

Por padrão, sharedBounds() e sharedElement() não notificam o contêiner pai sobre mudanças de tamanho durante as transições de layout.

Para propagar as mudanças de tamanho para o contêiner pai à medida que ele faz a transição, mude o parâmetro placeHolderSize para PlaceHolderSize.animatedSize. Isso faz com que o item aumente ou diminua. Todos os outros itens no layout respondem à mudança.

PlaceholderSize.contentSize (padrão)

PlaceholderSize.animatedSize

Observe como os outros itens da lista descem em resposta ao crescimento de um item.