- Введение
- Dynamics класс
- Использование dynamics
- Запуск анимации
- Сглаживание
- Animate Android custom view
- Анимации в Android по полочкам (Часть 1. Базовые анимации)
- Часть 1. Базовые анимации
- 1. Кадр за кадром
- 2. Анимация свойств объекта (aka Property Animator)
- 3. Анимация View (aka View animation)
- 4. Анимация векторных ресурсов (aka AnimatedVectorDrawable)
Введение
Во второй части этой серии постов мы начали делать view для отображения графика, предполагаю, что вы прочитали эту статью. Если это не так, я рекомендую вам сделать это. Итак, продолжим работу над нашим view компонентом.
У меня есть идея сделать в верхней части графика три кнопки, с помощью которых пользователь сможет переключать данные и отображать их. В данном случае это будут различные категории тренировок: ходьба, бег и езда на велосипеде.
Если мы реализуем кнопки и установку новых данных, а потом запустим приложение, то мы не увидим никаких изменений в графике. Метод setChartData() устанавливает новые данные, но view не отображает их. Почему? Потому что мы забыли сообщить view компоненту о том, что это надо сделать. Перерисовка view выполняется с помощью метода invalidate(). Если мы добавим в метод setChartData() вызов метода invalidate() — это решит проблему, график будет обновляться при смене данных. Несмотря на полученный результат, можно сделать view компонент еще лучше, добавив анимацию, возникающую при смене данных.
Для того чтобы реализовать анимацию, нужно выполнить два условия. Во-первых, нужен алгоритм, который будет изменять данные графика шаг за шагом, от старых значений к новым. Во-вторых, нужен какой-то механизм, позволяющий обновлять view с определенной периодичностью.
Давайте начнем с данных. Существует несколько вариантов, как это можно реализовать. Самый очевидный — это простая линейная интерполяция.
Dynamics класс
Для решения этой задачи, мы будем использовать класс, который я назвал Dynamics. Объект Dynamics — это, по сути точка, которая имеет позицию и скорость. У нее также есть конечная позиция, куда она в итоге должна придти, и метод update(), который обновляет позицию и скорость . В сокращенном виде класс выглядит так.
Наибольший интерес здесь представляет метод update(). В первой строке метода выполняется вычисление временного интервала, прошедшего с момента последнего обновления. Чтобы избежать возможных сбоев в анимации, максимально возможное время ограничено 50 мс.
В следующей строке выполняется обновление скорости движения точки, величина которой зависит от удаления до конечной позиции и коэффициента упругости (springiness). Далее обновленное значение скорости умножается на коэффициент затухания, который принимает значения от 0 до 1. Если не использовать коэффициент затухания, точка будет продолжать двигаться всегда. Можно воспринимать эту часть как амортизатор, который гасит колебания в подвеске.
В конце метода мы обновляем позицию точки, используя простое интегрирование Эйлера, и сохраняем время обновления, для того чтобы вычислить временной интервал в следующий раз.
Такой алгоритм примерно описывает поведение точки «прикрепленной» к пружине. При высоком значении коэффициента упругости точка будет ускоряться к конечной позиции быстрее и будет колебаться вокруг нее. При увеличении коэффициента затухания, ускорение замедлится и, если коэффициент достаточно высок, точка не будет колебаться.
Для того чтобы знать, когда нужно остановить анимацию, мы добавим следующий метод.
Метод возвращает true, если точка находится в конечной позиции и если скорость равна нулю. Поскольку проверять равенство двух float значений не очень хорошая идея, мы проверяем насколько эти значения близки к друг другу. В данной случае TOLERANCE равна 0,01 и для нас такой точности достаточно.
Использование dynamics
Чтобы использовать класc dynamics, нужно обновить view компонент, который мы создавали в предыдущей части. Для этого нужно поменять тип datapoints массива в тех методах, где он используется. Это несложно. Например, разница между старым и новым методами drawLineChart() будет выглядеть так.
В последнем случае для получения новой точки мы используем функцию getPosition().
Остальные изменения лучше посмотреть в коде проекта.
Запуск анимации
Теперь нам нужно решить, как обновлять позиции точек и перерисовывать экран. Я буду использовать для этого объект типа Runnable. Runnable – это интерфейс с методом run(). Любой объект, реализующий этот интерфейс, может быть запущен в отдельном потоке с помощью класса Thread. В данном случае, мы будет запускать задачу в потоке пользовательского интерфейса (UI-thread), поскольку только из него можно обновлять view компоненты.
Код будет выглядеть так:
Я создал объект типа Runnable и реализовал метод run(), в теле которого выполняется обновление позиций точек. Если хотя бы одна из точек продолжает движение, метод run() будет перезапускаться с небольшой задержкой. Перезапуск выполняется с помощью метода postDelayed(..). В конце метода run() вызывается метод invalidate(), который сообщает системе о необходимости перерисовки view компонента.
Что случится, если следующее обновление позиций точек выполнится до того как произойдет предыдущая перерисовка экрана? Теоретически это возможно, поскольку мы не можем контролировать этот процесс. По сути, ничего плохого не произойдет, потому что runnable объект «заворачивается» в сообщение и добавляется к очереди объекта Looper, обрабатываемого в UI потоке. То же самое происходит и при вызове метода ivalidate(). Looper обрабатывает сообщения в порядке поступления, поэтому перерисовка компонента будет выполняться корректно.
Комбинация dynamiсs и runnable объектов — удобный способ реализации анимации, который легко настраивать и легко расширять. Я регулярно использую этот паттерн с View и ViewGroup объектами. Обычно я реализую базовых функционал view компонента, а после того как все отлажу, добавляю анимацию, используя описанный выше подход.
Для запуска анимации я изменил метод setChartData(). Давайте посмотрим на него.
Метод обрабатывает две ситуации. Если у нас не было данных или новые данные отличаются по числу точек, мы создаем новый массив объектов типа Dynamics и инициализируем их. Текущую и конечную позиции точки мы устанавливаем равной Y значению, а скорость — 0. После инициализации всех точек, view компонент перерисовывается с помощью метода invalidate(). Анимация в этом случае не выполняется, поскольку изменения количества точек требует более сложного алгоритма.
С другой стороны, если у нас уже есть набор данных, нам нужно только поменять конечные позиции точек и запустить анимацию. Поскольку анимация уже может быть запущенной, мы сначала удаляем объект animator из очереди сообщений с помощью метода removeCallbacks(). Я бы рекомендовал вам всегда делать это перед запуском новой анимации. Ну а далее мы вызываем метод post(), передав ему Runnable объект, в данном случае это наш animator. Метод invalidate() здесь вызывать не нужно, потому что текущие позиции точек не изменились.
Сглаживание
Последний штрих. Вам не кажется, что график выглядит немного грубовато? Давайте поменяем методику создания объекта Path. Если использовать вместо метода lineTo() метод cubicTo(), можно нарисовать график кривыми Безье, правда это требует вычисления двух дополнительных контрольных точек.
Хороший способ получить эти точки — расположить первую контрольную точку на прямой проходящей через начальную точку i и имеющую наклон как прямая между точками i-1 и i+1. Вторая контрольная точку должна быть на прямой, проходящей через точку i+1 и имеющая такой же наклон как прямая между точками i и i+2. Добавив таким образом контрольных точек, мы отобразим наш график в виде непрерывной кривой.
На рисунке ниже можно видеть конечный результат.
У многих типов анимации, включая стандартную анимацию Android, возникают проблемы, если прерывать ход выполнения анимации. Давайте я объясню это на примере.
Например у вас есть простое приложение, которой содержит рисунок и кнопку. При запуске приложения рисунок скрыт. Нажатие на кнопку делает рисунок видимым, если он скрыт или скрывает его, если он был видимым. Изменение состояния рисунка сопровождается анимацией с использованием AlphaAnimation. Если мы нажимаем на кнопку только когда анимация завершена, все работает хорошо. Но если мы нажмем кнопку, чтобы сделать рисунок видимым, а затем быстро нажмем опять, то состояние рисунка скачкообразно изменится от половинной прозрачности до полной видимости и затем затухнет. Этот тип ошибок анимации можно встретить достаточно часто и выглядит это как мерцание.
Если вы запустите данный пример на эмуляторе, то увидите, что здесь такой проблемы нет. Изменение конечной позиции анимации не меняет текущую позицию или скорость напрямую. Анимация будет менять направление со временем, плавно двигаясь к новой конечной позиции.
Тестовый проект для этой статьи можно скачать с GitHub.
По материалам сайта jayway.
Вольный перевод — Pavel Bobkov
Источник
Animate Android custom view
Apr 15, 2019 · 2 min read
In part 1, I’ve shown how to create a custom view to display a circle made of up to five arcs with different colours. In this post I’m showing how to animate the circle showing one arc after arc, to achieve the following:
Ok, so the animation is pretty simple: it happens only when the colours are set, it lasts a specific amount of time, it goes from 0 to 360 degrees, and it draws one arc after the other.
ValueAnimator i s a god fit in this scenario because we want to update the onDraw method 360 times and draw all the arcs up to the current value in the animation:
The animation part itself is pretty easy! We are getting values from 0 to 360 and we update the currentSweepAngle property so we know at which stage of the circle we are at every clock of the animation.
The invalidate() method will then request onDraw to be called again so we can update the view:
Thanks to Kotlin it’s a very short method but a lot is going on.
Let’s first understand how it’s supposed to work: for example, if we have three arcs to draw we know that we have the following values:
Or if we have five arcs:
When we iterate on each arc, the currentSweepAngle is the maximum angle up to which we need to draw, therefore the first check
basically tells if current sweep angle is past the entire size of the arc. If so, we can fully draw that arc:
If not it means that we can either be in a not fully to be drawn arc or iterate through an arc that doesn’t need to be drawn at this stage (eg. at position 10 with only two arcs, 0–180 and 180–360, we only draw part of the first arc and we skip completely the second one):
If that condition is true we know that we are drawing a slice of an arc:
Источник
Анимации в Android по полочкам (Часть 1. Базовые анимации)
Сегодня я хочу немного рассказать про анимацию в Android. Думаю для всех достаточно очевидный факт, что анимации могут украсить наше приложение. Но я считаю, что они могут намного больше. Первое это — хорошие анимации даже при скромной графике могут вывести наше приложение на абсолютно новый уровень. Второе — это дополнительный инструмент для общения с пользователем, позволяющий фокусировать внимание, подсказывать механики приложения, и многое другое… но это отдельная большая тема.
Сегодня мы поговорим об инструментах для создания анимации. К сожалению, так получилось, что в Android достаточно много способов что либо анимировать, и по началу в них очень легко запутаться. Я постараюсь привести максимально общую классификацию и оставлю ссылки исключительно на документацию, т.к. туториалов можно найти предостаточно. Надеюсь эта статья поможет уложить в голове всё по полочками и, при необходимости создать анимацию, выбрать наиболее подходящий способ.
Часть 1. Базовые анимации
Часть 2. Комплексные анимации
Часть 3. «Низкоуровневые» анимации
Часть 1. Базовые анимации
1. Кадр за кадром
Предполагаю, что первая анимация в мире создавалась именно так, и в Android до сих пор доступна эта возможность.
Всё что нужно сделать это создать xml со ссылками на каждый кадр:
И запустить анимацию (Здесь и далее все примеры будут приведены на Kotlin):
Сложные по графике анимации, небольших размеров и подготовленные во внешнем редакторе.
Возможность достичь любой сложности эффектов
Большое потребление ресурсов и, как следствие, довольно затратный импорт в приложение с возможностью получить OutOfMemory. Если по каким-то причинам вам нужно показывать большое количество кадров, то придётся писать свою реализацию с постепенной подгрузкой изображений в память. Но если так пришлось делать, возможно проще прибегнуть к видео?
2. Анимация свойств объекта (aka Property Animator)
Если нам нужно всего-лишь передвинуть что-нибудь на несколько пикселей в сторону или изменить прозрачность, чтобы не плодить миллион очень похожих друг на друга кадров на помощь приходит Animator. Фактически с помощью него можно анимировать любое свойство любых объектов.
Базовый абстрактный класс называется Animator, у него есть несколько наследников, нам важны:
ValueAnimator — позволяет анимировать любое свойство
ObjectAnimator — наследуется от ValueAnimator и имеет упрощённый интерфейс для анимации свойств View.
ViewPropertyAnimator — Предоставляет ещё один удобный интерфейс для анимации View. Не унаследован от Animator и используется в методе View::animate()
Анимацию выше можно описать как в коде:
так и в XML ( animator/open_animator.xml ):
Так-же есть возможность описать нашу анимацию переходов между стейтами View, что соответсвенно, с лёгкостью позволит создать анимированные переходы между стейтами у любых View. Описанная в XML анимация будет автоматически запущена при смене состояния View.
animator/state_animator.xml
Анимация View объектов и любых их параметров
Анимация любых других параметров
В некоторой степени требовательны к ресурсам
3. Анимация View (aka View animation)
До появления Animator в Android были только Animations. Основной недостаток которых был в том что они анимировали только представление вида и никак на самом деле не изменяли его свойства. Поэтому если хочется анимировать перемещение какого-либо элемента, то дополнительно по окончанию анимации нужно изменить ещё его свойства. Такой подход так или иначе не очень удобен, если вам нужна чуть более сложная анимация или нужно отлавливать нажатия в момент анимации.
Анимацию можно запустить как в коде:
так и в XML (обратите внимание, что синтаксис отличается от xml для Animator):
anim/open_animation.xml
Там, где API не позволяет использовать Animator.
Устаревший API, меняет только представление вида.
4. Анимация векторных ресурсов (aka AnimatedVectorDrawable)
На мой взгляд самая интересная часть в Android анимациях. Можно относительно малыми силами добиваться сложных и интересных эффектов. Трансформации иконок в Android сделаны именно так.
VectorDrawable состоит из Path и Group элементов. Создание анимации сводится к тому, чтобы прописать движение к этим элементам. Андроид на картинке выше, в коде будет выглядеть так:
Чтобы не писать XML вручную можно воспользоваться онлайн инструментом.
Начиная с API 25 векторные анимации отрисовываются в RenderThread, поэтому, даже если мы загрузим чем-то наш UI Thread (но мы же никогда так не делаем, да?), анимации всё равно будут проигрываться плавно.
Иконки
Анимационные эффекты
Нет возможности вручную управлять точкой анимации во времени (т.е. фактически отсутствует какой-либо метод, вроде setCurrentTime)
Источник