Enquanto o app está sendo usado, novas informações aparecem na tela e as informações antigas são removidas. Mudar o que é mostrado na tela imediatamente pode ser chocante, e os usuários podem perder conteúdo novo que aparece repentinamente. As animações atrasam as mudanças e chamam a atenção do usuário com movimento para que as atualizações sejam mais óbvias.
Há três animações comuns que você pode usar para mostrar ou ocultar uma visualização: animações de revelação, crossfade e animação de virada de cartão.
Criar uma animação de fading cruzado
Uma animação de crossfade, também conhecida como dissolver, esmaece gradualmente
uma View
ou
ViewGroup
e, ao mesmo tempo,
acompanha outra. Essa animação é útil para situações em que você quer
alternar conteúdo ou visualizações no seu app. A animação de crossfade mostrada aqui usa
ViewPropertyAnimator
,
que está disponível para o Android 3.1 (API de nível 12) e versões mais recentes.
Veja um exemplo de crossfade de um indicador de progresso para conteúdo de texto:
Criar as visualizações
Crie as duas visualizações em que você quer fazer a transição de crossfade. O exemplo abaixo cria um indicador de progresso e uma visualização de texto rolável:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView style="?android:textAppearanceMedium"
android:lineSpacingMultiplier="1.2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lorem_ipsum"
android:padding="16dp" />
</ScrollView>
<ProgressBar android:id="@+id/loading_spinner"
style="?android:progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
Configurar a animação de fading cruzado
Para configurar a animação de crossfade, faça o seguinte:
- Crie variáveis de membro para as visualizações que você quer usar na animação. Você vai precisar dessas referências mais tarde ao modificar as visualizações durante a animação.
- Defina a visibilidade da visualização que está sendo mostrada como
GONE
. Isso evita que a visualização use o espaço de layout e a omite dos cálculos de layout, o que acelera o processamento. - Armazene em cache a propriedade do sistema
config_shortAnimTime
em uma variável de membro. Essa propriedade define uma duração "curta" padrão para a animação. Essa duração é ideal para animações sutis ou animações que ocorrem com frequência.config_longAnimTime
econfig_mediumAnimTime
também estão disponíveis.
Confira um exemplo que usa o layout do snippet de código anterior como a visualização de conteúdo da atividade:
Kotlin
class CrossfadeActivity : Activity() { private lateinit var contentView: View private lateinit var loadingView: View private var shortAnimationDuration: Int = 0 ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_crossfade) contentView = findViewById(R.id.content) loadingView = findViewById(R.id.loading_spinner) // Initially hide the content view. contentView.visibility = View.GONE // Retrieve and cache the system's default "short" animation time. shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime) } ... }
Java
public class CrossfadeActivity extends Activity { private View contentView; private View loadingView; private int shortAnimationDuration; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_crossfade); contentView = findViewById(R.id.content); loadingView = findViewById(R.id.loading_spinner); // Initially hide the content view. contentView.setVisibility(View.GONE); // Retrieve and cache the system's default "short" animation time. shortAnimationDuration = getResources().getInteger( android.R.integer.config_shortAnimTime); } ... }
Fading cruzado nas visualizações
Quando as visualizações estiverem configuradas corretamente, faça o fading cruzado da seguinte forma:
- Para a visualização que será mostrada, defina o valor Alfa como 0 e a visibilidade
como
VISIBLE
desde a configuração inicial deGONE
. Isso torna a visualização visível, mas transparente. - Para a visualização que será mostrada, anime o valor Alfa de 0 a 1. Para a visualização que desaparecerá, anime o valor Alfa de 1 a 0.
- Usando
onAnimationEnd()
em umaAnimator.AnimatorListener
, defina a visibilidade da visualização que está desaparecendo comoGONE
. Mesmo que o valor Alfa seja 0, definir a visibilidade da visualização comoGONE
impede que ela use o espaço de layout e a omite dos cálculos de layout, o que acelera o processamento.
O método a seguir mostra um exemplo de como fazer isso:
Kotlin
class CrossfadeActivity : Activity() { private lateinit var contentView: View private lateinit var loadingView: View private var shortAnimationDuration: Int = 0 ... private fun crossfade() { contentView.apply { // Set the content view to 0% opacity but visible, so that it is // visible but fully transparent during the animation. alpha = 0f visibility = View.VISIBLE // Animate the content view to 100% opacity and clear any animation // listener set on the view. animate() .alpha(1f) .setDuration(shortAnimationDuration.toLong()) .setListener(null) } // Animate the loading view to 0% opacity. After the animation ends, // set its visibility to GONE as an optimization step so it doesn't // participate in layout passes. loadingView.animate() .alpha(0f) .setDuration(shortAnimationDuration.toLong()) .setListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { loadingView.visibility = View.GONE } }) } }
Java
public class CrossfadeActivity extends Activity { private View contentView; private View loadingView; private int shortAnimationDuration; ... private void crossfade() { // Set the content view to 0% opacity but visible, so that it is // visible but fully transparent during the animation. contentView.setAlpha(0f); contentView.setVisibility(View.VISIBLE); // Animate the content view to 100% opacity and clear any animation // listener set on the view. contentView.animate() .alpha(1f) .setDuration(shortAnimationDuration) .setListener(null); // Animate the loading view to 0% opacity. After the animation ends, // set its visibility to GONE as an optimization step so it doesn't // participate in layout passes. loadingView.animate() .alpha(0f) .setDuration(shortAnimationDuration) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { loadingView.setVisibility(View.GONE); } }); } }
Criar uma animação de virada de cartão
A virada de cartão alterna entre as visualizações de conteúdo, mostrando uma animação que emula uma virada de cartão. A animação de virada de cartão mostrada aqui usa
FragmentTransaction
.
Veja a aparência de uma virada de cartão:
Criar os objetos animadores
Para criar a animação de virada de cartão, são necessários quatro animadores. Dois animadores são para quando a frente do cartão é animada para fora e para a esquerda e quando ela é animada para dentro e para a esquerda. Os outros dois animadores são para quando a parte de trás do cartão é animada para dentro e para a direita e quando ela é animada para fora e para a direita.
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Before rotating, immediately set the alpha to 0. -->
<objectAnimator
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha"
android:duration="0" />
<!-- Rotate. -->
<objectAnimator
android:valueFrom="-180"
android:valueTo="0"
android:propertyName="rotationY"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:duration="@integer/card_flip_time_full" />
<!-- Halfway through the rotation, set the alpha to 1. See startOffset. -->
<objectAnimator
android:valueFrom="0.0"
android:valueTo="1.0"
android:propertyName="alpha"
android:startOffset="@integer/card_flip_time_half"
android:duration="1" />
</set>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Rotate. -->
<objectAnimator
android:valueFrom="0"
android:valueTo="180"
android:propertyName="rotationY"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:duration="@integer/card_flip_time_full" />
<!-- Halfway through the rotation, set the alpha to 0. See startOffset. -->
<objectAnimator
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha"
android:startOffset="@integer/card_flip_time_half"
android:duration="1" />
</set>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Before rotating, immediately set the alpha to 0. -->
<objectAnimator
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha"
android:duration="0" />
<!-- Rotate. -->
<objectAnimator
android:valueFrom="180"
android:valueTo="0"
android:propertyName="rotationY"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:duration="@integer/card_flip_time_full" />
<!-- Halfway through the rotation, set the alpha to 1. See startOffset. -->
<objectAnimator
android:valueFrom="0.0"
android:valueTo="1.0"
android:propertyName="alpha"
android:startOffset="@integer/card_flip_time_half"
android:duration="1" />
</set>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Rotate. -->
<objectAnimator
android:valueFrom="0"
android:valueTo="-180"
android:propertyName="rotationY"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:duration="@integer/card_flip_time_full" />
<!-- Halfway through the rotation, set the alpha to 0. See startOffset. -->
<objectAnimator
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha"
android:startOffset="@integer/card_flip_time_half"
android:duration="1" />
</set>
Criar as visualizações
Cada lado do card é um layout separado que pode conter qualquer conteúdo que você quiser, como duas visualizações de texto, duas imagens ou qualquer combinação de visualizações para alternar. Use os dois layouts nos fragmentos que serão animados posteriormente. O layout a seguir cria um lado de um card, que mostra texto:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#a6c"
android:padding="16dp"
android:gravity="bottom">
<TextView android:id="@android:id/text1"
style="?android:textAppearanceLarge"
android:textStyle="bold"
android:textColor="#fff"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/card_back_title" />
<TextView style="?android:textAppearanceSmall"
android:textAllCaps="true"
android:textColor="#80ffffff"
android:textStyle="bold"
android:lineSpacingMultiplier="1.2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/card_back_description" />
</LinearLayout>
E o próximo layout cria o outro lado do cartão, que exibe uma
ImageView
:
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/image1"
android:scaleType="centerCrop"
android:contentDescription="@string/description_image_1" />
Criar os fragmentos
Crie classes de fragmento para a frente e para o verso do cartão. Nas classes
de fragmento, retorne os layouts criados com o
método
onCreateView()
. Em seguida, você pode criar instâncias desse fragmento na atividade mãe
em que quer mostrar o cartão.
O exemplo abaixo mostra classes de fragmentos aninhadas dentro da atividade pai que as usa:
Kotlin
class CardFlipActivity : FragmentActivity() { ... /** * A fragment representing the front of the card. */ class CardFrontFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = inflater.inflate(R.layout.fragment_card_front, container, false) } /** * A fragment representing the back of the card. */ class CardBackFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = inflater.inflate(R.layout.fragment_card_back, container, false) } }
Java
public class CardFlipActivity extends FragmentActivity { ... /** * A fragment representing the front of the card. */ public class CardFrontFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_card_front, container, false); } } /** * A fragment representing the back of the card. */ public class CardBackFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_card_back, container, false); } } }
Animar a virada de cartão
Mostre os fragmentos dentro de uma atividade mãe. Para fazer isso, crie o layout
da sua atividade. O exemplo abaixo cria um
FrameLayout
ao qual você pode adicionar
fragmentos durante a execução:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
No código da atividade, defina a visualização de conteúdo como o layout que você criar. É recomendável mostrar um fragmento padrão quando a atividade for criada. O exemplo de atividade abaixo mostra como mostrar a frente do cartão por padrão:
Kotlin
class CardFlipActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_activity_card_flip) if (savedInstanceState == null) { supportFragmentManager.beginTransaction() .add(R.id.container, CardFrontFragment()) .commit() } } ... }
Java
public class CardFlipActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_activity_card_flip); if (savedInstanceState == null) { getSupportFragmentManager() .beginTransaction() .add(R.id.container, new CardFrontFragment()) .commit(); } } ... }
Com a frente do cartão aparecendo, é possível mostrar a parte de trás dele com a animação de virar em um momento adequado. Crie um método para mostrar o outro lado do cartão que faça o seguinte:
- Define as animações personalizadas que você criou para as transições de fragmento.
- Substitui o fragmento exibido por um novo e anima esse evento com as animações personalizadas que você criou.
- Adiciona o fragmento exibido anteriormente à backstack do fragmento. Assim, quando o usuário toca no botão "Voltar", o cartão é invertido.
Kotlin
class CardFlipActivity : FragmentActivity() { ... private fun flipCard() { if (showingBack) { supportFragmentManager.popBackStack() return } // Flip to the back. showingBack = true // Create and commit a new fragment transaction that adds the fragment // for the back of the card, uses custom animations, and is part of the // fragment manager's back stack. supportFragmentManager.beginTransaction() // Replace the default fragment animations with animator // resources representing rotations when switching to the back // of the card, as well as animator resources representing // rotations when flipping back to the front, such as when the // system Back button is tapped. .setCustomAnimations( R.animator.card_flip_right_in, R.animator.card_flip_right_out, R.animator.card_flip_left_in, R.animator.card_flip_left_out ) // Replace any fragments in the container view with a fragment // representing the next page, indicated by the just-incremented // currentPage variable. .replace(R.id.container, CardBackFragment()) // Add this transaction to the back stack, letting users press // the Back button to get to the front of the card. .addToBackStack(null) // Commit the transaction. .commit() } }
Java
public class CardFlipActivity extends FragmentActivity { ... private void flipCard() { if (showingBack) { getSupportFragmentManager().popBackStack(); return; } // Flip to the back. showingBack = true; // Create and commit a new fragment transaction that adds the fragment // for the back of the card, uses custom animations, and is part of the // fragment manager's back stack. getSupportFragmentManager() .beginTransaction() // Replace the default fragment animations with animator // resources representing rotations when switching to the back // of the card, as well as animator resources representing // rotations when flipping back to the front, such as when the // system Back button is pressed. .setCustomAnimations( R.animator.card_flip_right_in, R.animator.card_flip_right_out, R.animator.card_flip_left_in, R.animator.card_flip_left_out) // Replace any fragments in the container view with a fragment // representing the next page, indicated by the just-incremented // currentPage variable. .replace(R.id.container, new CardBackFragment()) // Add this transaction to the back stack, letting users press // Back to get to the front of the card. .addToBackStack(null) // Commit the transaction. .commit(); } }
Criar uma animação de revelação circular
Revelar animações fornece continuidade visual aos usuários ao mostrar ou ocultar um grupo
de elementos da interface. O método
ViewAnimationUtils.createCircularReveal()
permite animar um círculo de recorte para revelar ou ocultar uma visualização. Essa
animação é fornecida na classe
ViewAnimationUtils
,
disponível para o Android 5.0 (nível 21 da API) e versões mais recentes.
Veja um exemplo de como revelar uma visualização anteriormente invisível:
Kotlin
// A previously invisible view. val myView: View = findViewById(R.id.my_view) // Check whether the runtime version is at least Android 5.0. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Get the center for the clipping circle. val cx = myView.width / 2 val cy = myView.height / 2 // Get the final radius for the clipping circle. val finalRadius = Math.hypot(cx.toDouble(), cy.toDouble()).toFloat() // Create the animator for this view. The start radius is 0. val anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius) // Make the view visible and start the animation. myView.visibility = View.VISIBLE anim.start() } else { // Set the view to invisible without a circular reveal animation below // Android 5.0. myView.visibility = View.INVISIBLE }
Java
// A previously invisible view. View myView = findViewById(R.id.my_view); // Check whether the runtime version is at least Android 5.0. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Get the center for the clipping circle. int cx = myView.getWidth() / 2; int cy = myView.getHeight() / 2; // Get the final radius for the clipping circle. float finalRadius = (float) Math.hypot(cx, cy); // Create the animator for this view. The start radius is 0. Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius); // Make the view visible and start the animation. myView.setVisibility(View.VISIBLE); anim.start(); } else { // Set the view to invisible without a circular reveal animation below // Android 5.0. myView.setVisibility(View.INVISIBLE); }
A animação ViewAnimationUtils.createCircularReveal()
leva cinco parâmetros.
O primeiro parâmetro é a visualização que você quer ocultar ou mostrar na tela. Os dois parâmetros a seguir são as coordenadas X e Y do centro do círculo de recorte. Normalmente, esse é o centro da visualização, mas também é possível usar o
ponto em que o usuário toca para que a animação comece no ponto selecionado. O quarto parâmetro é o raio inicial do círculo de recorte.
No exemplo anterior, o raio inicial é definido como zero para que a visualização exibida fique oculta pelo círculo. O último parâmetro é o raio final do círculo. Ao mostrar uma visualização, aumente o raio final dela para que ela possa ser totalmente revelada antes do término da animação.
Para ocultar uma visualização que estava visível, faça o seguinte:
Kotlin
// A previously visible view. val myView: View = findViewById(R.id.my_view) // Check whether the runtime version is at least Android 5.0. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Get the center for the clipping circle. val cx = myView.width / 2 val cy = myView.height / 2 // Get the initial radius for the clipping circle. val initialRadius = Math.hypot(cx.toDouble(), cy.toDouble()).toFloat() // Create the animation. The final radius is 0. val anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0f) // Make the view invisible when the animation is done. anim.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) myView.visibility = View.INVISIBLE } }) // Start the animation. anim.start() } else { // Set the view to visible without a circular reveal animation below // Android 5.0. myView.visibility = View.VISIBLE }
Java
// A previously visible view. final View myView = findViewById(R.id.my_view); // Check whether the runtime version is at least Android 5.0. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Get the center for the clipping circle. int cx = myView.getWidth() / 2; int cy = myView.getHeight() / 2; // Get the initial radius for the clipping circle. float initialRadius = (float) Math.hypot(cx, cy); // Create the animation. The final radius is 0. Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0f); // Make the view invisible when the animation is done. anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); myView.setVisibility(View.INVISIBLE); } }); // Start the animation. anim.start(); } else { // Set the view to visible without a circular reveal animation below Android // 5.0. myView.setVisibility(View.VISIBLE); }
Nesse caso, o raio inicial do círculo de recorte é definido para ser tão grande quanto
a visualização, para que ela fique visível antes do início da animação. O raio final
é definido como zero para que a visualização seja ocultada quando a animação terminar.
Adicione um listener à animação para que a visibilidade da visualização possa ser definida como
INVISIBLE
quando a animação
for concluída.