Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Метод 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() — это где происходит реальное отрисовывание:
- PaintingContext — контекст с доступом к Canvas
- Canvas API — методы рисования (drawRect, drawCircle и т.д.)
- shouldRepaint() — критично для оптимизации
- RepaintBoundary — для изоляции дорогих операций
- Layer система — для работы с прозрачностью и трансформациями
Понимание painting позволяет создавать кастомные визуальные эффекты и оптимизировать производительность.