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

Как устроен RenderBox?

1.3 Junior🔥 71 комментариев
#Архитектура Flutter

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

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

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

Как устроен RenderBox?

RenderBox — это фундаментальный класс в Flutter, который отвечает за рендеринг двумерных элементов. Это самый глубокий уровень отрисовки, где происходит вычисление размеров и позиций виджетов. Понимание RenderBox критично для создания кастомных виджетов и оптимизации производительности.

Иерархия классов

RenderObject (абстрактный базовый класс)
    ↓
RenderBox (для 2D виджетов)
    ↓
RenderFlex, RenderPadding, RenderImage, RenderText и т.д.

Основные слои Flutter

Widget Layer (статическое описание)
    ↓
Element Layer (управляет жизненным циклом)
    ↓
RenderObject Layer (вычисления и рендеринг) ← RenderBox
    ↓
Paint Layer (рисование на экране)

Что такое RenderBox?

RenderBox — это объект, который:

  1. Вычисляет размер (Size) — ширину и высоту элемента
  2. Вычисляет позицию (Offset) — где находится элемент
  3. Рисует себя (paint) — создает слои для отрисовки
  4. Обрабатывает события (handleEvent) — клики, жесты

Жизненный цикл RenderBox

1. performLayout() — вычисляет размер
2. performPaint(PaintingContext context) — рисует
3. handleEvent(PointerEvent event) — обработка входа

performLayout() — Вычисление размера

Это самый важный метод. Здесь вычисляется, какой размер должен иметь элемент.

class CustomRenderBox extends RenderBox {
  @override
  void performLayout() {
    // constraints содержит максимальный размер, который мы можем использовать
    size = constraints.biggest; // Используем максимально доступный размер
  }
}

Constraints (ограничения):

abstract class BoxConstraints {
  final double minWidth;
  final double maxWidth;
  final double minHeight;
  final double maxHeight;
}

// Примеры
BoxConstraints(
  minWidth: 0,
  maxWidth: 300,
  minHeight: 0,
  maxHeight: 500,
)

performPaint() — Рисование

class CustomRenderBox extends RenderBox {
  @override
  void paint(PaintingContext context, Offset offset) {
    // Рисуем прямоугольник
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;
    
    context.canvas.drawRect(
      Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),
      paint,
    );
  }
}

Полный пример: Кастомный виджет

// Шаг 1: Создаем RenderBox
class _CustomRender extends RenderBox {
  Color _color = Colors.blue;
  
  set color(Color value) {
    if (_color != value) {
      _color = value;
      markNeedsPaint(); // Запускаем перерисовку
    }
  }

  @override
  void performLayout() {
    // Вычисляем размер: квадрат 100x100
    size = Size(100, 100);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    final paint = Paint()..color = _color;
    context.canvas.drawCircle(
      offset + Offset(50, 50), // центр
      50, // радиус
      paint,
    );
  }
}

// Шаг 2: Создаем RenderObjectWidget
class CustomPaint extends RenderObjectWidget {
  final Color color;
  
  CustomPaint({required this.color});

  @override
  RenderObject createRenderObject(BuildContext context) {
    return _CustomRender()..color = color;
  }

  @override
  void updateRenderObject(
    BuildContext context,
    _CustomRender renderObject,
  ) {
    renderObject.color = color;
  }
}

// Использование
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: CustomPaint(color: Colors.red),
      ),
    );
  }
}

RenderBox с детьми (Composite RenderBox)

class _PaddingRender extends RenderBox {
  RenderBox? child;
  final EdgeInsets padding;

  _PaddingRender(this.child, this.padding);

  @override
  void performLayout() {
    // Вычисляем размер для ребенка
    final childConstraints = constraints.deflate(padding);
    child?.layout(childConstraints, parentUsesSize: true);

    // Размер родителя = размер ребенка + padding
    size = Size(
      child!.size.width + padding.left + padding.right,
      child!.size.height + padding.top + padding.bottom,
    );
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      // Рисуем ребенка с учетом padding
      context.paintChild(
        child!,
        offset + Offset(padding.left, padding.top),
      );
    }
  }
}

