Что такое RepaintBoundary и когда его использовать?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
RepaintBoundary — оптимизация отрисовки
RepaintBoundary — это специальный виджет, который создаёт новый слой рисования (paint layer) и изолирует процесс перерисовки его содержимого от остальной части дерева виджетов.
Как работает визуализация в Flutter?
Флаттер проходит по дереву виджетов и отрисовывает всё на экран через несколько фаз:
- Build — создание дерева виджетов
- Layout — расчёт размеров и позиций
- Paint — отрисовка на Canvas
- Composite — композиция слоёв
По умолчанию, если переживает один виджет — всё дерево может быть перерисовано (repaint). Это расточительно для сложных интерфейсов.
Что делает RepaintBoundary?
RepaintBoundary отсекает цепочку перерисовки. Если виджет внутри RepaintBoundary изменился:
- Перерисовывается только содержимое внутри границы
- Родительские виджеты не перерисовываются
- Создаётся отдельный слой (layer) в GPU
Практический пример
class CounterApp extends StatefulWidget {
@override
State<CounterApp> createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
// Сложный виджет, который НЕ зависит от counter
ExpensiveWidget(),
// Обновляется часто
Text(Counter: $counter),
ElevatedButton(
onPressed: () => setState(() => counter++),
child: Text(Increment),
),
],
),
);
}
}
Проблема: При каждом нажатии на кнопку весь Column перерисовывается, включая ExpensiveWidget.
Решение с RepaintBoundary:
class _CounterAppState extends State<CounterApp> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
// Изолируем дорогой виджет
RepaintBoundary(
child: ExpensiveWidget(),
),
// Этот часто обновляется
Text(Counter: $counter),
ElevatedButton(
onPressed: () => setState(() => counter++),
child: Text(Increment),
),
],
),
);
}
}
Теперь ExpensiveWidget перерисовываться не будет при изменении counter.
Когда использовать RepaintBoundary?
Используй, если:
-
Есть дорогой виджет, который редко меняется
RepaintBoundary( child: ComplexAnimation(), ) -
Внутри анимация или частое обновление
RepaintBoundary( child: AnimatedContainer(), ) -
Список с тысячами элементов
ListView.builder( itemBuilder: (context, index) { return RepaintBoundary( child: ExpensiveListTile(), ); }, ) -
Видео или камера поверх UI
Stack( children: [ RepaintBoundary( child: CameraPreview(), ), Text(Overlay), ], )
Когда НЕ использовать?
Не используй, если:
- Элемент редко обновляется и его маленькие
- Виджет содержит текст или иконки (маленькая стоимость отрисовки)
- Нет заметных проблем с производительностью
Продвинутый пример: Списки и анимации
ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
// Каждый элемент в отдельном слое
return RepaintBoundary(
child: AnimatedListTile(
index: index,
animate: animationController,
),
);
},
)
Без RepaintBoundary — при анимации все 1000 элементов пересчитывались. С ним — только видимые.
Измерение эффекта с DevTools
Флаттер DevTools показывает рамку вокруг перерисовываемых областей:
// В main.dart при разработке
runApp(
MyApp(
showSemanticsDebugger: true,
),
);
// Или в DevTools: Performance → "Show repaint rainbow"
Сравнение: с и без RepaintBoundary
| Метрика | Без RepaintBoundary | С RepaintBoundary |
|---|---|---|
| Отрисовка дорогого виджета | Каждый раз | Только если изменился |
| GPU память | Меньше | Больше (за счёт слоёв) |
| CPU нагрузка | Выше | Ниже |
| FPS при анимации | Может упасть | Стабильнее |
Помните!
RepaintBoundary — это инструмент оптимизации, не решение. Используй его когда:
- Заметишь проблемы с производительностью
- Определишь через DevTools, что проблема именно в repaint
- Есть дорогой виджет внутри часто обновляемого контейнера
Вывод: RepaintBoundary изолирует отрисовку и улучшает FPS в сложных интерфейсах. Используй с умом — где есть вещи редко меняющиеся, но заключённые в часто обновляемых контейнерах.