Personalizar a transição de elementos compartilhados

Para personalizar a execução da animação de transição de elemento compartilhado, há alguns 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 para o 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 o texto no exemplo anterior se mover com um arco movimento, 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, você pode definir o parâmetro resizeMode. como RemeasureToBounds ou ScaleToBounds. Esse parâmetro determina como o elemento compartilhado transita entre os dois estados. ScaleToBounds primeiro mede o layout filho com as restrições de análise antecipada (ou destino). Em seguida, o layout estável da filha é dimensionado para caber nos limites compartilhados. ScaleToBounds pode ser considerada uma "escala gráfica" entre os estados.

Já o RemeasureToBounds mede e reorganiza o layout filho dos sharedBounds com restrições fixas animadas com base no tamanho de destino. A reavaliação é acionada pela mudança de tamanho dos limites, que pode ser de cada frame.

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

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 os estados inicial e final. Isso pode ser um comportamento indesejável quando com animação de conteúdo, como texto.

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

Não há Modifier.skipToLookahead() - observe o fluxo de texto "Lorem Ipsum"

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

Recorte e sobreposições

Um conceito importante ao criar elementos compartilhados no Compose é que, para para compartilhar entre diferentes elementos combináveis, a renderização do o elemento combinável é elevado a uma sobreposição de camada quando a transição é iniciada para sua correspondência no destino. O efeito disso é que ele vai escapar dos limites do pai e das transformações de camada (por exemplo, o Alfa e a escala).

Ele será renderizado sobre outros elementos de interface não compartilhados. Quando a transição for concluída, o elemento será removido da sobreposição para o próprio DrawScope.

Para recortar um elemento compartilhado em uma forma, use o Modifier.clip() padrão função. 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 elemento pai contêiner, é possível definir clipInOverlayDuringTransition em sharedElement(). De Por padrão, para limites compartilhados aninhados, clipInOverlayDuringTransition usa o clipe caminho do sharedBounds() pai.

oferecer suporte à manutenção de elementos da interface específicos, como uma barra na parte de baixo ou uma ação flutuante. sempre na parte superior durante uma transição de elemento compartilhado, use Modifier.renderInSharedTransitionScopeOverlay() Por padrão, mantém o conteúdo na sobreposição durante o tempo em que o modificador transição está ativa.

Por exemplo, no Jetsnack, a BottomAppBar precisa ser colocada sobre o elemento compartilhado até que a tela não esteja visível. Adicionar o modificador no elemento combinável o mantém elevado.

Sem Modifier.renderInSharedTransitionScopeOverlay()

Com Modifier.renderInSharedTransitionScopeOverlay()

Às vezes, você pode querer que o elemento combinável não compartilhado também seja animado, assim como permaneçam sobre os outros elementos combináveis antes da transição. Nesses casos, use renderInSharedTransitionScopeOverlay().animateEnterExit() para animar o do elemento combinável enquanto a transição do elemento compartilhado é executada:

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

Figura 2.Barra de apps inferior deslizando para dentro e para fora conforme as transições da animação

No caso raro em que você quer que o elemento compartilhado não seja renderizado em uma sobreposição, defina renderInOverlayDuringTransition em sharedElement() como falso.

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 nenhuma mudança de tamanho à medida que o layout transita.

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

PlaceholderSize.contentSize (padrão)

PlaceholderSize.animatedSize

(Observe como os outros itens na lista se movem para baixo em resposta ao crescimento de um item)