Connect with us

Программирование

Как добавить анимацию переворачивания карточки в Android-приложение

Анимация может помочь нам улучшить взаимодействие с пользователем.

Опубликовано

/

     
     

Недавно мы в HealthifyMe обновили дашборд, чтобы сделать его более привлекательным для пользователей. И мы попробовали действительно классные анимации. Одна из них — анимация переворачивающейся карточки. Если пользователь отслеживает потребление воды, сон или что-то еще, и достигает своей цели, мы переворачиваем карточку с прогресса на вознаграждение.

В этой статье мы покажем, как это реализовать.

Переворачивание карточки

Шаг 1. Создаем дизайн лицевой и оборотной стороны карточки.

Card_gratification.xml — дизайн задней части.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guide_start"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent=".30" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guide_end"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent=".70" />
<ImageView
android:id="@+id/btn_share_achievement"
android:layout_width="@dimen/button_height"
android:layout_height="@dimen/button_height"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/card_padding"
android:src="@drawable/ic_share_dashboard_card"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/img_gratification"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="@id/guide_end"
app:layout_constraintStart_toStartOf="@id/guide_start"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".25"
app:lottie_autoPlay="true"
app:lottie_loop="false" />
<TextView
android:id="@+id/txt_gratification"
style="@style/SansMediumXMediumTextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/goal_achieved"
android:textColor="@color/text_color_black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/img_gratification"
app:layout_constraintVertical_bias=".23" />
<TextView
android:id="@+id/btn_got_it"
style="@style/SansSmallMediumTextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:text="@string/got_it"
android:textAllCaps="true"
android:textColor="@color/water_track_accent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txt_gratification" />
</androidx.constraintlayout.widget.ConstraintLayout>

Здесь вместо определения родительскому View фиксированной ширины и высоты я предоставляю match_parent. Так можно поддерживать устройства разных размеров. Чтобы понять это, вы можете прочитать мою предыдущую статью «Выбор наилучшего макета при разработке приложения».

Перейдем к дизайну лицевой части, который представляет собой прогресс потребления воды.

Card_water_tracker.xml

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/water_progress_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/shadow_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guide_start"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent=".30" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guide_end"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent=".70" />
<com.github.lzyzsd.circleprogress.DonutProgress
android:id="@+id/dp_water_progress"
android:layout_width="0dp"
android:layout_height="0dp"
android:rotation="-90"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="@id/guide_end"
app:layout_constraintStart_toStartOf="@id/guide_start"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".25"
custom:donut_finished_color="@color/new_water_track_button_blue"
custom:donut_finished_stroke_width="@dimen/small_padding"
custom:donut_progress="0"
custom:donut_text_color="@color/transparent"
custom:donut_unfinished_color="@color/activity_background_grey"
custom:donut_unfinished_stroke_width="@dimen/small_padding" />
<ImageView
android:id="@+id/img_glass"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/small_padding"
android:src="@drawable/ic_water_hydration_glass"
app:layout_constraintBottom_toBottomOf="@id/dp_water_progress"
app:layout_constraintEnd_toEndOf="@id/dp_water_progress"
app:layout_constraintStart_toStartOf="@id/dp_water_progress"
app:layout_constraintTop_toTopOf="@id/dp_water_progress"
app:tint="@color/water_track_accent" />
<ImageView
android:id="@+id/btn_add_glass"
android:layout_width="@dimen/button_height"
android:layout_height="@dimen/button_height"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingStart="@dimen/card_padding"
android:paddingTop="@dimen/card_padding_more"
android:paddingEnd="@dimen/content_gutter"
android:paddingBottom="@dimen/card_padding_more"
android:scaleType="fitXY"
android:src="@drawable/ic_water_plus"
app:layout_constraintBottom_toBottomOf="@id/img_glass"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toEndOf="@id/dp_water_progress"
app:layout_constraintTop_toTopOf="@id/img_glass" />
<ImageView
android:id="@+id/btn_remove_glass"
android:layout_width="@dimen/button_height"
android:layout_height="@dimen/button_height"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingStart="@dimen/content_gutter"
android:paddingTop="@dimen/card_padding_more"
android:paddingEnd="@dimen/card_padding"
android:paddingBottom="@dimen/card_padding_more"
android:scaleType="fitXY"
android:src="@drawable/ic_water_minus"
app:layout_constraintBottom_toBottomOf="@id/img_glass"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="@id/dp_water_progress"
app:layout_constraintTop_toTopOf="@id/img_glass" />
<TextView
android:id="@+id/txt_water_goal"
style="@style/SansMediumXMediumTextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_color_black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dp_water_progress"
app:layout_constraintVertical_bias=".23"
tools:text="5 of 10" />
<TextView
android:id="@+id/txt_water"
style="@style/SansRegularSmallTextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/small_padding"
android:textColor="@color/disabled_text_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txt_water_goal"
tools:text="5 of 10" />
</androidx.constraintlayout.widget.ConstraintLayout>

Шаг 2. Включаем оба макета в один xml.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/water_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/view_gratification"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
android:background="@drawable/shadow_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<include
layout="@layout/card_gratification"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<!-- Add card_water_tracker.xml here-->
</androidx.constraintlayout.widget.ConstraintLayout>
view raw gistfile1.txt hosted with ❤ by GitHub

