← Назад к вопросам

Как работает метод painting?

2.0 Middle🔥 171 комментариев
#Архитектура Flutter

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Метод Painting в Flutter: Как отрисовываются элементы

Паinting — это процесс рисования визуальных элементов на холсте (Canvas). Это ключевой этап в жизненном цикле кадра. Подробно расскажу как это работает и как правильно его использовать.

1. Что такое Painting?

Painting — это этап во время сборки кадра, когда каждый RenderObject отрисовывает себя на Canvas. Это превращает логическую иерархию виджетов в пиксели на экране.

Рендер-дерево (logical) → paint() методы → Display List → Skia → GPU → Экран

2. Сигнатура метода paint()

// Это сигнатура, которую реализуют RenderObject и CustomPainter
void paint(PaintingContext context, Offset offset) {
  // context - контекст рисования (доступ к Canvas)
  // offset - смещение относительно родителя
}

3. PaintingContext и Canvas

PaintingContext — обёртка над Canvas, которая управляет слоями.

abstract class PaintingContext {
  // Основной метод - получение Canvas
  Canvas get canvas;

  // Рисование child-виджетов
  void paintChild(RenderObject child, Offset offset);

  // Работа со слоями (Opacity, Transform и т.д.)
  void pushOpacity(Offset offset, int alpha, VoidCallback painter);
  void pushTransform(bool needsCompositing, Matrix4 transform, VoidCallback painter);
}

4. Простой пример: CustomPainter

class MyCustomPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // size - доступный размер для рисования
    // canvas - инструмент для рисования

    // Рисуем прямоугольник
    final rect = Rect.fromLTWH(0, 0, size.width, size.height);
    final paint = Paint()..color = Colors.blue;
    canvas.drawRect(rect, paint);

    // Рисуем текст
    final textPainter = TextPainter(
      text: TextSpan(
        text: 'Hello',
        style: TextStyle(color: Colors.white, fontSize: 24),
      ),
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();
    textPainter.paint(canvas, Offset(10, 10));
  }

  @override
  bool shouldRepaint(MyCustomPainter oldDelegate) {
    // Вернуть true если нужно перерисовать
    // false если содержимое не изменилось
    return false;
  }
}

// Использование
CustomPaint(
  size: Size(200, 200),
  painter: MyCustomPainter(),
)

5. Как работает процесс painting

Шаг 1: Обход дерева

Scaffold (paint вызывается)
  ↓ paintChild() → AppBar
     ↓ paint() отрисовывает AppBar
  ↓ paintChild() → Body
     ↓ paint() отрисовывает Body
        ↓ paintChild() → Text
           ↓ paint() отрисовывает Text

Шаг 2: Каждый RenderObject рисует себя

class RenderContainer extends RenderBox {
  @override
  void paint(PaintingContext context, Offset offset) {
    // 1. Рисуем background
    if (decoration != null) {
      decoration.paint(
        context.canvas,
        offset & size,
      );
    }

    // 2. Вызываем paint для children
    if (child != null) {
      context.paintChild(child!, offset + childOffset);
    }

    // 3. Рисуем border
    if (decoration != null) {
      decoration.paintBorder(
        context.canvas,
        offset & size,
      );
    }
  }
}

6. Canvas API - основные методы

class CanvasExample extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 2.0;

    // Рисование фигур
    canvas.drawRect(
      Rect.fromLTWH(10, 10, 100, 50),
      paint,
    );

    canvas.drawCircle(
      Offset(150, 35), // центр
      25, // радиус
      paint,
    );

    canvas.drawLine(
      Offset(200, 10),
      Offset(250, 60),
      paint..strokeWidth = 3.0,
    );

    // Рисование пути
    final path = Path()
      ..moveTo(50, 150)
      ..lineTo(100, 150)
      ..quadraticBezierTo(125, 150, 150, 175)
      ..close();
    canvas.drawPath(path, paint);

    // Рисование картинки
    // canvas.drawImage(image, offset, paint);

    // Рисование с трансформацией
    canvas.save();
    canvas.translate(200, 200);
    canvas.rotate(3.14 / 4); // 45 градусов
    canvas.drawRect(Rect.fromLTWH(-25, -25, 50, 50), paint);
    canvas.restore();
  }

  @override
  bool shouldRepaint(CanvasExample oldDelegate) => false;
}

7. Оптимизация: shouldRepaint

Это критично для производительности!

class DataPainter extends CustomPainter {
  final String data;
  final Color color;

  DataPainter(this.data, this.color);

  @override
  void paint(Canvas canvas, Size size) {
    // Дорогие операции рисования
    // ...
  }

