Автор: Андрей Белоус (@tzkt1 в Телеграм)
Как Android-разработчик, я люблю находить какие-нибудь хитрые трюки или куски кода в кодовых базах известных приложений для Android. Это не только расширяет мои знания, но и действительно интересно, как другие разработчики думают о решении тех или иных проблем.
Одно из самых удобных, плавных и надежных приложений, которые я использую ежедневно — это мессенджер Telegram. Поскольку исходный код приложения для Android доступен на GitHub, иногда мне хочется покопаться в кодовой базе, чтобы проверить, как разработчики Telegram реализовали ту или иную функцию. Довольно часто подходы, которые они используют, действительно интересны, поэтому я решил поделиться парой вещей, которые нашел в их коде.
Разделение устройств по классам производительности
Первая интересная вещь — это разделение устройств по классам производительности. Так как Android имеет очень большую фрагментацию, и вы хотите, чтобы ваше приложение работало как можно более плавно на любом устройстве, вероятно, было бы неплохо делать некоторые вещи, основываясь на аппаратной мощности устройства.
Telegram разделяет все устройства на три класса производительности: LOW, AVERAGE и HIGH, и каждый класс производительности устанавливается на основе информации о железе устройства, такой как количество CPU, частоты процессоров и класс памяти.
Telegram показывает некоторые анимации и задает параметры размытия, измеряет количество частиц в анимации частиц и определяет размер области, в которой должен отрисовываться поток с камеры, исходя из класса производительности устройства.
На самом деле идея разделения устройств по классам производительности не уникальна, и для этого уже существует несколько готовых решений. Существует библиотека от Meta*, которая делает что-то подобное, и Google недавно выпустил альфа-версию своей библиотеки определения класса производительности.
Интересный подход к анимации
Способов запустить анимацию на Android довольно много, у каждого из них есть свои плюсы и минусы, но есть один, с которым я раньше не сталкивался. Это просто, но довольно элегантно. Идею, стоящую за этим, можно показать с помощью пары строк кода.
У нас есть цикл анимации, каждый раз при вызове onDraw мы просто аннулируем представление, поэтому оно будет вызываться снова при следующем проходе отрисовки. Хорошо, но где анимация? Вы правы, это еще не анимация, и чтобы это была анимация, анимированное значение (может быть что угодно, цвет, переход, что угодно) должно немного меняться от одного прохода отрисовки к другому, поэтому для пользователей это будет выглядеть как анимация.
Хорошим примером использования для этого является анимация звуковой амплитуды (например, голоса), поскольку существует просто поток амплитуд, и представление должно иметь возможность быстро анимировать их.
Поток амплитуд будет выглядеть как массив значений с плавающей запятой в диапазоне от 0 до 1200f. [0f, 5f, 646.5f … 700f, 400f, 200f, … ]. Идея состоит в том, что каждый раз, когда в представление отправляется новое значение, мы будем устанавливать новую целевую амплитуду для анимации, и до тех пор, пока новые значения отправляются, каждый раз, когда вызывается onDraw(), мы немного корректируем текущее значение амплитуды в соответствии с новым целевым значением.
Давайте разделим эту концепцию на несколько частей.
Первая просто обновляет вид с новым значением амплитуды, полученным от оборудования. На этом шаге мы также должны вычислить малую дельту амплитуды — значение, которое будет прибавляться или вычитаться из текущей амплитуды при каждом вызове onDraw(). Чем больше это значение, тем больше скорость, с которой будет изменяться амплитуда для отображения в View.
Не обращайте внимания на все случайные числа, которые вы видели в этом Gist, они просто выбраны для того, чтобы переменная deltaAmplitude была относительно небольшой.
Вторая часть заключается в фактическом обновлении текущего значения амплитуды с учетом этой переменной deltaAmplitude и рисовании на холсте. Для этого примера я просто нарисую круг, который будет представлять текущую амплитуду. Вместо этого Telegram рисует что-то, называемое blob.
Ключевым моментом в коде выше является функция calculateNextFrame, она принимает dt — дельту времени между вызовами onDraw(), и на ее основе и deltaAmplitude вычисляет следующую амплитуду, которую нужно отрисовать на холсте.
И последнее, что нужно сделать, это просто отправить несколько случайных значений амплитуды в View, чтобы посмотреть, как он с этим справится.
Объединив два DynamicView вместе в одном макете с установкой для них разной скорости, мы можем увидеть довольно хорошие результаты.
Надеюсь, вам понравилась статья. Как всегда, весь код доступен на GitHub. Ваше здоровье!