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

Что такое рендеринг в Flutter?

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

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

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

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

Рендеринг в Flutter: От виджетов к пикселям

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

1. Что такое рендеринг?

Рендеринг — это не просто "отрисовка". Это трёхэтапный процесс:

1. Build (Dart) → дерево виджетов
2. Layout (Dart) → размеры и позиции
3. Paint (Skia C++) → пиксели на экране

Главное отличие Flutter: используется retained mode (сохранённый режим), а не immediate mode (немедленный режим).

  • Retained mode (Flutter): создаём дерево один раз, затем обновляем его
  • Immediate mode (Canvas/WebGL): перерисовываем всё каждый кадр

2. Двухуровневая архитектура

Flutter имеет две отдельные иерархии:

Слой 1: Widget (Dart) — логический слой

Class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 200,
      height: 100,
      child: Text('Hello'),
    );
  }
}

Слой 2: RenderObject (Dart + C++) — технический слой

Container создаёт RenderContainer (которая наследуется от RenderBox)
Text создаёт RenderParagraph
Эти RenderObject'ы отвечают за измерение, размещение и рисование

3. Процесс рендеринга в деталях

┌─────────────────────────────────────────┐
│ 1. BUILD PHASE (Dart виджеты)          │
│  Widget → RenderObject                  │
│  build() → createRenderObject()         │
└──────────────┬──────────────────────────┘
               ↓
┌─────────────────────────────────────────┐
│ 2. LAYOUT PHASE (Расчет размеров)      │
│  Constraints → Size                    │
│  performLayout()                        │
└──────────────┬──────────────────────────┘
               ↓
┌─────────────────────────────────────────┐
│ 3. PAINT PHASE (Рисование)             │
│  Display List → Skia → GPU             │
│  paint()                                │
└──────────────┬──────────────────────────┘
               ↓
            Экран

4. Build Phase - Создание RenderTree

// Когда Flutter видит виджет
class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {},
      child: Text('Click me'),
    );
  }
}

// Flutter делает примерно так:
// 1. Вызывает build()
// 2. ElevatedButton создаёт RenderConstrainedBox
// 3. Text создаёт RenderParagraph
// 4. Создаётся RenderTree из этих объектов

RenderObject иерархия:

RenderBox (Root - обычно это Render View)
  ├─ RenderDecoratedBox (если есть decoration)
  ├─ RenderPadding
  ├─ RenderFlex (Column, Row)
  │  ├─ RenderParagraph (Text)
  │  ├─ RenderImage
  │  └─ RenderConstrainedBox
  └─ RenderStack

5. Layout Phase - Измерение и позиционирование

// Constraints проходят вниз, Size проходит вверх

abstract class RenderObject {
  // constraints получены от родителя
  BoxConstraints constraints;

  // Здесь мы вычисляем размер
  void performLayout() {
    // Вычисляем size на основе constraints
    // Обычно: size = constraints.constrain(desiredSize)
  }

  // Размер после вычисления
  Size size;
}

// Пример: Container 300x200
class RenderContainer extends RenderBox {
  @override
  void performLayout() {
    // Родитель говорит: maxWidth=500, maxHeight=600
    // Container говорит: я хочу 300x200
    size = constraints.constrain(Size(300, 200));
    // Результат: size = 300x200
  }
}

6. Paint Phase - Отрисовка

abstract class RenderObject {
  void paint(PaintingContext context, Offset offset) {
    // context.canvas - инструмент для рисования
    // offset - позиция в родительском координатном пространстве
  }
}

// Пример: RenderDecoratedBox (контейнер с цветом)
class RenderDecoratedBox extends RenderBox {
  @override
  void paint(PaintingContext context, Offset offset) {
    // 1. Рисуем фон
    final rect = offset & size;
    context.canvas.drawRect(
      rect,
      Paint()..color = Colors.blue,
    );

    // 2. Рисуем child
    context.paintChild(child, offset);
  }
}

7. RenderObject vs Widget

Widget — это конфигурация (статические данные)

class Text extends Widget {
  final String data;
  final TextStyle style;

  @override
  RenderObject createRenderObject(BuildContext context) {
    // Создаём RenderObject на основе конфигурации
    return RenderParagraph(
      TextPainter(text: TextSpan(text: data, style: style)),
    );
  }
}

RenderObject — это состояние и логика рендеринга

class RenderParagraph extends RenderBox {
  final TextPainter textPainter;
  Size _textSize;

