
Há uma boa prática para tornar seu aplicativo bonito e dinâmico e hoje em dia existem muitas ferramentas e maneiras de conseguir isso. Um deles é a transição de elemento compartilhado.
Neste artigo, abordarei alguns erros que me custaram muito tempo; Vou mostrar como evitá-los se você decidir implementar esse tipo de transição com fragmentos no aplicativo.
Começar
Antes de fazer a animação, li dezenas de artigos, mas a maioria era sobre Transição de Atividades. No entanto, me deparei com alguns realmente bons sobre Fragments e quero dar uma pequena recapitulação sobre como criar a transição de elemento compartilhado.
Aqui estão as principais etapas para criar animação:
- Ative
setReorderingAllowed(true)
. Permite reordenar uma chamada aos métodos do ciclo de vida e sua animação será exibida corretamente. - Chame
addSharedElement()
e adicione as visualizações que serão compartilhadas entre as telas - Adicione
android:transitionName
exclusivo em cada visualização da transição - Adicione
sharedElementEnterTransition/sharedElementReturnTransition
no fragmento de destino. Opcionalmente: para um melhor efeito, também podemos definir enterTransition/exitTransition
. - Adicione
postponeEnterTransition/startPostponedEnterTransition
para definir o momento em que os dados são carregados e a interface do usuário está pronta para ser desenhada
Parece que isso é suficiente para criar animação e deixar seus designers e usuários felizes. Mas sempre há alguns acidentes. Vamos dar uma olhada no que teremos se seguirmos as etapas listadas acima:
Não é o que esperávamos. Vamos descobrir.
Erro # 1. Transição estáticaNomes (meio dia desperdiçado)
Como eu disse antes, nossos modos de exibição devem ter nomes de transição exclusivos - caso contrário, a estrutura de transição não será capaz de reconhecer qual modo de exibição participa da transição e o fará sem eles. Então, onde está um problema?
A coisa é RecyclerView. É isso.
Se houver RecyclerView e uma exibição animada fizer parte do item RecyclerView, você deverá criar e definir o
transitionName
dinamicamente (esqueça o XML). Além disso, você deve fazer isso nos dois fragmentos, mesmo que o segundo não tenha o RecyclerView.
Portanto, a correção é:
val transitionNameImage = context.getString(R.string.transition_image, title)
Você deve ter notado que coloquei "title" como argumento para obter um nome único. É melhor usar o modelo de domínio em vez de, por exemplo, a posição do item na tela, porque não há necessidade de passar esses nomes como argumentos para o Bundle e analisá-los a partir do segundo fragmento.
Erro # 2. Não considerando o fragmento pai (1,5 dias desperdiçados)
Eu sei, você pode perguntar "Por que?" Mas quando eu estava lendo os artigos sobre animação compartilhada, ninguém considerou um exemplo na hierarquia de fragmentos complexos. É por isso que às vezes você pode não prestar atenção.
Portanto, meu primeiro fragmento era filho do contêiner do fragmento e não admira que
postponeEnterTransition()/startPostponedEnterTransition()
não tenha efeito.
Aqui está override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) parentFragment?.also { parentFragment -> NewsTransitioner.setupFirstFragment(parentFragment) parentFragment.postponeEnterTransition() }
O que você precisa fazer aqui é chamar esses métodos do fragmento pai.
Erro # 3. Deslizamento subestimado (2 dias desperdiçados)
"Ok, estou aprendendo a fazer transição compartilhada, quando chamar os métodos necessários em relação ao ciclo de vida e ao carregamento. Desta vez, vai funcionar!
Bem, eu estava errado. Este talvez seja o erro mais complicado que já enfrentei. Vamos dar uma olhada no que temos até agora:
Você pode notar que há uma falha estranha com a transição de entrada. Quando começa, a imagem já mudou de matriz e depois é só passar para a posição final.
Não quero descrever toda a investigação aqui. Para encurtar a história, tive a sorte de encontrar
um belo artigo .
Onde encontrei solução. Aqui está:
“Temos essa falha porque o Glide tenta otimizar o carregamento da imagem. Por padrão, o Glide está redimensionando e aparando imagens para corresponder à exibição de destino. ”
Para corrigi-lo, adicionei, sem brincadeiras, uma única linha de código como esta para inicializar a cadeia do Glide:
Glide .with(target) .load(url) .apply( RequestOptions().dontTransform()
Portanto, você deve desativar as transformações do Glide nas imagens, se estiverem envolvidas em uma transição compartilhada.
Erro # 4. Gerenciando incorretamente postPostponeTransition ()
Honestamente, não é exatamente um erro, mas ainda suponho que seria bom mencionar.
Quando se trata de gerenciar os
postPostponeTransition()
e
startPostponedEnterTransition()
, você deve selecionar o momento certo. O momento é quando a interface do usuário já deve ser desenhada.
Existem dois pontos principais que devemos saber antes de chamar os métodos:
- por um lado, quando as imagens com transição estão carregadas e prontas
- por outro lado, a hierarquia de visualizações é medida e definida
Para imagens, geralmente usamos o Glide e ele tem um ouvinte sofisticado:
RequestListener<Drawable> { override fun onLoadFailed( e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean ): Boolean { startPostponedEnterTransition() return false } override fun onResourceReady( resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean ): Boolean { startPostponedEnterTransition() return false } }
Observe que chamamos
startPostponedEnterTransition()
nos dois casos: sucesso e erro. É vital porque, se não o fizermos, seu aplicativo congelará.
Nos casos em que a transição ocorre sem nenhuma imagem, você pode usar o método de extensão
doOnPreDraw()
do ktx no layout raiz e
startPostponedEnterTransition()
por lá.
BÔNUS : Falando em RecyclerView, não basta adicionar um ouvinte para imagens. Devemos manter uma posição de item do RecyclerView de onde a transição começa. Quando o usuário retornar à tela anterior, devemos comparar a posição carregada da imagem com a posição retida no ouvinte e iniciar a transição somente quando corresponderem.
Juntando tudo
Neste artigo, mostrei algumas dicas que você pode enfrentar ao implementar uma transição compartilhada com fragmentos e as maneiras de lidar com eles.
Resumidamente, aqui estão eles:
- Lembre-se da hierarquia dos fragmentos (não se esqueça do fragmento pai)
- No caso do RecyclerView, sempre crie nomes de transição dinamicamente (origem + destino)
- Desativar qualquer transformação Glide
- Faça chamadas
postPostponeTransition()
e startPostponedEnterTransition()
corretamente em relação à sua lógica.
Obrigado pela leitura e até a próxima!
PS Se você quer saber como animar cantos de imagem,
aqui o código , basta adicionar ao resto as transições de animação compartilhada.
Exemplo fragment.sharedElementEnterTransition = TransitionSet().apply { addTransition(ChangeImageTransform()) addTransition(ChangeBounds()) addTransition(ChangeTransform()) addTransition(ChangeOutlineRadiusTransition(animationRadiusData.startRadius, animationRadiusData.endRadius) ) }