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

Что такое 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 нужны только для специфичных высоконагруженных случаев.

Что такое RenderBox и RenderObject во Flutter? | PrepBro