
Ресурсы Вселенной ограничены, а постоянно растущее население планеты может привести к голодному вымиранию всего человечества в ближайшем будущем. Чтобы спасти людей от голодного вымирания, киновселенная 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".
Выводы
Добавление таких красивых эффектов делает приложение живым и запоминающимся. Вместо скучного исчезновения текста пользователь видит классную анимацию, которая превращает обычное удаление сообщения в маленькое эстетическое удовольствие. Это создает ощущение премиального продукта, над которым по-настоящему старались. Такие детали вызывают у пользователей "вау-эффект", повышают доверие к приложению и заставляют пользователя возвращаться в него снова просто потому, что им приятно пользоваться.
Ниже представлено демо видео примера как работает описанный мною выше код.

