Когда стандартных виджетов Material или Cupertino становится недостаточно для реализации уникального UI/UX, единственным выходом становится CustomPaint и работа с Canvas. Это переход от композиции готовых блоков к прямому управлению отрисовкой пикселей через графический движок Skia или Impeller.
Механика CustomPaint и работа с Canvas
В основе кастомной графики лежит класс CustomPainter, который требует переопределения метода paint. В этом методе разработчик получает доступ к объекту Canvas, позволяющему рисовать линии, дуги, прямоугольники и сложные пути (Path). Важно понимать, что Canvas работает в системе координат, где точка (0,0) — это верхний левый угол виджета.
Мини-кейс: создание динамического графика с плавными кривыми. Вместо наслоения сотен мелких контейнеров, которые перегружают дерево виджетов, используется один CustomPaint, где через cubicTo отрисовывается единая кривая Безье. Это снижает нагрузку на CPU и GPU, так как отрисовка происходит за один проход.
Вывод: используйте CustomPaint тогда, когда количество стандартных виджетов для реализации одного элемента превышает 5-7 штук — это сигнал к оптимизации через Canvas.
Оптимизация перерисовок через shouldRepaint
Главная ошибка новичков — возврат true в методе shouldRepaint, что заставляет Flutter перерисовывать элемент при каждом обновлении кадра, даже если данные не изменились. Это приводит к падению FPS и перегреву устройства. Правильный подход — сравнение старых и новых параметров внутри этого метода.
Пример: если вы рисуете индикатор прогресса, который зависит от значения percent, метод shouldRepaint должен возвращать true только если oldWidget.percent != this.percent. В противном случае Canvas будет выполнять тяжелые математические расчеты по отрисовке путей впустую.
Вывод: строгая логика в shouldRepaint — единственный способ сохранить 60/120 FPS при наличии сложной кастомной графики на экране.
Работа с Path и сложными формами
Для создания нестандартных фигур используется класс Path. Он позволяет комбинировать линейные отрезки, дуги и кривые. Сложность возникает при необходимости создания адаптивных форм, которые должны корректно масштабироваться под разные разрешения экранов, что требует вычисления координат в относительных единицах (процентах от size.width и size.height).
Практический нюанс: при создании замкнутых фигур с заливкой (fill) и обводкой (stroke) важно следить за толщиной линии. Stroke рисуется по центру линии, поэтому часть обводки может выйти за границы Canvas и «обрезаться». Для решения этой проблемы необходимо смещать координаты Path на половину ширины strokeWidth.
Вывод: всегда закладывайте отступы (padding) внутри CustomPaint, равные толщине обводки, чтобы избежать визуальных артефактов по краям элемента.
Интеграция кастомной графики в архитектуру
Кастомный элемент не должен быть изолированным «черным ящиком». Он должен реагировать на изменения состояния приложения. Здесь возникает связь с тем, как реализована разработка мобильных приложений на Flutter через призму управления состоянием приложения: данные из провайдера или BLoC должны передаваться в CustomPainter через параметры виджета, вызывая перерисовку только необходимых участков.
Сценарий: интерактивная карта с точками. Вместо того чтобы перерисовывать весь Canvas при нажатии на одну точку, стоит обернуть CustomPaint в RepaintBoundary. Это создаст отдельный слой в графическом движке, позволяя Flutter перерисовывать только измененный слой, не затрагивая остальной интерфейс.
Вывод: RepaintBoundary обязателен для любых сложных анимаций на Canvas, чтобы изолировать тяжелую отрисовку от общего дерева виджетов.
Вывод
Разработка через Canvas — это инструмент для создания высокопроизводительного и уникального интерфейса, но он требует глубокого понимания геометрии и жизненного цикла виджетов. Избегайте использования CustomPaint для простых элементов, которые можно собрать из стандартных виджетов, так как это усложняет поддержку кода. Начинайте с изучения класса Path и обязательно внедряйте RepaintBoundary для оптимизации. Мой совет: если элемент требует интерактивности (кликов по конкретным областям Canvas), сочетайте CustomPaint с GestureDetector и методом path.contains(offset) для точного определения координат касания.