  @override
  bool shouldRepaint(DataPainter oldDelegate) {
    // Перерисовать ТОЛЬКО если изменились данные
    return oldDelegate.data != data || oldDelegate.color != color;
  }

  @override
  bool shouldRebuildSemantics(DataPainter oldDelegate) => false;
}

// Использование
CustomPaint(
  painter: DataPainter(data, color),
  // Теперь перерисовка происходит только при изменении data/color
)

8. Использование PaintingContext для child элементов

class MyRenderObject extends RenderBox {
  late RenderBox child;

  @override
  void performLayout() {
    child.layout(constraints);
    size = child.size;
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    // Способ 1: просто нарисовать child
    context.paintChild(child, offset);

    // Способ 2: нарисовать child с эффектом
    // Например, с полупрозрачностью
    context.pushOpacity(
      offset,
      128, // alpha (0-255)
      () => context.paintChild(child, offset),
    );
  }
}

9. Слои (Compositing)

Что происходит внутри paint():

class LayeredPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // Layer 1: Background
    canvas.drawRect(
      Rect.fromLTWH(0, 0, size.width, size.height),
      Paint()..color = Colors.grey[300]!,
    );

    // Layer 2: Content (может быть скрыто с opacity)
    canvas.drawCircle(
      Offset(size.width / 2, size.height / 2),
      50,
      Paint()..color = Colors.blue,
    );

    // Layer 3: Border
    canvas.drawRect(
      Rect.fromLTWH(0, 0, size.width, size.height),
      Paint()
        ..color = Colors.black
        ..style = PaintingStyle.stroke
        ..strokeWidth = 2,
    );
  }

  @override
  bool shouldRepaint(LayeredPainter oldDelegate) => false;
}

10. Производительность: RepaintBoundary

Эсли paint() дорогой, изолируй его.

// ❌ Без изоляции: перерисовывается вместе с родителем
MyComplexPainter()

// ✅ С изоляцией: перерисовывается отдельно
RepaintBoundary(
  child: MyComplexPainter(),
)

11. Практический пример: Кастомный график

class ChartPainter extends CustomPainter {
  final List<double> dataPoints;
  final Color lineColor;
  final Color gridColor;

  ChartPainter(this.dataPoints, this.lineColor, this.gridColor);

  @override
  void paint(Canvas canvas, Size size) {
    // Рисуем сетку
    _drawGrid(canvas, size);

    // Рисуем оси
    _drawAxes(canvas, size);

    // Рисуем линию данных
    _drawLine(canvas, size);
  }

  void _drawGrid(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = gridColor
      ..strokeWidth = 0.5;

    // Вертикальные линии
    for (int i = 0; i <= 5; i++) {
      final x = size.width * (i / 5);
      canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
    }

    // Горизонтальные линии
    for (int i = 0; i <= 5; i++) {
      final y = size.height * (i / 5);
      canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
    }
  }

  void _drawLine(Canvas canvas, Size size) {
    if (dataPoints.isEmpty) return;

    final paint = Paint()
      ..color = lineColor
      ..strokeWidth = 2.0
      ..strokeCap = StrokeCap.round;

    final path = Path();
    final maxValue = dataPoints.reduce((a, b) => a > b ? a : b);

    for (int i = 0; i < dataPoints.length; i++) {
      final x = (size.width / (dataPoints.length - 1)) * i;
      final y = size.height * (1 - (dataPoints[i] / maxValue));

      if (i == 0) {
        path.moveTo(x, y);
      } else {
        path.lineTo(x, y);
      }
    }

    canvas.drawPath(path, paint);
  }

  void _drawAxes(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.black
      ..strokeWidth = 1.0;

    canvas.drawLine(Offset(0, size.height), Offset(size.width, size.height), paint);
    canvas.drawLine(Offset(0, 0), Offset(0, size.height), paint);
  }

  @override
  bool shouldRepaint(ChartPainter oldDelegate) {
    return oldDelegate.dataPoints != dataPoints ||
        oldDelegate.lineColor != lineColor ||
        oldDelegate.gridColor != gridColor;
  }
}

Вывод

Метод paint() — это где происходит реальное отрисовывание:

  1. PaintingContext — контекст с доступом к Canvas
  2. Canvas API — методы рисования (drawRect, drawCircle и т.д.)
  3. shouldRepaint() — критично для оптимизации
  4. RepaintBoundary — для изоляции дорогих операций
  5. Layer система — для работы с прозрачностью и трансформациями

Понимание painting позволяет создавать кастомные визуальные эффекты и оптимизировать производительность.

Как работает метод painting? | PrepBro