Надеемся, вы уже прочитали наш рассказ о том как дизайнер Виталий Рубцов и iOS разработчик Максим Лазебный создали совершенно новую анимацию верхней панели, которая получила зловещее название — меню-гильотина.
Вскоре после разработки меню-гильотины для iOS, наш Android разработчик Дмитрий Денисенко принялся воплощать в жизнь такую же анимацию на платформе Android (посмотрите ее тут GitHub). Он даже не мог представить, с какими трудностями ему предстоит столкнуться и как глубоко придется зайти в поисках решения проблем.
С чего начать?
В начале, я хотел применить стандартное решение для создания меню-гильотины на Android. У меня была идея использовать ObjectAnimation, чтобы реализовать вращение панели навигации. Так же я хотел добавить BounceInterpolator, чтобы создать эффект отскока, когда панель сталкивается с левой рамкой экрана. А так же BounceInterpolator использовался, чтобы сделать отскок мощнее.
По умолчанию BounceInterpolator никак не настраивается и у меня не было выбора кроме как написать свой собственный интерполятор. Я предполагал добавить не только эффект отскока, но и эффект свободного падения, чтобы анимация выглядела более естественно.
Анимация гильотины включает в себя вращение и отскок гильотины, а так же отскакивание панели задач. Я использовал два пользовательских интерполятора, чтобы создать эффект свободного падения и отскока. А теперь пришло время ознакомиться с процессом разработки.
Как мы создавали вращение меню-гильотины
Мне нужно было две вещи, чтобы создать анимацию вращения :
- Найти центр вращения
- Создать ObjectAnimation чтобы сделать вращение реалистичным.
До того, как я смог рассчитать центр вращения, мне пришлось поместить layout на экран:
private void setUpOpeningView(final View openingView) { if (mActionBarView != null) { mActionBarView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.JELLY_BEAN) { mActionBarView.getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { mActionBarView.getViewTreeObserver().removeGlobalOnLayoutListener(this); } mActionBarView.setPivotX(calculatePivotX(openingView)); mActionBarView.setPivotY(calculatePivotY(openingView)); } }); } } private float calculatePivotY(View burger) { return burger.getTop() + burger.getHeight() / 2 } private float calculatePivotY(View burger) { return burger.getTop() + burger.getHeight() / 2; }
После мне оставалось только написать пару строчек кода:
ObjectAnimator rotationAnimator = ObjectAnimator.ofFloat(mGuillotineView, "rotation", GUILLOTINE_OPENED_ANGLE, GUILLOTINE_CLOSED_ANGLE); /* setting duration, listeners, interpolator, etc. */ rotationAnimator.start();
На самом деле центром вращения является «гамбургер». Анимация требует наличия двух «гамбургеров»: первый — при закрытой панели, второй — при открытой. Чтобы сделать анимацию плавной, оба гамбургера должны быть одинаковыми и использовать одни и те же координаты. Чтобы добиться этого, я создал этот наш гамбургер в панели действий (которую вы не сможете увидеть) и совместил его с центром гамбургера меню-гильотины.
Как мы создали свободное падение и отскок
Чтобы создать анимацию свободного падения меню-гильотины на iOS, мой коллега Максим Лазебный использовал по умолчанию класс UIDynamicItemBehavior, который настраивался с помощью свойств упругости и сопротивления. Однако это оказалось не так уж и просто осуществить на Android.
Как упоминалось ранее, я мог использовать BounceInterpolator по умолчанию для анимации вращения, однако, отскок получается слишком плавный (как если бы на месте гильотины был бы мяч). Поэтому я попытался создать пользовательский интерполятор. Мне хотелось придать ускорение и резкость анимации.
Скорость интерполяции изменяется от 0 до 1 (коэффициенты). В моем случае, угол вращения изменяется от 0 до 90 градусов (по часовой стрелке). Это значит, что в точке 0 градусов коэффициент интерполяции будет так же равен «0» (исходное положение). Когда угол равен 90 градусов, интерполяция будет равна «1»( конечное положение). Так как наша интерполяция имеет квадратичную зависимость, возможна анимация свободного падения и вращения, как на скриншоте Виталия. Мне пришлось вспомнить курс школьной математики, чтобы сделать пользовательский интерполятор. После некоторых размышлений, я взял тетрадку и нарисовал функцию на графике, чтобы показать зависимость свойств объекта от времени.
Я написал три квадратичных уравнения, которые иллюстрируются на графике.
А вот код интерполятора:
public class GuillotineInterpolator implements TimeInterpolator { public static final float ROTATION_TIME = 0.46667f; public static final float FIRST_BOUNCE_TIME = 0.26666f; public static final float SECOND_BOUNCE_TIME = 0.26667f; public GuillotineInterpolator() { } public float getInterpolation(float t) { if (t < ROTATION_TIME) return rotation(t); else if (t < ROTATION_TIME + FIRST_BOUNCE_TIME) return firstBounce(t); else return secondBounce(t); } private float rotation(float t) { return 4.592f * t * t; } private float firstBounce(float t) { return 2.5f * t * t - 3f * t + 1.85556f; } private float secondBounce(float t) { return 0.625f * t * t - 1.08f * t + 1.458f; } }
Как мы добились анимации отскакивания панели задач
Сейчас наше меню-гильотина может падать и отскакивать, когда оно соприкасается с левой границей экрана. Кроме того, оставалась еще одна анимация, которую мне необходимо было создать. Когда меню-гильотина возвращалось в исходное положение, оно сталкивалось с панелью задач, создавая эффект подпрыгивания. Для этого мне потребовался еще один интерполятор.
Здесь график начинается и заканчивается в точке 0 градусов, но квадратичная зависимость построена по таким же принципам, как и в предыдущем случае.
public class ActionBarInterpolator implements TimeInterpolator { private static final float FIRST_BOUNCE_PART = 0.375f; private static final float SECOND_BOUNCE_PART = 0.625f; @Override public float getInterpolation(float t) { if (t < FIRST_BOUNCE_PART) { return (-28.4444f) * t * t + 10.66667f * t; } else if (t < SECOND_BOUNCE_PART) { return (21.33312f) * t * t - 21.33312f * t + 4.999950f; } else { return (-9.481481f) * t * t + 15.40741f * t - 5.925926f; } } }
В итоге, мы получаем три экземпляра ObjectAnimation: открытие и затвор гильотины, вращение панели задач, а так же две интерполяции: падение гильотины и отскакивание панели задач. Все что мне потребовалось сделать дальше — установить интерполяции к соответствующим анимациям, запустить отскакивание панели задач после падения меню, и связать анимации с соответствующими гамбургерами.
ObjectAnimator rotationAnimator = initAnimator(ObjectAnimator.ofFloat(mGuillotineView, ROTATION, GUILLOTINE_CLOSED_ANGLE, GUILLOTINE_OPENED_ANGLE)); rotationAnimator.setInterpolator(mInterpolator); rotationAnimator.setDuration(mDuration); rotationAnimator.addListener(new Animator.AnimatorListener() {…});
Ну вот и все. Создание анимаций было в некотором роде испытанием, но оно того стоило.
Источник: How We Developed the Guillotine Menu Animation for Android
Автор статьи: Владислав Водолазов