Во время использования приложения на экране появляется новая информация, а старая удаляется. Мгновенное изменение отображаемой на экране информации может быть неприятным, и пользователи могут пропустить внезапно появившийся контент. Анимация замедляет изменения и привлекает внимание пользователя движением, делая обновления более заметными.
Для отображения или скрытия элемента можно использовать три распространенных типа анимации: анимация раскрытия, анимация перекрестного затухания и анимация переворачивания карточки.
Создайте анимацию плавного перехода.
Анимация плавного перехода (также известная как растворение) постепенно скрывает один View или ViewGroup , одновременно плавно добавляя другой. Эта анимация полезна в ситуациях, когда необходимо переключать контент или представления в приложении. Показанная здесь анимация плавного перехода использует ViewPropertyAnimator , доступный для Android 3.1 (уровень API 12) и выше.
Вот пример плавного перехода от индикатора выполнения к текстовому содержимому:
Создайте представления
Создайте два элемента, которые вы хотите плавно переходить один в другой. В следующем примере создаются индикатор выполнения и прокручиваемое текстовое поле:
<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>
Настройте анимацию плавного перехода.
Для настройки анимации плавного перехода выполните следующие действия:
- Создайте переменные-члены для тех представлений, которые вы хотите использовать для плавного перехода между ними. Эти ссылки понадобятся вам позже при изменении представлений во время анимации.
- Установите видимость элемента, который постепенно появляется, в значение
GONE. Это предотвратит использование элементом пространства компоновки и исключит его из вычислений компоновки, что ускорит обработку. - Свойство
config_shortAnimTimeможно кэшировать в переменной-члене класса. Это свойство определяет стандартную "короткую" продолжительность анимации. Такая продолжительность идеально подходит для незначительных анимаций или анимаций, которые происходят часто. Также доступны свойстваconfig_longAnimTimeиconfig_mediumAnimTime.
Вот пример использования макета из предыдущего фрагмента кода в качестве представления содержимого активности:
Котлин
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); } ... }
Плавное переключение между видами
После правильной настройки отображения, выполните следующие действия для плавного перехода между ними:
- Для элемента, который постепенно появляется, установите значение альфа-канала равным 0, а видимость —
VISIBLEвместо исходного значенияGONE. Это сделает элемент видимым, но прозрачным. - Для элемента, который плавно появляется, анимируйте изменение его альфа-канала от 0 до 1. Для элемента, который плавно исчезает, анимируйте изменение альфа-канала от 1 до 0.
- Используя
onAnimationEnd()вAnimator.AnimatorListener, установите видимость исчезающего представления вGONE. Даже если значение альфа-канала равно 0, установка видимости представления вGONEпредотвращает использование им пространства компоновки и исключает его из вычислений компоновки, что ускоряет обработку.
Следующий метод демонстрирует пример того, как это сделать:
Котлин
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); } }); } }
Создайте анимацию переворачивания карты.
Переключение между представлениями контента осуществляется с помощью анимации, имитирующей переворачивание карточки. В показанной здесь анимации переворачивания карточки используется FragmentTransaction .
Вот как выглядит переворачивание карты:
Создайте объекты аниматора.
Для создания анимации переворачивания карты вам потребуется четыре аниматора. Два аниматора отвечают за анимацию движения лицевой стороны карты наружу и влево, а также за анимацию движения внутрь и слева. Два других аниматора отвечают за анимацию движения оборотной стороны карты внутрь и справа, а также за анимацию движения наружу и вправо.
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>
Создайте представления
Каждая сторона карточки представляет собой отдельный макет, который может содержать любой контент по вашему желанию, например, два текстовых поля, два изображения или любую комбинацию полей для переключения между ними. Используйте эти два макета во фрагментах, которые вы будете анимировать позже. Следующий макет создает одну сторону карточки, на которой отображается текст:
<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>
Следующий макет создает другую сторону карточки, на которой отображается 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" />
Создайте фрагменты
Создайте классы фрагментов для лицевой и оборотной стороны карточки. В классах фрагментов возвращайте созданные вами макеты из метода onCreateView() . Затем вы можете создать экземпляры этого фрагмента в родительской активности, где хотите отобразить карточку.
В следующем примере показаны вложенные классы фрагментов внутри родительской активности, которая их использует:
Котлин
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); } } }
Анимируйте переворачивание карты
Отображайте фрагменты внутри родительской активности. Для этого создайте макет для вашей активности. В следующем примере создается FrameLayout , в который вы можете добавлять фрагменты во время выполнения:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
В коде активности установите в качестве представления содержимого созданный вами макет. Рекомендуется показывать фрагмент по умолчанию при создании активности. В следующем примере активности показано, как по умолчанию отображать лицевую сторону карточки:
Котлин
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(); } } ... }
Когда лицевая сторона открыта, вы можете в подходящий момент показать обратную сторону открытки с анимацией переворота. Создайте метод для отображения другой стороны открытки, который выполняет следующие действия:
- Задает пользовательские анимации, созданные вами для переходов между фрагментами.
- Заменяет отображаемый фрагмент новым фрагментом и анимирует это событие с помощью созданных вами пользовательских анимаций.
- Добавляет ранее отображавшийся фрагмент в стек возврата фрагментов, так что при нажатии пользователем кнопки «Назад» карточка переворачивается обратно.
Котлин
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(); } }
Создайте анимацию кругового раскрытия.
Анимация появления обеспечивает пользователям визуальную непрерывность при отображении или скрытии группы элементов пользовательского интерфейса. Метод ViewAnimationUtils.createCircularReveal() позволяет анимировать круг отсечения для отображения или скрытия элемента. Эта анимация предоставляется в классе ViewAnimationUtils , который доступен для Android 5.0 (уровень API 21) и выше.
Вот пример, демонстрирующий, как отобразить ранее невидимый элемент:
Котлин
// 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); }
Анимация ViewAnimationUtils.createCircularReveal() принимает пять параметров. Первый параметр — это представление, которое вы хотите скрыть или показать на экране. Следующие два параметра — это координаты X и Y центра окружности отсечения. Обычно это центр представления, но вы также можете использовать точку, на которую нажимает пользователь, чтобы анимация начиналась с выбранной им точки. Четвертый параметр — это начальный радиус окружности отсечения.
В предыдущем примере начальный радиус установлен равным нулю, чтобы отображаемый объект был скрыт кругом. Последний параметр — это конечный радиус круга. При отображении объекта сделайте конечный радиус больше радиуса объекта, чтобы объект мог быть полностью показан до завершения анимации.
Чтобы скрыть ранее видимый элемент, выполните следующие действия:
Котлин
// 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); }
В этом случае начальный радиус окружности обрезки устанавливается равным размеру представления, чтобы представление было видно до начала анимации. Конечный радиус устанавливается равным нулю, чтобы представление было скрыто после завершения анимации. Добавьте обработчик событий к анимации, чтобы видимость представления устанавливалась в состояние INVISIBLE после завершения анимации.
