Enquanto o app está sendo usado, novas informações aparecem na tela e as antigas são removidas. Mudar imediatamente o que aparece na tela pode ser desagradável, e os usuários podem perder conteúdo novo que aparece de repente. As animações desaceleram 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 podem ser usadas para mostrar ou ocultar uma visualização: animações de revelação, animações de fading cruzado e animações de virada de cartão.
Criar uma animação de fading cruzado
Uma animação de fading cruzado, também conhecida como dissolver, esmaece gradualmente
uma View
ou
ViewGroup
enquanto esmaece
outro ao mesmo tempo. Essa animação é útil para situações em que você quer
alternar conteúdo ou visualizações no seu app. A animação de fading cruzado mostrada aqui usa
ViewPropertyAnimator
,
disponível para Android 3.1 (nível 12 da API) e versões mais recentes.
Veja um exemplo de fading cruzado de um indicador de progresso para conteúdo de texto:
Criar as visualizações
Crie as duas visualizações que você quer usar na animação de fading cruzado. 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 fading cruzado, 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 será mostrada como
GONE
. Isso impede 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 de 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 usando 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 vai aparecer, 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 será ocultada paraGONE
. 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 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 usados quando a parte de trás do cartão é animada para dentro e a partir da 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 entre elas. Use os dois layouts nos fragmentos que serão animados depois. O layout abaixo 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 mostra um
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 que você criou do
método
onCreateView()
. 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 mãe 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
para sua atividade. O exemplo a seguir cria um
FrameLayout
ao qual você pode adicionar
fragmentos no ambiente de 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 parte frontal 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, você pode mostrar a parte de trás do cartão 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 fragmento 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 oferece 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 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 seguintes são as coordenadas X e Y para o centro do círculo
de recorte. Normalmente, esse é o centro da visualização, mas você também pode usar o
ponto 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 mostrada fique oculta pelo círculo. O último parâmetro é o raio final do círculo. Ao exibir uma visualização, torne o raio final maior que a visualização para que ela possa ser totalmente revelada antes que a animação termine.
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 como maior que
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 fique oculta quando a animação termina.
Adicione um listener à animação para que a visibilidade da visualização possa ser definida como
INVISIBLE
quando a animação
for concluída.