Шаг 3. Давайте создадим drawable анимацию. Когда мы переворачиваем карточку, один макет вращается по часовой стрелке и уходит назад, а второй макет, для оборотной стороны, вращается против часовой стрелки и выходит на первый план, так как обе вещи происходят параллельно.

Посмотрим на анимацию повнимательнее.

Как добавить анимацию переворачивания карточки в Android-приложение

Flip_out.xml — эта анимация предназначена для карточки, которая находится спереди и которую нужно переместить назад. Поворачиваем ее по оси Y от 0 до 180 градусов. Помните, что когда мы отправляем ее назад, другой макет мы перемещаем на передний план, поэтому, если полная продолжительность анимации составляет 1500, то на половине времени, равном 750, нужно изменить прозрачность alpha с 1 на 0.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="1500"
android:propertyName="rotationY"
android:valueFrom="0"
android:valueTo="180" />
<objectAnimator
android:duration="0"
android:propertyName="alpha"
android:startOffset="750"
android:valueFrom="1.0"
android:valueTo="0.0" />
</set>
view raw flip_out.xml hosted with ❤ by GitHub

Flip_in.xml: эта анимация для стороны, которая выходит на первый план. Поворачиваем ее в обратном направлении по оси Y.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="0"
android:propertyName="alpha"
android:valueFrom="1.0"
android:valueTo="0.0" />
<objectAnimator
android:duration="1500"
android:propertyName="rotationY"
android:repeatMode="reverse"
android:valueFrom="-180"
android:valueTo="0" />
<objectAnimator
android:duration="0"
android:propertyName="alpha"
android:startOffset="750"
android:valueFrom="0.0"
android:valueTo="1.0" />
</set>
view raw flip_in.xml hosted with ❤ by GitHub

Шаг 4: Применяем анимацию к View. Показываем один экран перед поворотом и скрываем его после окончания анимации.

  • visibleView — View, который вы хотите вывести на передний план;
  • inVisibleView — View, который вы хотите перевернуть.

fun flipCard(context: Context, visibleView: View, inVisibleView: View) {
try {
visibleView.visible()
val flipOutAnimatorSet =
AnimatorInflater.loadAnimator(
context,
R.animator.flip_out
) as AnimatorSet
flipOutAnimatorSet.setTarget(inVisibleView)
val flipInAnimationSet =
AnimatorInflater.loadAnimator(
context,
R.animator.flip_in
) as AnimatorSet
flipInAnimationSet.setTarget(visibleView)
flipOutAnimatorSet.start()
flipInAnimationSet.start()
flipInAnimatorSet.doOnEnd {
inVisibleView.gone()
}
} catch (e: Exception) {
logHandledException(e)
}
}
view raw UiUtils.kt hosted with ❤ by GitHub

Как добавить анимацию переворачивания карточки в Android-приложение

Но переворот теперь выходит за пределы окна. Чтобы избежать этого, нам нужно добавить расстояние до камеры к обоим представлениям.

Шаг 5: Добавляем расстояние до камеры view.CameraDistance

Устанавливает расстояние по оси Z от камеры до этого View. Расстояние до камеры влияет на трехмерные преобразования, например поворот вокруг осей X и Y. Если свойства RotationX или RotationY изменены и это представление большое (больше половины размера экрана), рекомендуется всегда использовать расстояние до камеры, превышающее высоту (при вращении по оси X) или ширину (при вращении по оси Y) этого вида.

Расстояние до камеры от плоскости View может влиять на перспективное искажение, когда он вращается вокруг оси X или Y. Например, большое расстояние приведет к большому углу обзора, и не будет большого перспективного искажения вида при его вращении. Небольшое расстояние может вызвать гораздо большее перспективное искажение при повороте, а также может привести к некоторым артефактам рисования, если повернутый вид оказывается частично позади камеры (вот почему рекомендуется использовать расстояние, по крайней мере, равное размеру View, если вид предполагается вращать).

val cameraDist = 8000 * scale
visibleView.cameraDistance = cameraDist
inVisibleView.cameraDistance = cameraDist

Вот весь код:

fun flipCard(context: Context, visibleView: View, inVisibleView: View) {
try {
visibleView.visible()
val scale = context.resources.displayMetrics.density
val cameraDist = 8000 * scale
visibleView.cameraDistance = cameraDist
inVisibleView.cameraDistance = cameraDist
val flipOutAnimatorSet =
AnimatorInflater.loadAnimator(
context,
R.animator.flip_out
) as AnimatorSet
flipOutAnimatorSet.setTarget(inVisibleView)
val flipInAnimatorSet =
AnimatorInflater.loadAnimator(
context,
R.animator.flip_in
) as AnimatorSet
flipInAnimatorSet.setTarget(visibleView)
flipOutAnimatorSet.start()
flipInAnimatorSet.start()
flipInAnimatorSet.doOnEnd {
inVisibleView.gone()
}
} catch (e: Exception) {
logHandledException(e)
}
}
view raw UiUtils.kt hosted with ❤ by GitHub

Как добавить анимацию переворачивания карточки в Android-приложение

Заключение

Анимация может помочь нам улучшить взаимодействие с пользователем, но слишком большое количество анимаций может отвлекать пользователей от выполнения нужных задач. Поэтому при разработке приложения всегда понимайте, кто ваша аудитория, и как лучше всего сказать им, что нужно сделать то или иное действие.

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: