CustomPainter — что это, методы paint и shouldRepaint во Flutter

Автор: IT Sectr Опубликовано: 2026-07-22 Время чтения: 10 мин

CustomPainter — это абстрактный класс из Flutter SDK, предоставляющий разработчикам полный контроль над отрисовкой кастомной 2D-графики на Canvas. Разработчик создаёт подкласс CustomPainter и реализует два обязательных метода: paint(Canvas, Size) и shouldRepaint(covariant CustomPainter oldDelegate). По данным Flutter API Documentation (2026), CustomPainter используется совместно с виджетом CustomPaint, который делегирует отрисовку переданному объекту CustomPainter на каждом кадре анимации или изменения состояния.

Главное

  • CustomPainter — абстрактный класс Flutter, определяющий кастомную отрисовку на Canvas через методы paint и shouldRepaint
  • CustomPaint — виджет, принимающий CustomPainter в качестве параметра painter или foregroundPainter и отображающий результат на экране
  • Canvas — объект, предоставляющий методы рисования (drawLine, drawCircle, drawPath, drawRect, drawArc и другие)
  • shouldRepaint — метод оптимизации, определяющий, нужно ли перерисовывать объект при обновлении
  • size — размер области рисования, передаваемый в paint(Canvas canvas, Size size) из родительского виджета

Что такое CustomPainter?

CustomPainter — это базовый класс Flutter для реализации произвольной 2D-графики. Он заменяет собой подход с переопределением onDraw в Android, предоставляя более гибкую и декларативную модель отрисовки. Разработчик создаёт подкласс CustomPainter, переопределяет метод paint, где получает Canvas и Size, и выполняет на Canvas все операции рисования. Виджет CustomPaint управляет вызовом paint и интегрирует результат в дерево виджетов.

Ключевое отличие CustomPainter от прямого рисования через Canvas — разделение логики отрисовки (paint) и триггера перерисовки (shouldRepaint). Система Flutter вызывает shouldRepaint каждый раз, когда родительский виджет изменяется. Если shouldRepaint возвращает false, paint не вызывается, что экономит ресурсы. Этот механизм аналогичен shouldRebuild в RenderObject и является частью архитектуры Flutter с её тремя деревьями (Widget, Element, RenderObject).

CustomPaint — это виджет, который принимает параметры painter (фоновый слой) и foregroundPainter (передний слой). Оба параметра принимают объекты CustomPainter. Фоновый painter рисуется до дочернего виджета CustomPaint, а foregroundPainter — после. Это позволяет использовать CustomPaint как контейнер с декоративным фоном или наложением, не теряя возможности встраивать обычные виджеты внутрь.

Canvas и Paint во Flutter

Canvas — это центральный объект для рисования во Flutter, инкапсулирующий графическую поверхность. Он предоставляет около 40 методов для рисования: drawLine, drawCircle, drawRect, drawPath, drawArc, drawImage, drawPicture, drawPoints и другие. Canvas во Flutter соответствует аналогичному классу в Android Canvas, но адаптирован под архитектуру Dart и Flutter Engine. Canvas работает в системе координат, где начало (0, 0) — верхний левый угол.

Paint — не путать с методом paint() — это класс, определяющий стиль отрисовки: цвет (color), толщину линии (strokeWidth), стиль (style: fill или stroke), прозрачность, режим смешивания (blendMode) и маску для сглаживания. Paint настраивается перед рисованием и передаётся в методы Canvas. Важно: Paint — изменяемый объект, но рекомендуется создавать его один раз и переиспользовать, меняя только необходимые параметры.

Метод CanvasНазначениеПараметры
drawCircleРисование окружностиOffset center, double radius, Paint paint
drawLineРисование отрезкаOffset p1, Offset p2, Paint paint
drawRectРисование прямоугольникаRect rect, Paint paint
drawPathРисование произвольного контураPath path, Paint paint
drawArcРисование дуги или сектораRect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint
drawRRectПрямоугольник со скруглёнными угламиRRect rrect, Paint paint
drawOvalРисование овалаRect rect, Paint paint
drawImageРисование изображения из ImageImage image, Offset p, Paint paint

Трансформации Canvas

Canvas поддерживает трансформации системы координат: translate(double dx, double dy) сдвигает начало координат; rotate(double radians) вращает систему; scale(double sx, double sy) масштабирует; skew(double sx, double sy) наклоняет. Перед трансформацией рекомендуется сохранять состояние Canvas через save(), а после завершения восстанавливать через restore(). Это предотвращает накопление трансформаций на последующих вызовах paint.

Методы paint и shouldRepaint

paint(Canvas canvas, Size size) — обязательный метод, в котором реализуется вся логика отрисовки. Параметр canvas — графическая поверхность для рисования. Параметр size — размер области, выделенной для рисования (ширина и высота в логических пикселях). Canvas предварительно настроен так, что начало координат (0,0) соответствует верхнему левому углу области рисования. Все координаты задаются относительно этого начала.

shouldRepaint(covariant CustomPainter oldDelegate) — обязательный метод, оптимизирующий частоту перерисовки. Flutter вызывает shouldRepaint при каждом обновлении дерева виджетов, которое затрагивает CustomPainter. Если метод возвращает false, уже нарисованное изображение считается валидным и paint не вызывается. Типичная реализация: return oldDelegate.someField != someField — сравнение полей старого и нового painter для определения необходимости перерисовки.

Для анимированных или постоянно изменяющихся изображений (часы, индикаторы загрузки, графики) shouldRepaint должен возвращать true при каждом изменении состояния. Если объект статичен (логотип, иконка, фон), shouldRepaint возвращает false после первой отрисовки. Flutter автоматически кэширует результат paint и перерисовывает только при изменении размера, прозрачности или вызове shouldRepaint.

Пример кастомного рисования с CustomPainter на Dart

Создадим GaugePainter — кастомный круговой датчик с анимированным заполнением. CustomPainter принимает текущее значение (progress от 0.0 до 1.0) и отрисовывает дугу с градиентом. В методе shouldRepaint сравнивается значение progress для оптимизации. CustomPaint помещается в дерево виджетов с параметром size для ограничения области рисования.

dart
class GaugePainter extends CustomPainter {
    final double progress;
    final Color startColor;
    final Color endColor;

    GaugePainter({
        required this.progress,
        this.startColor = Colors.blue,
        this.endColor = Colors.cyan,
    });

    @override
    void paint(Canvas canvas, Size size) {
        final center = Offset(size.width / 2,
            size.height / 2);
        final radius = center.dx - 12;
        final bgPaint = Paint()
            ..color = Colors.grey.withOpacity(0.3)
            ..style = PaintingStyle.stroke
            ..strokeWidth = 16
            ..strokeCap = StrokeCap.round;

        final progressPaint = Paint()
            ..shader = LinearGradient(
                colors: [startColor, endColor],
            ).createShader(
                Rect.fromCircle(
                    center, radius))
            ..style = PaintingStyle.stroke
            ..strokeWidth = 16
            ..strokeCap = StrokeCap.round;

        canvas.drawCircle(center, radius, bgPaint);
        canvas.drawArc(
            Rect.fromCircle(center, radius),
            -math.pi / 2,
            2 * math.pi * progress,
            false, progressPaint);
    }

    @override
    bool shouldRepaint(
        GaugePainter oldDelegate) {
        return oldDelegate.progress != progress;
    }
}

CustomPainter с Path для сложных фигур

Пример рисует волновой график с использованием Path. Path позволяет строить произвольные контуры из линий, кривых Безье и дуг. После построения контур заливается цветом или обводится линией. В примере график строится на основе массива точек, имитирующих волновую форму.

dart
class WavePainter extends CustomPainter {
    final List<double> values;

    WavePainter({required this.values});

    @override
    void paint(Canvas canvas, Size size) {
        final path = Path();
        final dx = size.width /
            (values.length - 1);

        path.moveTo(0, size.height);
        for (var i = 0; i < values.length; i++) {
            final x = i * dx;
            final y = size.height *
                (1 - values[i]);
            path.lineTo(x, y);
        }
        path.lineTo(size.width, size.height);
        path.close();

        final fillPaint = Paint()
            ..color = Colors.blue.withOpacity(0.3)
            ..style = PaintingStyle.fill;

        canvas.drawPath(path, fillPaint);
    }

    @override
    bool shouldRepaint(
        WavePainter oldDelegate) {
        return oldDelegate.values != values;
    }
}

ForegroundPainter и backgroundPainter: разница

painter (фоновый) рисуется до дочернего виджета CustomPaint. Если у CustomPaint есть дочерний виджет (параметр child), фоновый painter отображается под ним. Это подходит для декора: полосы прогресса, фоновые узоры, водяные знаки. foregroundPainter рисуется после дочернего виджета, то есть поверх него. Используется для наложений: бликов, масок, измерительных линеек, аннотаций.

Разница между painter и foregroundPainter критична для Z-order. Если нужно нарисовать что-то позади содержимого — используйте painter. Если поверх содержимого — foregroundPainter. В некоторых случаях можно использовать оба painter одновременно: фон через painter, метку-наложение через foregroundPainter. Это позволяет разделить логику разных графических слоёв на два отдельных класса CustomPainter.

Параметр size в методе paint одинаков для обоих painter и определяется размером области, выделенной под CustomPaint. Размер можно задать явно через size-параметр в CustomPaint или неявно через родительский контейнер. Если size равен Size.zero, paint не вызывается — Flutter пропускает отрисовку для нулевой области. Это поведение следует учитывать при анимированном появлении элемента.

Оптимизация производительности CustomPainter

Главное правило производительности CustomPainter — не создавать объекты внутри метода paint. Canvas, Paint, Path, Rect, Offset и другие объекты рисования должны создаваться в конструкторе CustomPainter или кэшироваться. Создание объектов внутри paint приводит к аллокациям на каждом кадре, что при 60 кадрах в секунду вызывает частые сборки мусора и дёрганье анимации (jank).

Кэширование Path существенно ускоряет отрисовку повторяющихся сложных контуров. Если контур не меняется между кадрами (например, фоновая сетка), создайте Path один раз и используйте его многократно. RepaintBoundary — виджет-обёртка, который предотвращает перерисовку CustomPainter при изменении вышестоящего дерева виджетов. Если CustomPainter статичен, оберните его в RepaintBoundary для изоляции.

Для анимированных CustomPainter с частой перерисовкой используйте Ticker или AnimationController с shouldRepaint, возвращающим true при изменении анимированного значения. Оптимально сравнивать анимированное поле в shouldRepaint, а не перерисовывать на каждом кадре без необходимости. Rect caching для сложных градиентов: если градиент не меняет цвета, можно закэшировать Shader через Paint.shader.

Часто задаваемые вопросы

В чём разница между CustomPainter и Canvas в Flutter?

CustomPainter — это класс-контракт, определяющий, что и как рисовать на Canvas. Canvas — сам объект рисования, который предоставляет методы (drawCircle, drawLine и т.д.). CustomPainter получает Canvas через метод paint и управляет его использованием. Один Canvas может использоваться разными CustomPainter в разное время.

Можно ли использовать CustomPainter без CustomPaint?

Нет, CustomPainter не имеет смысла без CustomPaint. CustomPaint — это виджет, который вызывает метод paint CustomPainter и отображает результат на экране. Без CustomPaint Canvas не будет создан, и paint не выполнится. Можно, однако, реализовать собственный RenderObject с Canvas, но это более низкоуровневый подход.

Как анимировать CustomPainter?

Используйте AnimationController с Ticker, передавая анимированное значение в CustomPainter через параметры конструктора. При изменении значения вызывайте setState() в виджете, содержащем CustomPaint, что триггерит вызов shouldRepaint. Если shouldRepaint возвращает true, система вызывает paint для нового кадра анимации.

Почему customPainter не обновляется при изменении данных?

Причина почти всегда в shouldRepaint, возвращающем false. Проверьте, корректно ли сравниваются поля oldDelegate с новыми значениями. Если используется mutable объект, shouldRepaint может не заметить изменений, так как ссылка на объект остаётся той же. Используйте == или immutable-объекты для корректного сравнения.

Можно ли использовать CustomPainter для анимации в ReorderableListView?

Да, CustomPainter используется для отрисовки кастомных декораций в сложных списках. Однако учтите, что при переупорядочивании элементов RepaintBoundary внутри ListView кэширует отрисовку каждого элемента. CustomPainter должен быть лёгким и не содержать тяжёлых операций в paint, чтобы не замедлять анимацию перетаскивания.

Итоги

  • CustomPainter — абстрактный класс Flutter для кастомной 2D-отрисовки с методами paint и shouldRepaint
  • CustomPaint — виджет, принимающий painter (фон) и foregroundPainter (передний план) для отображения графики
  • Canvas — объект с методами рисования: drawLine, drawCircle, drawPath, drawRect, drawArc и другими
  • Paint — класс, определяющий стиль отрисовки: цвет, толщину, стиль (fill/stroke), прозрачность и режим смешивания
  • shouldRepaint — оптимизационный метод, возвращающий true только при изменении полей, влияющих на отрисовку
  • Создание объектов внутри paint запрещено — все Paint, Path, Rect должны кэшироваться для 60 fps
  • RepaintBoundary — виджет для изоляции CustomPainter от перерисовок вышестоящего дерева виджетов

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также