markNeedsPaint() vs markNeedsLayout()

markNeedsPaint() — нужна перерисовка (paint)

class _MyRender extends RenderBox {
  Color _color = Colors.blue;
  
  set color(Color value) {
    _color = value;
    markNeedsPaint(); // Только перерисовка
  }
}

markNeedsLayout() — нужен пересчет размера

class _MyRender extends RenderBox {
  double _width = 100;
  
  set width(double value) {
    _width = value;
    markNeedsLayout(); // Пересчитаем размер и перерисуем
  }
}

Обработка событий

class _ClickableRender extends RenderBox {
  Function? onTap;

  @override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {
    // Проверяем, попадает ли точка нажатия в наш элемент
    if (!size.contains(position)) {
      return false;
    }
    result.add(BoxHitTestEntry(this, position));
    return true;
  }

  @override
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    if (event is PointerDownEvent) {
      onTap?.call();
    }
  }
}

Практический пример: Кастомный Button

class _ButtonRender extends RenderBox {
  String _text = '';
  Function? _onPress;
  Color _bgColor = Colors.blue;

  void updateButton(String text, Function onPress, Color bgColor) {
    if (_text != text) {
      _text = text;
      markNeedsLayout();
    }
    _onPress = onPress;
    if (_bgColor != bgColor) {
      _bgColor = bgColor;
      markNeedsPaint();
    }
  }

  @override
  void performLayout() {
    // Размер: 200x60
    size = constraints.constrain(Size(200, 60));
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    // Фон
    final bgPaint = Paint()..color = _bgColor;
    context.canvas.drawRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),
        Radius.circular(8),
      ),
      bgPaint,
    );

    // Текст (упрощенный пример)
    context.canvas.drawCircle(
      offset + Offset(size.width / 2, size.height / 2),
      5,
      Paint()..color = Colors.white,
    );
  }

  @override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {
    if (!size.contains(position)) return false;
    result.add(BoxHitTestEntry(this, position));
    return true;
  }

  @override
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    if (event is PointerDownEvent) {
      _onPress?.call();
    }
  }
}

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

1. Избегай лишних layout вызовов

// ❌ Плохо
@override
void performLayout() {
  for (int i = 0; i < 1000; i++) {
    child?.layout(constraints); // 1000 раз!
  }
}

// ✅ Хорошо
@override
void performLayout() {
  final layoutChildren = _getVisibleChildren();
  for (final child in layoutChildren) {
    child.layout(constraints);
  }
}

2. Используй RepaintBoundary для кеширования рисования

RepaintBoundary(
  child: ExpensiveWidget(),
)
// Если контент не меняется, он не перерисовывается

3. Кеш вычисленных значений

class _CacheRender extends RenderBox {
  Size? _cachedSize;

  @override
  void performLayout() {
    if (_cachedSize != null && !_constraintsChanged()) {
      size = _cachedSize!;
      return;
    }
    // дорогостоящие вычисления
    _cachedSize = computeSize();
    size = _cachedSize!;
  }
}

Когда использовать RenderBox

Используйте когда:

  • Нужен максимальный контроль над рендерингом
  • Создаете специализированные графические компоненты
  • Нужна высокая производительность
  • Работаете с кастомными анимациями

НЕ используйте когда:

  • Можно обойтись обычными виджетами
  • Это усложняет код без пользы
  • Нужен быстрый прототип

Вывод

RenderBox — это мощный инструмент для создания высокопроизводительных и специализированных UI элементов. Он работает на низком уровне, где:

  • performLayout() вычисляет размеры
  • paint() рисует на canvas
  • hitTest() обрабатывает события

Понимание RenderBox позволяет создавать кастомные виджеты, оптимизировать производительность и работать с низкоуровневым рендерингом в Flutter.

Как устроен RenderBox? | PrepBro