«Щелчок Таноса» или искусство в разработке

—

от автора

Ресурсы Вселенной ограничены, а постоянно растущее население планеты может привести к голодному вымиранию всего человечества в ближайшем будущем. Чтобы спасти людей от голодного вымирания, киновселенная Marvel придумала оригинальный способ решения проблемы. Она создала могущественного титана по имени Танос, взявшего на себя миссию спасителя. Идея Таноса состояла в том, чтобы собрав шесть Камней Бесконечности и объединив их силу в специальной Перчатке, раз и навсегда избавиться от половины населения планеты, просто щелкнув пальцами.

Эта история породила метафорическое значение, а фраза «Щелчок Таноса» стала крылатой и приобрела вполне реальные события. К примеру в бизнесе такое действие иронично называют жесткими антикризисными мерами, когда руководство компании одним махом увольняет большое количество сотрудников, дабы сократить расходы и спастись от банкротства.

Искусство программирования

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

Крылатая фраза Стива Джобса гласит: «Думай иначе». Это означает, что сегодня уже недостаточно просто уметь программировать — нужно создавать нечто неожиданное. Уже не достаточно того, чтобы приложение просто работало, важно, чтобы оно увлекало пользователя и не оставляло ни единого шанса конкурентам. В общем, настало время превращать программный код в настоящее искусство. О том как это сделать и пойдёт речь в данной статье.

Так как статья расчитана на Java-разработчиков, я расскажу о том, как можно применить метод «Щелчка Таноса» при решении задач построения графического интерфейса приложения. В качестве примера я расскажу, как реализовать удаление элементов списка сообщений в чате с применением эффекта взрыва, при котором сообщение будто разрывается на мелкие кусочки и растворяется в пространстве.

Итак представьте, что вы создали некий чат и в нём присутствуют вьюшки отображающие текст, картинки и прочие информационные блоки. Здача состоит в том, чтобы при удалении сообщений пользователь видел не просто исчезновение элементов с экрана но и распыляющую эти частицы анимацию. Это выглядит завораживающе и хочется повторять этот процесс снова и снова. Это отличный способ принудить пользователя удалять лишние сообщения из чата дабы не перегружать память устройства и не хранить лишние записи в базе данных.

Магия

В этом примере будут использоваться только нативные компоненты ОС Андроид, поэтому дополнительные библиотеки не потребуются. Это сделает код чистым и легковесным без лишнего «шума и пыли». Итак, для реализации задуманного будем работать с растровым изображением (Bitmap) и аниматором ValueAnimator. Нам потребуется создать кастомный View который и возьмёт на себя всю работу.

На первом этапе нужно получить целевой View (TextView или любой другой) из списка сообщений и сконвертировать его в Bitmap. Иными словами создадим скриншот этого элемента а сам элемент сделаем невидимым View.GONE Далее мы будем работать с картинкой уже скрытого сообщения а само сообщение можно будет просто удалить.

Далее полученное растровое изображение нужно просканировать по сетке с определенным шагом (меньший шаг — лучше эффект но выше нагрузка на процессор). Для каждого полученного пикселя нужно создать объект-частицу (Particle) с исходными координатами (x, y) и цветом.

Каждой полученной частице нужно задать случайное направление движения по осям X и Y, а также придать ускорение и время жизни, чтобы частица не существовала бесконечно. Это определит общую физику движения для всего облака частиц.

С помощью ValueAnimator от 0 до 1 запускается цикл перерисовки. На каждом кадре в методе onDraw кастомного View координаты частиц должны изменяться, а их прозрачность alpha уменьшаться. После частицы нужно нарисовать на холсте через drawPoint или drawCircle.

Для хранения параметров частиц создадим специальный класс:

