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

Ресурсы Вселенной ограничены, а постоянно растущее население планеты может привести к голодному вымиранию всего человечества в ближайшем будущем. Чтобы спасти людей от голодного вымирания, киновселенная 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 в Bitmap
targetView.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, который заставляет систему непрерывно перерисовывать экран, создавая эффект анимации.
@Override
protected 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);
// Создаем оверлей для анимации поверх экрана используя наш кастомный View
DisappearAnimationView animView = new DisappearAnimationView(this);
rootLayout.addView(animView, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));Для повышения производительности в манифесте нужно включить аппаратное ускорение - android:hardwareAccelerated="true".
Выводы
Добавление таких красивых эффектов делает приложение живым и запоминающимся. Вместо скучного исчезновения текста пользователь видит классную анимацию, которая превращает обычное удаление сообщения в маленькое эстетическое удовольствие. Это создает ощущение премиального продукта, над которым по-настоящему старались. Такие детали вызывают у пользователей "вау-эффект", повышают доверие к приложению и заставляют пользователя возвращаться в него снова просто потому, что им приятно пользоваться.
Ниже представлено демо видео примера как работает описанный мною выше код.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.