← Назад к вопросам
Что такое RenderBox и RenderObject во Flutter?
3.0 Senior🔥 92 комментариев
#Архитектура Flutter
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
RenderBox и RenderObject во Flutter
Это низкоуровневая часть Flutter, которая отвечает за макет, рисование и обработку событий.
Иерархия классов
Object
└─ RenderObject (абстрактный)
├─ RenderBox (с координатами и размерами)
├─ RenderSliver (для сложных прокруток)
└─ RenderShiftedBox
└─ RenderPadding
└─ RenderDecoratedBox
Почти все визуальные виджеты используют RenderBox.
RenderObject — основа рендеринга
abstract class RenderObject extends AbstractNode {
// Макет и размеры
late Offset _offset = Offset.zero;
// Жизненный цикл
void markNeedsPaint(); // Нужно перерисовать
void markNeedsLayout(); // Нужен новый макет
void markNeedsSemantics(); // Обновить семантику
// Обработка событий
void handleEvent(PointerEvent event, covariant HitTestEntry entry);
bool hitTest(HitTestResult result, {required Offset position});
// Рисование
void paint(PaintingContext context, Offset offset);
}
RenderBox — прямоугольные объекты
Это наиболее часто используемый класс. Работает с 2D координатами и размерами.
class RenderBox extends RenderObject {
// Размеры
late Size _size = Size.zero;
// Получить размер
Size get size => _size;
// Расстояние до родителя
Offset get semanticBounds => Offset.zero & size;
// Преобразование координат
Offset localToGlobal(Offset point) { ... }
Offset globalToLocal(Offset point) { ... }
}
Жизненный цикл рендеринга
1. build() ← Виджеты создаются/обновляются
↓
2. createRenderObject() или updateRenderObject()
↓ ← RenderObject создаётся/обновляется
3. performLayout() ← Вычисление размеров и позиций
↓
4. paint() ← Рисование на Canvas
↓
5. compositingBits ← Подготовка к отправке GPU
↓
ГПУ рисует на экране
При изменении setState():
это может быть markNeedsPaint() или markNeedsLayout()
Пример: Кастомный RenderBox
// 1. RenderObject
class RenderColorBox extends RenderBox {
final Color color;
double borderRadius;
RenderColorBox({
required this.color,
this.borderRadius = 0,
});
// Контрактный размер
@override
void performLayout() {
// Вычислить размер на основе constraints
size = constraints.constrain(Size(200, 200));
}
// Рисование
@override
void paint(PaintingContext context, Offset offset) {
final rect = offset & size;
// Нарисовать закруглённый прямоугольник
context.canvas.drawRRect(
RRect.fromRectAndRadius(rect, Radius.circular(borderRadius)),
Paint()..color = color,
);
}
// Обработка нажатий
@override
bool hitTest(HitTestResult result, {required Offset position}) {
if (!size.contains(position)) return false;
result.add(BoxHitTestEntry(this, position));
return true;
}
// Обновление свойств
void updateColor(Color newColor) {
color = newColor;
markNeedsPaint(); // Перерисовать
}
void updateBorderRadius(double radius) {
borderRadius = radius;
markNeedsPaint();
}
}
// 2. RenderObjectWidget
class ColorBox extends RenderObjectWidget {
final Color color;
final double borderRadius;
ColorBox({
required this.color,
this.borderRadius = 0,
});
@override
RenderColorBox createRenderObject(BuildContext context) {
return RenderColorBox(
color: color,
borderRadius: borderRadius,
);
}
@override
void updateRenderObject(
BuildContext context,
covariant RenderColorBox renderObject,
) {
renderObject
..updateColor(color)
..updateBorderRadius(borderRadius);
}
}
Практический пример: Кастомная анимированная рамка
class RenderAnimatedBorder extends RenderBox {
Color borderColor;
double borderWidth;
double borderRadius;
double animationValue; // 0.0 - 1.0
RenderAnimatedBorder({
required this.borderColor,
required this.borderWidth,
required this.borderRadius,
required this.animationValue,
});
@override
void performLayout() {
size = constraints.constrain(Size(300, 300));
}
@override
void paint(PaintingContext context, Offset offset) {
final rect = offset & size;
final rrect = RRect.fromRectAndRadius(
rect,
Radius.circular(borderRadius),
);
// Рисование основного прямоугольника
context.canvas.drawRRect(
rrect,
Paint()
..color = Colors.white
..style = PaintingStyle.fill,
);
// Рисование границы с анимацией
final path = Path()..addRRect(rrect);
context.canvas.drawPath(
path,
Paint()
..color = borderColor.withOpacity(animationValue)
..strokeWidth = borderWidth
..style = PaintingStyle.stroke,
);
}
}
markNeedsPaint() vs markNeedsLayout()
class MyRenderBox extends RenderBox {
Color _color = Colors.blue;
Size _customSize = Size(100, 100);
// Изменение цвета — только перерисовка
void setColor(Color color) {
if (_color != color) {
_color = color;
markNeedsPaint(); // Быстрое обновление
}
}
// Изменение размера — полный пересчёт макета
void setSize(Size size) {
if (_customSize != size) {
_customSize = size;
markNeedsLayout(); // Дороже, но необходимо
}
}
@override
void performLayout() {
// Вычислить размер на основе constraints
size = constraints.constrain(_customSize);
}
@override
void paint(PaintingContext context, Offset offset) {
context.canvas.drawRect(
offset & size,
Paint()..color = _color,
);
}
}
Иерархия Widget → RenderObject
StatelessWidget
└─ build() → RenderObjectWidget
└─ createRenderObject() → RenderObject
└─ performLayout()
└─ paint()
└─ hitTest()
Структура примерно так:
Flutter ← UI слой
↓
RenderingEngine ← Логика макета
├─ performLayout()
├─ paint()
└─ hitTest()
↓
Skia Library ← Рисование
↓
GPU ← Вывод
Когда нужен кастомный RenderBox?
- Сложные геометрические фигуры
- Высокая производительность (сложные анимации)
- Кастомный макет
- Специальная обработка событий
// ❌ Плохо — множество виджетов
Column(
children: [
for (int i = 0; i < 1000; i++)
Container(width: 100, height: 100, color: Colors.blue),
],
)
// ✅ Хорошо — один RenderBox с кастомным макетом
class GridRender extends RenderBox {
@override
void paint(PaintingContext context, Offset offset) {
for (int i = 0; i < 1000; i++) {
// Рисуем 1000 квадратов эффективно
context.canvas.drawRect(
Rect.fromLTWH(/* ... */),
Paint()..color = Colors.blue,
);
}
}
}
Совет для production
Для большинства случаев используй встроенные виджеты:
- CustomPaint — для рисования
- Stack + Positioned — для макета
- SingleChildRenderObjectWidget — если нужно
Кастомные RenderObject нужны только для специфичных высоконагруженных случаев.