public class Particle {    float cx; // Текущая координата X    float cy; // Текущая координата Y    float originalX;    float originalY;    int color;    float radius;        // Скорости и направление разлета    float vx;    float vy;    float alpha = 1.0f;}

Теперь получим растровое изображение самого элемента сообщения:

// Конвертируем View в BitmaptargetView.setDrawingCacheEnabled(true);Bitmap bitmap = Bitmap.createBitmap(targetView.getDrawingCache());targetView.setDrawingCacheEnabled(false);targetView.setVisibility(View.GONE); // Скрываем оригинал// Получаем размеры изображенияint width = bitmap.getWidth();int height = bitmap.getHeight();

Теперь нужно нарезать картинку на пиксели и задать необходимые параметры частицам:

// Задаём переменныеList<Particle> particles;Random random = new Random();// Шаг сеткиint step = 4; // Нарезаем Bitmap на пиксели-частицыfor (int y = 0; y < height; y += step) {    for (int x = 0; x < width; x += step) {        int color = bitmap.getPixel(x, y);        // Пропускаем полностью прозрачные пиксели        if (color == 0) continue;         Particle particle = new Particle(); // <- класс частиц описанный выше        particle.originalX = x;        particle.originalY = y;        particle.cx = x;        particle.cy = y;        particle.color = color;        particle.radius = step / 2f;        // Задаём случайный вектор разлета        particle.vx = (random.nextFloat() - 0.5f) * 20f;        particle.vy = (random.nextFloat() - 0.7f) * 15f;        particles.add(particle);    }}

Когда частицы собраны в коллекцию можно запускать анимацию:

// Задаём переменныеValueAnimator animator;// Настраиваем и запускаем аниматорanimator = ValueAnimator.ofFloat(0f, 1f);animator.setDuration(1200); // Длительность анимации в мсanimator.setInterpolator(new AccelerateDecelerateInterpolator());        animator.addUpdateListener(animation -> {    float progress = (float) animation.getAnimatedValue();    updateParticles(progress);    invalidate(); // Перерисовка холста});animator.addListener(new android.animation.AnimatorListenerAdapter() {    @Override    public void onAnimationEnd(android.animation.Animator.AnimatorListener animation) {        if (endListener != null) endListener.onEnd();    }});animator.start();

Теперь нужно переопределить стандартный метод отрисовки компонента View, чтобы отобразить каждый элемент из списка частиц в виде круга с заданным цветом, прозрачностью и координатами. Функция работает в связке с вызовом метода invalidate, который заставляет систему непрерывно перерисовывать экран, создавая эффект анимации.

@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    if (particles == null) return;    for (Particle p : particles) {        paint.setColor(p.color);        paint.setAlpha((int) (p.alpha * 255));        canvas.drawCircle(p.cx, p.cy, p.radius, paint);    }}

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

for (Particle p : particles) {    // Изменяем координаты с учетом скорости    p.cx += p.vx;    p.cy += p.vy;    p.vy += 0.5f; // Гравитация тянет частицы вниз                // Плавное исчезновение (alpha от 1 до 0 во второй половине анимации)    if (progress > 0.5f) {        p.alpha = 1.0f - ((progress - 0.5f) * 2f);    }}

Интегрировать можно как в Activity так и во Fragment. Чтобы запустить эффект «Щелчок Таноса» или разрыва сообщения на частицы для конкретного сообщения (например, по клику или таймеру автоудаления), нужно добавить описанный выше кастомный View поверх корневого макета и выполнить код.

// Корневой контейнер (например, FrameLayout или ConstraintLayout)ViewGroup rootLayout = findViewById(id.root_layout); // Само сообщениеTextView messageTextView = findViewById(id.tvMessage); // Создаем оверлей для анимации поверх экрана используя наш кастомный ViewDisappearAnimationView animView = new DisappearAnimationView(this);rootLayout.addView(animView, new ViewGroup.LayoutParams(    ViewGroup.LayoutParams.MATCH_PARENT,     ViewGroup.LayoutParams.MATCH_PARENT));

Для повышения производительности в манифесте нужно включить аппаратное ускорение — android:hardwareAccelerated=»true».

Выводы

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

Ниже представлено демо видео примера как работает описанный мною выше код.

ссылка на оригинал статьи https://habr.com/ru/articles/1086864/