Erros ocultos com transições de elemento compartilhado



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:

  1. Ative setReorderingAllowed(true) . Permite reordenar uma chamada aos métodos do ciclo de vida e sua animação será exibida corretamente.
  2. Chame addSharedElement() e adicione as visualizações que serão compartilhadas entre as telas
  3. Adicione android:transitionName exclusivo em cada visualização da transição
  4. Adicione sharedElementEnterTransition/sharedElementReturnTransition no fragmento de destino. Opcionalmente: para um melhor efeito, também podemos definir enterTransition/exitTransition .
  5. 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:

Animação quebrada


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() } // Initialization UI } 


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:

Animação atual


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() // this line ) 

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:

  1. Lembre-se da hierarquia dos fragmentos (não se esqueça do fragmento pai)
  2. No caso do RecyclerView, sempre crie nomes de transição dinamicamente (origem + destino)
  3. Desativar qualquer transformação Glide
  4. Faça chamadas postPostponeTransition() e startPostponedEnterTransition() corretamente em relação à sua lógica.

Animação final


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) ) } 

Source: https://habr.com/ru/post/pt463459/


All Articles