Gdy użytkownik korzysta z aplikacji, na ekranie pojawiają się nowe informacje, a stare są usuwane. Natychmiastowa zmiana tego, co jest wyświetlane na ekranie, może być irytująca, a użytkownicy mogą przegapić nowe treści, które pojawiają się nagle. Animacje spowalniają zmiany i przyciągają wzrok użytkownika ruchem, dzięki czemu aktualizacje są bardziej widoczne.
Do wyświetlania lub ukrywania widoku możesz użyć 3 popularnych animacji: animacji ujawniania, przenikania i odwracania karty.
Tworzenie animacji przenikania
Animacja przenikania, zwana też rozpuszczaniem, stopniowo zanika
jeden element View lub
ViewGroup, jednocześnie stopniowo pojawiając się w innym. Ta animacja jest przydatna w sytuacjach, gdy chcesz
przełączać treści lub widoki w aplikacji. Pokazana tutaj animacja przenikania korzysta z
ViewPropertyAnimator,
który jest dostępny w Androidzie 3.1 (API na poziomie 12) i nowszym.
Oto przykład przenikania od wskaźnika postępu do treści tekstowej:
Tworzenie widoków
Utwórz 2 widoki, które chcesz przenikać. Poniższy przykład tworzy wskaźnik postępu i przewijany widok tekstu:
<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>
Konfigurowanie animacji przenikania
Aby skonfigurować animację przenikania:
- Utwórz zmienne składowe dla widoków, które chcesz przenikać. Te odwołania będą Ci potrzebne później, gdy będziesz modyfikować widoki podczas animacji.
- Ustaw widoczność widoku, który ma się pojawić, na
GONE. Zapobiega to używaniu przez widok miejsca na układzie i pomija go w obliczeniach układu, co przyspiesza przetwarzanie. - Zapisz w pamięci podręcznej właściwość
config_shortAnimTimesystemową w zmiennej składowej. Ta właściwość określa standardowy „krótki” czas trwania animacji. Ten czas trwania jest idealny w przypadku subtelnych animacji lub animacji, które występują często.config_longAnimTimeiconfig_mediumAnimTimesą również dostępne.
Oto przykład użycia układu z poprzedniego fragmentu kodu jako widoku treści aktywności:
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); } ... }
Przenikanie widoków
Gdy widoki są prawidłowo skonfigurowane, możesz je przenikać, wykonując te czynności:
- W przypadku widoku, który ma się pojawić, ustaw wartość alfa na 0, a widoczność
na
VISIBLE(z początkowego ustawieniaGONE). Dzięki temu widok będzie widoczny, ale przezroczysty. - W przypadku widoku, który ma się pojawić, animuj wartość alfa od 0 do 1. W przypadku widoku, który ma zniknąć, animuj wartość alfa od 1 do 0.
- Używając
onAnimationEnd()wAnimator.AnimatorListener, ustaw widoczność widoku, który ma zniknąć, naGONE. Nawet jeśli wartość alfa wynosi 0, ustawienie widoczności widoku naGONEzapobiega używaniu przez widok miejsca na układzie i pomija go w obliczeniach układu, co przyspiesza przetwarzanie.
Poniższa metoda pokazuje, jak to zrobić:
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); } }); } }
Tworzenie animacji odwracania karty
Odwracanie karty przełącza widoki treści, wyświetlając animację, która naśladuje odwracanie karty. Pokazana tutaj animacja odwracania karty korzysta z
FragmentTransaction.
Oto jak wygląda odwracanie karty:
Tworzenie obiektów animatora
Aby utworzyć animację odwracania karty, potrzebujesz 4 animatorów. 2 animatory są używane, gdy przednia strona karty animuje się w lewo i znika oraz gdy animuje się w prawo i pojawia. Pozostałe 2 animatory są używane, gdy tylna strona karty animuje się w prawo i pojawia oraz gdy animuje się w lewo i znika.
card_flip_left_in.xml
<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>
card_flip_left_out.xml
<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>
card_flip_right_in.xml
<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>
card_flip_right_out.xml
<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>
Tworzenie widoków
Każda strona karty to osobny układ, który może zawierać dowolną treść, np. 2 widoki tekstu, 2 obrazy lub dowolną kombinację widoków, między którymi można się przełączać. Użyj 2 układów we fragmentach, które będziesz później animować. Poniższy układ tworzy jedną stronę karty, na której wyświetla się tekst:
<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>
A następny układ tworzy drugą stronę karty, na której wyświetla się
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" />
Tworzenie fragmentów
Utwórz klasy fragmentów dla przedniej i tylnej strony karty. W klasach fragmentów zwróć układy utworzone za pomocą metody.onCreateView() Następnie możesz utworzyć instancje tego fragmentu w aktywności nadrzędnej, w której chcesz wyświetlić kartę.
Poniższy przykład pokazuje zagnieżdżone klasy fragmentów w aktywności nadrzędnej, która ich używa:
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); } } }
Animowanie odwracania karty
Wyświetl fragmenty w aktywności nadrzędnej. Aby to zrobić, utwórz układ dla aktywności. Poniższy przykład tworzy
FrameLayout, do którego możesz dodawać
fragmenty w czasie działania programu:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
W kodzie aktywności ustaw widok treści na utworzony układ. Dobrym rozwiązaniem jest wyświetlanie domyślnego fragmentu podczas tworzenia aktywności. Poniższy przykład aktywności pokazuje, jak domyślnie wyświetlać przednią stronę karty:
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(); } } ... }
Gdy wyświetla się przednia strona karty, możesz w odpowiednim momencie wyświetlić tylną stronę za pomocą animacji odwracania. Utwórz metodę wyświetlania drugiej strony karty, która wykonuje te czynności:
- Ustawia niestandardowe animacje utworzone na potrzeby przejść między fragmentami.
- Zastępuje wyświetlany fragment nowym fragmentem i animuje to zdarzenie za pomocą utworzonych przez Ciebie animacji niestandardowych.
- Dodaje wcześniej wyświetlany fragment do stosu wstecznego fragmentów, dzięki czemu gdy użytkownik kliknie przycisk Wstecz, karta się odwróci.
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(); } }
Tworzenie animacji ujawniania okrężnego
Animacje ujawniania zapewniają użytkownikom wizualną ciągłość, gdy wyświetlasz lub ukrywasz grupę elementów interfejsu. The
ViewAnimationUtils.createCircularReveal()
metoda umożliwia animowanie okręgu przycinania w celu ujawnienia lub ukrycia widoku. Ta
animacja jest dostępna w
ViewAnimationUtils klasie,
która jest dostępna w Androidzie 5.0 (API na poziomie 21) i nowszym.
Oto przykład pokazujący, jak ujawnić wcześniej niewidoczny widok:
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); }
Animacja ViewAnimationUtils.createCircularReveal() przyjmuje 5 parametrów.
Pierwszy parametr to widok, który chcesz ukryć lub wyświetlić na ekranie. Następne 2 parametry to współrzędne X i Y środka okręgu przycinania. Zwykle jest to środek widoku, ale możesz też użyć punktu, w którym użytkownik dotknie ekranu, aby animacja zaczęła się w wybranym przez niego miejscu. Czwarty parametr to początkowy promień okręgu przycinania.
W poprzednim przykładzie początkowy promień jest ustawiony na zero, dzięki czemu wyświetlany widok jest ukryty przez okrąg. Ostatni parametr to końcowy promień okręgu. Podczas wyświetlania widoku ustaw końcowy promień większy niż widok, aby widok mógł się w pełni ujawnić przed zakończeniem animacji.
Aby ukryć wcześniej widoczny widok:
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); }
W tym przypadku początkowy promień okręgu przycinania jest ustawiony na taką samą wartość jak widok, dzięki czemu widok jest widoczny przed rozpoczęciem animacji. Końcowy promień jest ustawiony na zero, dzięki czemu widok jest ukryty po zakończeniu animacji.
Dodaj do animacji słuchacza, aby po zakończeniu animacji można było ustawić widoczność widoku na
INVISIBLE gdy animacja
zakończy się.