A API Fragment oferece duas maneiras de usar efeitos de movimento e transformações
para conectar fragmentos visualmente durante a navegação. Uma delas é o
framework de animação, que usa
Animation
e
Animator
. A
outra é o framework de transição, que inclui
transições de elementos compartilhados.
Você pode especificar efeitos personalizados para inserir e remover fragmentos e para transições de elementos compartilhados entre fragmentos.
- Um efeito de entrada determina como um fragmento entra na tela. Por exemplo, você pode criar um efeito para fazer com que o fragmento apareça deslizando da borda da tela ao navegar até ele.
- Um efeito de saída determina como um fragmento sai da tela. Por exemplo, você pode criar um efeito para esmaecer o fragmento ao navegar para sair dele.
- Uma transição de elemento compartilhado determina a forma como uma visualização compartilhada entre
dois fragmentos se move entre eles. Por exemplo, uma imagem exibida
em uma
ImageView
no fragmento A passa para o fragmento B quando este se torna visível.
Definir animações
Primeiro, é necessário criar animações para os efeitos de entrada e saída, que são executados ao navegar até um novo fragmento. Você pode definir animações como recursos de animação de interpolação. Esses recursos permitem que você defina como os fragmentos vão girar, expandir, esmaecer e se mover durante a animação. Por exemplo, talvez você queira que o fragmento atual esmaeça e o novo fragmento apareça deslizando do canto direito da tela, conforme mostrado na figura 1.
Essas animações podem ser definidas no diretório res/anim
:
<!-- res/anim/fade_out.xml -->
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="1"
android:toAlpha="0" />
<!-- res/anim/slide_in.xml -->
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="100%"
android:toXDelta="0%" />
Você também pode especificar animações para os efeitos de entrada e saída executados
ao exibir a pilha de retorno, o que pode acontecer quando o usuário toca no botão "Para cima" ou
"Voltar". Elas são chamadas de animações popEnter
e popExit
. Por
exemplo, quando um usuário volta para uma tela anterior, é interessante que o
fragmento atual deslize para fora da tela à direita e que o fragmento anterior
apareça.
Essas animações podem ser definidas da seguinte maneira:
<!-- res/anim/slide_out.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="0%"
android:toXDelta="100%" />
<!-- res/anim/fade_in.xml -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="0"
android:toAlpha="1" />
Depois de definir suas animações, use-as chamando
FragmentTransaction.setCustomAnimations()
,
transmitindo os recursos da animação pelo ID de recurso, conforme mostrado
no exemplo a seguir:
Kotlin
supportFragmentManager.commit { setCustomAnimations( R.anim.slide_in, // enter R.anim.fade_out, // exit R.anim.fade_in, // popEnter R.anim.slide_out // popExit ) replace(R.id.fragment_container, fragment) addToBackStack(null) }
Java
Fragment fragment = new FragmentB(); getSupportFragmentManager().beginTransaction() .setCustomAnimations( R.anim.slide_in, // enter R.anim.fade_out, // exit R.anim.fade_in, // popEnter R.anim.slide_out // popExit ) .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit();
Definir transições
Você também pode usar transições para definir efeitos de entrada e saída. Essas transições podem ser definidas em arquivos de recurso XML. Por exemplo, você pode querer que o fragmento atual esmaeça e o novo fragmento apareça deslizando da borda direita da tela. Essas transições podem ser definidas da seguinte forma:
<!-- res/transition/fade.xml -->
<fade xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"/>
<!-- res/transition/slide_right.xml -->
<slide xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:slideEdge="right" />
Depois de definir as transições, aplique-as chamando
setEnterTransition()
no fragmento de entrada e
setExitTransition()
no fragmento de saída, passando os recursos de transição inflada
pelo ID de recurso, conforme mostrado no exemplo a seguir:
Kotlin
class FragmentA : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val inflater = TransitionInflater.from(requireContext()) exitTransition = inflater.inflateTransition(R.transition.fade) } } class FragmentB : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.slide_right) } }
Java
public class FragmentA extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TransitionInflater inflater = TransitionInflater.from(requireContext()); setExitTransition(inflater.inflateTransition(R.transition.fade)); } } public class FragmentB extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TransitionInflater inflater = TransitionInflater.from(requireContext()); setEnterTransition(inflater.inflateTransition(R.transition.slide_right)); } }
Os fragmentos são compatíveis com transições do AndroidX. Embora os fragmentos também sejam compatíveis com transições de framework, é altamente recomendável usar transições do AndroidX, que são compatíveis com APIs de nível 14 e mais recentes e contêm correções de bugs que não estão presentes em versões anteriores de transições de framework.
Usar transições de elementos compartilhados
As transições de elementos compartilhados fazem parte
do framework de transição e determinam como as visualizações correspondentes se movem entre
dois fragmentos durante uma transição.
Por exemplo, talvez você queira que uma
imagem seja exibida em uma ImageView
no fragmento A, para fazer a transição para o fragmento B
quando este se tornar visível, conforme mostrado na figura 3.
De modo geral, faça uma transição de fragmentos com elementos compartilhados desta forma:
- Atribua um nome de transição exclusivo para cada visualização de elemento compartilhado.
- Adicione visualizações de elementos compartilhados e nomes de transição à
FragmentTransaction
. - Defina uma animação de transição de elemento compartilhado.
Primeiro, é necessário atribuir um nome de transição exclusivo para cada visualização de elemento compartilhado
a fim de permitir que as visualizações sejam mapeadas de um fragmento para o próximo. Defina um
nome de transição em elementos compartilhados em cada layout de fragmento usando
ViewCompat.setTransitionName()
,
que oferece compatibilidade com as APIs de nível 14 e mais recentes.
Como exemplo, o nome de transição de uma ImageView
nos fragmentos A e B
pode ser atribuído da seguinte maneira:
Kotlin
class FragmentA : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... val itemImageView = view.findViewById<ImageView>(R.id.item_image) ViewCompat.setTransitionName(itemImageView, “item_image”) } } class FragmentB : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... val heroImageView = view.findViewById<ImageView>(R.id.hero_image) ViewCompat.setTransitionName(heroImageView, “hero_image”) } }
Java
public class FragmentA extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... ImageView itemImageView = view.findViewById(R.id.item_image); ViewCompat.setTransitionName(itemImageView, “item_image”); } } public class FragmentB extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... ImageView heroImageView = view.findViewById(R.id.hero_image); ViewCompat.setTransitionName(heroImageView, “hero_image”); } }
Para incluir seus elementos compartilhados na transição de fragmento, sua
FragmentTransaction
precisa saber como as visualizações de cada elemento compartilhado são mapeadas de um
fragmento para o próximo. Adicione cada um dos elementos compartilhados à
FragmentTransaction
chamando
FragmentTransaction.addSharedElement()
e
transmitindo a visualização e o nome da transição da visualização correspondente no
próximo fragmento, conforme mostrado no exemplo a seguir:
Kotlin
val fragment = FragmentB() supportFragmentManager.commit { setCustomAnimations(...) addSharedElement(itemImageView, “hero_image”) replace(R.id.fragment_container, fragment) addToBackStack(null) }
Java
Fragment fragment = new FragmentB(); getSupportFragmentManager().beginTransaction() .setCustomAnimations(...) .addSharedElement(itemImageView, “hero_image”) .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit();
Para especificar como seus elementos compartilhados fazem a transição de um fragmento para o outro,
é necessário definir uma transição de entrada no fragmento
de destino. Chame
Fragment.setSharedElementEnterTransition()
no método onCreate()
do fragmento, conforme mostrado no exemplo a seguir:
Kotlin
class FragmentB : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedElementEnterTransition = TransitionInflater.from(requireContext()) .inflateTransition(R.transition.shared_image) } }
Java
public class FragmentB extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Transition transition = TransitionInflater.from(requireContext()) .inflateTransition(R.transition.shared_image); setSharedElementEnterTransition(transition); } }
A transição shared_image
é definida da seguinte maneira:
<!-- res/transition/shared_image.xml -->
<transitionSet>
<changeImageTransform />
</transitionSet>
Todas as subclasses de Transition
são compatíveis como transições de elementos compartilhados. Caso
você queira criar uma Transition
personalizada, consulte
Criar uma animação de transição personalizada.
changeImageTransform
, usada no exemplo anterior, é uma das traduções pré-criadas disponíveis
para uso. Você pode encontrar outras subclasses Transition
na referência da API para a
classe Transition
.
Por padrão, a transição de entrada do elemento compartilhado também é usada como a
transição de retorno dos elementos compartilhados. A transição de retorno determina como
os elementos compartilhados voltam para o fragmento anterior quando a transação
do fragmento é retirada da pilha de retorno. Caso queira especificar uma transição de
retorno diferente, faça isso usando
Fragment.setSharedElementReturnTransition()
no método onCreate()
do fragmento.
Compatibilidade com volta preditiva
Você pode usar a volta preditiva com muitas animações entre fragmentos, mas não todas. Ao implementar a volta preditiva, tenha em mente as seguintes considerações:
- Importe
Transitions 1.5.0
ou uma versão mais recente eFragments 1.7.0
ou uma mais recente. - A classe
Animator
, as subclasses e a biblioteca AndroidX Transition estão suporte. - Não há suporte para a classe
Animation
e a bibliotecaTransition
do framework. - Animações de fragmentos preditivos só funcionam em dispositivos com o Android 14 ou mais alto.
setCustomAnimations
,setEnterTransition
esetExitTransition
.setReenterTransition
,setReturnTransition
.setSharedElementEnterTransition
esetSharedElementReturnTransition
são é compatível com a volta preditiva.
Para saber mais, consulte Suporte a animações de volta preditiva
Adiamento de transições
Em alguns casos, pode ser necessário adiar a transição de fragmento por um curto período. Por exemplo, pode ser necessário aguardar até que todas as visualizações no fragmento de entrada sejam medidas e definidas para que o Android possa capturar com precisão os estados inicial e final da transição.
Além disso, a transição pode precisar ser adiada até que alguns dados necessários sejam carregados. Por exemplo, talvez seja necessário aguardar até que as imagens tenham sido carregadas para os elementos compartilhados. Caso contrário, a transição poderá ficar instável se uma imagem terminar de ser carregada durante ou após a transição.
Para adiar uma transição, é necessário garantir que a transação
do fragmento permita reordenar as mudanças de estado do fragmento. Para permitir reordenar
as mudanças de estado do fragmento, chame
FragmentTransaction.setReorderingAllowed()
,
conforme mostrado no exemplo a seguir:
Kotlin
val fragment = FragmentB() supportFragmentManager.commit { setReorderingAllowed(true) setCustomAnimation(...) addSharedElement(view, view.transitionName) replace(R.id.fragment_container, fragment) addToBackStack(null) }
Java
Fragment fragment = new FragmentB(); getSupportFragmentManager().beginTransaction() .setReorderingAllowed(true) .setCustomAnimations(...) .addSharedElement(view, view.getTransitionName()) .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit();
Para adiar a transição de entrada, chame
Fragment.postponeEnterTransition()
no método onViewCreated()
do fragmento de entrada:
Kotlin
class FragmentB : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... postponeEnterTransition() } }
Java
public class FragmentB extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... postponeEnterTransition(); } }
Depois que você carregar os dados e estiver tudo pronto para iniciar a transição,
chame Fragment.startPostponedEnterTransition()
.
O exemplo a seguir usa a
biblioteca Glide (link em inglês) para carregar uma imagem
em uma ImageView
compartilhada, adiando a transição correspondente até que o carregamento
da imagem seja concluído.
Kotlin
class FragmentB : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... Glide.with(this) .load(url) .listener(object : RequestListener<Drawable> { override fun onLoadFailed(...): Boolean { startPostponedEnterTransition() return false } override fun onResourceReady(...): Boolean { startPostponedEnterTransition() return false } }) .into(headerImage) } }
Java
public class FragmentB extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... Glide.with(this) .load(url) .listener(new RequestListener<Drawable>() { @Override public boolean onLoadFailed(...) { startPostponedEnterTransition(); return false; } @Override public boolean onResourceReady(...) { startPostponedEnterTransition(); return false; } }) .into(headerImage) } }
Ao lidar com casos como conexão de Internet lenta do usuário, pode ser
necessário que a transição adiada seja iniciada após determinado período,
em vez de esperar que todos os dados sejam carregados. Para essas situações, você pode
chamar
Fragment.postponeEnterTransition(long, TimeUnit)
no método onViewCreated()
do fragmento de entrada, transmitindo a duração
e a unidade de tempo. A transição adiada será iniciada automaticamente
após o tempo especificado.
Usar transições de elementos compartilhados com uma RecyclerView
As transições de entrada adiadas não serão iniciadas até que todas as visualizações
no fragmento de entrada sejam medidas e definidas. Ao usar uma
RecyclerView
, é necessário esperar
que todos os dados sejam carregados e que os itens RecyclerView
estejam prontos para serem desenhados
antes de iniciar a transição. Veja um exemplo:
Kotlin
class FragmentA : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { postponeEnterTransition() // Wait for the data to load viewModel.data.observe(viewLifecycleOwner) { // Set the data on the RecyclerView adapter adapter.setData(it) // Start the transition once all views have been // measured and laid out (view.parent as? ViewGroup)?.doOnPreDraw { startPostponedEnterTransition() } } } }
Java
public class FragmentA extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { postponeEnterTransition(); final ViewGroup parentView = (ViewGroup) view.getParent(); // Wait for the data to load viewModel.getData() .observe(getViewLifecycleOwner(), new Observer<List<String>>() { @Override public void onChanged(List<String> list) { // Set the data on the RecyclerView adapter adapter.setData(it); // Start the transition once all views have been // measured and laid out parentView.getViewTreeObserver() .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw(){ parentView.getViewTreeObserver() .removeOnPreDrawListener(this); startPostponedEnterTransition(); return true; } }); } }); } }
Um
ViewTreeObserver.OnPreDrawListener
é definido no pai da visualização de fragmento. Isso serve para garantir que todas as
visualizações do fragmento tenham sido medidas e definidas e estejam prontas para
serem desenhadas antes de iniciar a transição de entrada adiada.
Outro ponto a ser considerado ao usar transições de elementos compartilhados com uma
RecyclerView
é que não é possível definir o nome de transição no
layout XML do item RecyclerView
, porque um número arbitrário de itens compartilha esse layout. Um nome de transição exclusivo precisa ser atribuído para que
a animação de transição use a visualização correta.
Você pode dar um nome de transição exclusivo ao elemento compartilhado de cada item,
atribuindo o nome quando ViewHolder
estiver vinculado. Por exemplo, se os dados de
cada item incluem um ID exclusivo, esse ID pode ser usado como o nome da transição, conforme
mostrado no exemplo a seguir:
Kotlin
class ExampleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val image = itemView.findViewById<ImageView>(R.id.item_image) fun bind(id: String) { ViewCompat.setTransitionName(image, id) ... } }
Java
public class ExampleViewHolder extends RecyclerView.ViewHolder { private final ImageView image; ExampleViewHolder(View itemView) { super(itemView); image = itemView.findViewById(R.id.item_image); } public void bind(String id) { ViewCompat.setTransitionName(image, id); ... } }
Outros recursos
Para saber mais sobre transições de fragmentos, consulte os recursos a seguir.
Amostras
Postagens do blog
- Transições contínuas de elementos compartilhados: RecyclerView para ViewPager (link em inglês)
- Transições de fragmentos (link em inglês)