Планета гаджетов / технологий
Содержание
Джереми Мартинес в своем блоге рассказал, как он делал анимацию для своего приложения. Мы делимся с вами русским переводом статьи.
На прошлой неделе мы сделали ребрендинг – приложение Captain Train превратилось Trainline. Это означает, что нам пришлось изменить цвета, иконки, все пустые экраны и… анимации, чтобы они соответствовали нашему новому бренду. Прощай зеленый и привет темно синий и мятный!
У нас возник ряд вопросов о том, как создавать новую анимацию загрузки. Поэтому, думаю, эта статья поможет другим разработчикам. На самом деле, я попытаюсь объяснить, как мы разработали и сделали эту анимацию.
Спойлер: она полностью рендерится на устройстве – нет видео, нет gif, только старый добрый view и векторная анимация.
До ребрендинга у нас была такая анимация при загрузке результатов поиска:
Она была очень простая и сделана за 3 часа до выпуска приложения. У нас просто покадровая анимация (animation-list drawable) с 3 разными положениями шпал.
Для приложения Trainline нам надо было что-то получше. Дизайнер создал прекрасную иллюстрацию, и нашей задачей было анимировать ее. Давайте начнем с конца – вот что у нас получилось:
Теперь давайте разберемся, как я ее сделал.
Есть несколько техник, которые можно использовать для реализации анимации в Android. Важно отметить – я хотел, чтобы эта анимация работала на Ice Cream Sandwich (4.0), то есть на minSdk для нашего приложения.
Давайте рассмотрим разные возможности:
Первым шагом мы создали параллакс анимацию, чтобы придать чувство скорости и движения. Я провел декомпозицию оригинального изображения и разложил его на составные части в ImageViews:
Тут правило довольно простое: «Чем объект дальше, тем медленнее он движется». Например, очевидно, ветер должен двигаться быстрее горизонта.
Принципы анимации слоев также очень просты и одинаковы для всех: я двигаю слой справа налево. Он начинается за пределами окна просмотра и заканчивается тоже за пределами с другой стороны. Это повторяется бесконечно. Наконец, чем слой дальше, тем длительность анимации больше. Посмотрите на пример кода:
int translationXStart = totalViewWidth - mLayerView.getLeft() + OFFSET;
int translationXEnd = -mLayerView.getLeft() - mLayerView.getWidth() - OFFSET; mLayerAnimator = ObjectAnimator.ofFloat(mLayerView, View.TRANSLATION_X, translationXStart, translationXEnd); mLayerAnimator.setRepeatCount(ObjectAnimator.INFINITE);
mLayerAnimator.setRepeatMode(ObjectAnimator.RESTART);
mLayerAnimator.setInterpolator(LINEAR_INTERPOLATOR);
mLayerAnimator.setDuration(ANIMATION_DURATION);
Как вы можете видеть, все очень просто. Этот пример просто двигается по шкале X, но для некоторых слоев (поезд и птицы) мне надо было задействовать и ось Y.
Еще одна важная деталь – чтобы уменьшить потребление памяти, каждый слой переносит контент, то есть Android отрисовывает только необходимые изменения.
Как вы можете видеть, в анимации не двигаются только рельсы.
Наконец, мы сейчас не поддерживаем языки с написание справа налево, однако, если нам это понадобиться, то направление анимации легко будет изменить.
Так как анимация повторяет сама себя бесконечно, мне надо было найти возможность плавного затухания между двумя циклами. На самом деле, когда птица достигнет левой стороны экрана, она должна снова появиться справа. Тут я использовал небольшой трюк: два View с полупрозрачным фоновым градиентом. Посмотрите ниже пример:
Моей первой реализацией было просто движение поезда вверх и вниз на 1dp. Нормально? Нет. Я поговорил с дизайнером, и он посоветовал мне использовать более естественную реализацию. Правда ведь, поезд не двигается просто вверх вниз.
Я хотел создать собственную кривую, чтобы он двигался по-моему:
Лучший способ добиться этого – использовать PathInterpolator. Он, вероятно, менее известен среди разработчиков, но, по моему мнению, он самый мощный, особенно теперь, когда он позаимствован для PathInterpolatorCompat.
Давайте посмотрим на код:
private void prepareLocomotiveAnimation() { // First, create your path final Path path = new Path(); path.lineTo(0.25f, 0.25f); path.lineTo(0.5f, -0.25f); path.lineTo(0.7f, 0.5f); path.lineTo(0.9f, -0.75f); path.lineTo(1f, 1f); // Create the ObjectAnimatpr mLocomotiveAnimator = ObjectAnimator.ofFloat(mLocomotiveView, View.TRANSLATION_Y, -1dp, 0); mLocomotiveAnimator.setRepeatCount(ObjectAnimator.INFINITE); mLocomotiveAnimator.setRepeatMode(ObjectAnimator.REVERSE); mLocomotiveAnimator.setDuration(ANIMATION_DURATION_LOCOMOTIVE); // Use the PathInterpolatorCompat mLocomotiveAnimator.setInterpolator(PathInterpolatorCompat.create(path));
}
Никакой магии. Обратите внимание, что мне пришлось улучшить эту интерполяцию, используя quadTo вместо LineTo, чтобы сделать ее более гладкой. Также отметьте, что я зациклил анимацию, чтобы сделать ее непрерывной.
Это настоящая вишенка на торте. Я хотел, чтобы птицы летели. Хотя объяснить друзьям, что «я целый день учил птиц летать», было непросто 🙂 Но давайте посмотрим, как я сделал это.
Я говорил, что AnimatedVectorDrawables прекрасен? Ну так он отлично подходит для этого случая. Во-первых, импортируем VectorDrawable:
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:viewportWidth="18" android:viewportHeight="9" android:width="18dp" android:height="9dp"> <path android:name="bird" android:pathData="M2,4c3,-1 6,-1.5 7,2c1,-3.5 4,-3 7,-2" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeColor="#20416b" android:strokeMiterLimit="1.41421" android:strokeLineCap="round" />
</vector>
Значение pathData это вектор, когда крылья подняты.
Теперь вам надо анимировать, как крылья поднимаются и опускаются. Самая важная часть – иметь одинаковое количество точек в обоих веторах пути:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="500" android:interpolator="@android:anim/accelerate_interpolator" android:propertyName="pathData" android:repeatCount="infinite" android:repeatMode="reverse" android:valueFrom="M2,4c3,-1 6,-1.5 7,2c1,-3.5 4,-3 7,-2" android:valueTo="M4,7c3,-1 4,-4.5 5,-1c1,-3.5 2,0 5,1" android:valueType="pathType" />
Как вы видите, значение valueFrom это вектор поднятых крыльев, а valueTo – вектор опущенных.
Наконец, надо совместить VectorDrawable с AnimatedVectorDrawable:
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/animated_train_bird"> <target android:animation="@animator/fly" android:name="bird" />
</animated-vector>
Как вы можете видеть, это простой пример, иллюстрирующий всю силу AnimatedVectorDrawables, и красивый последний штрих к анимации.
В заключение я бы хотел сказать, что смысл в обратной связи! Вам всегда нужны другие люди, чтобы проверить смысл созданного, его красоту и естественность. На самом деле, часто это долгий путь подстройки каждого параметра.