  @override
  void performLayout() {
    // Вычисляем размер текста
    textPainter.layout(maxWidth: constraints.maxWidth);
    _textSize = textPainter.size;
    size = constraints.constrain(_textSize);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    textPainter.paint(context.canvas, offset);
  }
}

8. Пример полного рендеринга

class DemoScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          width: 200,
          height: 100,
          color: Colors.blue,
          child: Center(
            child: Text('Hello'),
          ),
        ),
      ],
    );
  }
}

// Рендеринг происходит так:

// BUILD PHASE:
// 1. build() возвращает Column
// 2. Column создаёт RenderFlex
// 3. Container создаёт RenderConstrainedBox + RenderDecoratedBox
// 4. Center создаёт RenderCenterBox
// 5. Text создаёт RenderParagraph

// LAYOUT PHASE:
// 1. RenderView (корень) получает constraints (весь экран)
// 2. RenderFlex вычисляет размер (занимает 200x100)
// 3. RenderConstrainedBox ограничивает до 200x100
// 4. RenderCenterBox центрирует Text
// 5. RenderParagraph вычисляет размер текста

// PAINT PHASE:
// 1. RenderFlex рисует детей
// 2. RenderDecoratedBox рисует синий фон
// 3. RenderCenterBox позиционирует текст по центру
// 4. RenderParagraph рисует текст

9. Оптимизация рендеринга

Problem: Ненужные rebuild'ы

// ❌ Это перестроит весь экран
setState(() => counter++);

// ✅ Используй const для переиспользования
const header = AppHeader();

// ❌ Новый объект каждый раз
var footer = AppFooter();

// ✅ const конструктор
const footer = AppFooter();

Problem: Дорогие вычисления в build()

// ❌ Медленно
class ExpensiveWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Это вычисляется каждый раз!
    final processedData = expensiveComputation(data);
    return Text(processedData);
  }
}

// ✅ Лучше
class FastWidget extends StatelessWidget {
  final String processedData; // Передай результат

  const FastWidget(this.processedData);

  @override
  Widget build(BuildContext context) {
    return Text(processedData);
  }
}

10. Сборка кадра vs Рендеринг

Сборка кадра (Frame Pipeline):

  1. VSYNC
  2. BUILD
  3. LAYOUT
  4. PAINT
  5. COMPOSITE

Рендеринг — это части 3 и 4 (LAYOUT + PAINT).

11. Трёхуровневая иерархия

Widget Layer (Dart) — конфигурация
  ↓ build() ↓ updateRenderObject()
RenderObject Layer (Dart) — логика рендеринга
  ↓ paint() ↓ drawXxx()
Skia/Engine Layer (C++) — GPU команды
  ↓ GPU commands ↓
GPU/Screen — пиксели

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

// Собственный RenderObject для полного контроля
class RenderCustom extends RenderBox {
  final String text;
  final Color color;

  RenderCustom({required this.text, required this.color});

  @override
  void performLayout() {
    // Вычисляем размер
    size = constraints.constrain(Size(200, 100));
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    final rect = offset & size;
    final paint = Paint()..color = color;

    // Рисуем прямоугольник
    context.canvas.drawRect(rect, paint);

    // Рисуем текст
    final textPainter = TextPainter(
      text: TextSpan(
        text: text,
        style: TextStyle(color: Colors.white),
      ),
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();
    textPainter.paint(
      context.canvas,
      offset + Offset(
        (size.width - textPainter.width) / 2,
        (size.height - textPainter.height) / 2,
      ),
    );
  }

  @override
  void setupParentData(RenderBox child) {
    if (child.parentData is! BoxParentData) {
      child.parentData = BoxParentData();
    }
  }
}

// Виджет для использования
class CustomWidget extends SingleChildRenderObjectWidget {
  final String text;
  final Color color;

  const CustomWidget({
    required this.text,
    required this.color,
    required Widget child,
  }) : super(child: child);

  @override
  RenderCustom createRenderObject(BuildContext context) {
    return RenderCustom(text: text, color: color);
  }

  @override
  void updateRenderObject(BuildContext context, RenderCustom renderObject) {
    // Обновляем RenderObject если Widget изменился
    // Обычно не нужно, если используем const
  }
}

Вывод

Рендеринг в Flutter — это трёхэтапный процесс:

  1. BUILD — конвертация виджетов в RenderObject'ы
  2. LAYOUT — вычисление размеров и позиций
  3. PAINT — рисование на Canvas

Понимание рендеринга критично для:

  • Оптимизации производительности
  • Создания кастомных виджетов
  • Отладки проблем с UI
  • Написания эффективного кода

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