RenderObject Tree는 Flutter 계층 구조의 세 번째 레벨로, 화면에 인터페이스를 실제로 렌더링하는 역할을 담당합니다. 이 트리의 각 노드는 크기 계산(layout)과 그림(painting)을 수행하여 위젟 구성을 픽셀로 변환합니다. Widget Tree와 달리, RenderObject Tree는 한 번 생성되며 기하학적 구조나 외관이 변경될 때만 업데이트됩니다. Flutter API Reference, 2025에 따르면, RenderObject Tree의 효율성은 애니메이션의 부드러움과 앱플리케이션의 응답 속도를 직접 결정합니다.
주요 포인트
RenderObject Tree는 메모리에서 인터페이스의 실제 표현을 제공하는 렌더 객체의 계층적 구조입니다. 각 RenderObject는 자신의 크기, 화면에서의 위치 그리고 자기 자신을 그리는 방법을 알고 있습니다. 가볍고 초당 수백 번 재생성될 수 있는 위젯과 달리, RenderObject는 그래픽 파이프라인에 직접 액세스할 수 있는 무거운 객체입니다.
위젯에서 픽셀까지의 길은 세 단계를 거쳐옵니다: Widget Tree는 화면에 뭐가 있어야 하는지 설명하고, Element Tree는 수명 주기를 관리하고, RenderObject Tree가 실제 작업을 수행합니다. 각 RenderObjectWidget(포함 Padding, Transform, CustomPaint)은 렌더 트리에 추가되는 RenderObject를 생성합니다. RenderBox는 가장 일반적인 RenderObject 유형으로, 표준 위젯의 99%에서 사용됩니다.
RenderObject는 layout과 paint에 대한 인터페이스를 정의하는 추상 클래스입니다. 그것은 부모 RenderObject와 자식 객체에 대한 참조 그리고 추상 메서드(performLayout, paint, hitTest)를 포함합니다. 각 구체적인 RenderObject는 자신의 동작에 따라 이 메서드들을 구현합니다: RenderFlex는 자식 간에 공간을 분배하고, RenderImage는 이미지를 표시하고, RenderParagraph는 텍스트를 렌더링합니다.
abstract class RenderObject {
RenderObject? parent;
Constraints constraints;
ParentData? parentData;
bool _needsLayout = true;
bool _needsPaint = true;
void performLayout();
void paint(PaintingContext context, Offset offset);
}
이 간단한 구조에서 RenderObject는 _needsLayout과 _needsPaint 플래그를 포함하며, 이는 객체가 업데이트를 필요로함을 나타냅니다. Widget Tree가 변경되면, Element Tree는 해당 RenderObject를 layout 또는 paint을 위해 "dirty"로 표시하고, 다음 프레임에서 Flutter는 필요한 작업만 수행합니다.
각 RenderObject는 두 가지 주요 단계를 수행합니다: layout(크기와 위치 결정)과 paint(그리기). 이 단계는 엄격한 순서로 실행됩니다: 먼저 전체 트리에 대한 layout, 그 다음 paint입니다. layout이 변경되지 않았다면 paint 단계를 건너뛐 GPU 리소스를 절약할 수 있습니다.
layout 동안 Flutter는 RenderObject Tree를 통해 위에서 아래로 제약(constraints)을 전달합니다. 각 부모는 자식의 최소 및 최대 너비와 높이를 설정합니다. 자식은 이 제약 내에서 크기를 계산하고 결과를 부모에게 반환합니다. 이 과정을 "downward pass"(제약을 아래로 전달) 그리고 "upward pass"(크기를 위로 전달)라고 합니다.
layout이 완료되면 Flutter는 paint를 수행합니다 — 각 RenderObject를 그립니다. 부모는 PaintingContext를 만들고, 그것을 자식 객체에 전달하고 오프셋(offset)을 지정합니다. 각 RenderObject는 그래픽 프리미티브(사각형, 원, 텍스트, 이미지, 변환)를 사용하여 Canvas에 자기 자신을 그립니다. Canvas는 Skia 또는 Impeller와 직접 상호작용합니다.
Flutter는 작업을 최소화하기 위해 "dirty" 노드 메커니즘을 사용합니다. 하나의 RenderObject 크기만 변경되면, Flutter는 전체 트리의 layout을 재계산하지 않고 변경된 노드와 고정 크기의 가장 가까운 RenderBox까지의 가능한 조상들을 표시합니다. 유사하게 외관이 변경되면 layout을 다시 하지 않고 paint만 표시됩니다.
Flutter는 다양한 작업을 위해 여러 카테고리의 RenderObject를 제공합니다: 표준 직사각형 요소에 대한 RenderBox, 스크롤 가능 영역에 대한 RenderSliver, CustomPainter를 통한 비표준 렌더링을 위한 커스텀 RenderObject가 있습니다. 각 유형은 렌더 트리에서의 역할에 맞게 최적화되어 있습니다.
RenderBox는 직사각형 인터페이스 요소의 기본 클래스입니다. 그 하위 유형에는 RenderPadding(패딩 추가), RenderTransform(변환 적용), RenderFlex(Row와 Column 구현), RenderStack(요소 격처), RenderImage(이미지 표시), RenderParagraph(텍스트 렌더링)가 있습니다. 각 하위 유형은 자기 로직에 따라 performLayout과 paint를 재정의합니다.
RenderSliver는 스크롤 가능 영역을 다루기 위한 RenderObject 유형입니다. RenderBox와 달리, Sliver는 뷰포트 정보를 포함하는 SliverConstraints를 사용합니다. RenderSliverList와 RenderSliverGrid는 보이는 영역에 있는 항목에 대해서만 RenderObject를 생성하여 수백만 건의 리스트를 처리할 수 있습니다.
비표준 그래픽의 경우 CustomPaint와 CustomPainter를 사용하세요. CustomPaint는 RenderCustomPaint를 생성하고, 이는 CustomPainter의 paint 메서드를 호출합니다. 이를 통해 Canvas를 완전히 제어하며 임의의 도형, 그래프 및 애니메이션을 그릴 수 있습니다. Flutter 팀에 따르면, 복잡한 벡터 그래픽에는 CustomPainter가 중첩된 표준 위젯보다 더 효율적입니다.
class CirclePainter extends CustomPainter {
final Color color;
CirclePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.width / 3,
paint,
);
}
@override
bool shouldRepaint(CirclePainter oldDelegate) =>
oldDelegate.color != color;
}
이 예제에서 CirclePainter가 Canvas에 원을 그립니다. shouldRepaint 메서드는 색상이 변경될 때만 true를 반환하여 불필요한 재그림을 방지합니다. CustomPainter는 가능한 가볍게 유지해야 합니다 — 모든 무거운 계산은 paint 메서드 밖에서 수행하세요.
RenderObject Tree는 RenderObjectWidget 메커니즘을 통해 Element Tree에서 생성됩니다. 각 RenderObjectWidget(Padding, Transform, CustomPaint)은 RenderObjectElement를 생성하고, 이는 다시 해당 RenderObject를 생성하고 관리합니다. 요소는 중개자 역할을 합니다: 위젯에서 RenderObject로 구성을 전달하고 변경 사항을 RenderObject에 알립니다.
RenderObjectElement가 마운트될 때 위젯의 createRenderObject 메서드를 호출합니다. 위젯은 RenderObject 인스턴스를 생성하여 요소에 반환합니다. 요소는 부모 RenderObject의 insertChildLayout 메서드를 호출하여 RenderObject를 RenderObject Tree에 삽입합니다. 이 과정은 처음 마운트에서만 발생하고, 그 후의 업데이트에서는 요소가 기존 RenderObject의 매개 변수를 간단히 업데이트합니다.
위젯의 구성이 변경될 때(예: padding 값 변경), 요소는 updateRenderObject 메서드를 호출하여 새 구성을 기존 RenderObject에 전달합니다. RenderObject는 자기 자신을 layout 또는 paint에 대해 "dirty"로 표시하고, 다음 프레임에서 프레임워크가 필요한 업데이트를 수행합니다.
요소가 언마운트될 때 unmount 메서드가 호출되여 RenderObject를 RenderObject Tree에서 제거하고 리소스를 해제합니다. 부모로부터 분리하기 위해 RenderObject.remove가 호출되며, 그 후 객체는 가비지 컬렉션의 대상이 됩니다. Flutter는 해당 요소 없이 RenderObject가 트리에 남아있지 않도록 보장합니다.
RepaintBoundary는 콘텐츠를 렌더링하기 위해 별도의 레이어를 생성하는 위젯입니다. RepaintBoundary 내의 콘텐츠가 변경되면 해당 레이어만 재그려지고, 나머지 화면은 그대로 유지됩니다. RepaintBoundary는 애니메이션, 비디오 플레이어, 대활적 차트 및 기타 빈번하게 업데이트되는 요소에 특히 유용합니다.
RepaintBoundary의 핵심에는 RenderRepaintBoundary가 있습니다 — 별도의 PictureLayer를 생성하는 특별한 RenderObject입니다. 첫 렌더링시 RenderRepaintBoundary는 이 레이어에 그래픽 명령을 기록합니다. 그 후에 업데이트할 때 RepaintBoundary 내의 콘텐츠만 변경되면, Flutter는 전체 화면 대신 이 레이어만 재그립니다. 다른 레이어들은 그대로 유지되며 재사용됩니다.
모든 위젯에 RepaintBoundary가 필요한 것은 아닙니다. 인터페이스의 일부가 높은 빈도(60 FPS 이상)로 업데이트되고 나머지 화면이 정적인 경우에 사용하세요. 일반적인 예: 애니메이션된 로딩 인디케이터, 비디오 플레이어, 게임 Canvas, 빈번한 재그림이 필요한 CustomPainter. 정적 텍스트나 버튼에 대해서는 RepaintBoundary가 과핸하며 메모리 소비만 증가시킵니다.
| 시나리오 | 권장 | 이유 |
|---|---|---|
| 애니메이션 | RepaintBoundary로 감싸기 | 빈번하게 업데이트되는 영역 검리 |
| 스크롤 가능 리스트 | 필요 없음 | ListView가 자동으로 레이어 사용 |
| 정적 텍스트 | 필요 없음 | 빈번한 재그림 없음 |
| CustomPainter | 권장함 | 빈번한 그래픽 재그림 |
성능 RenderObject Tree의 노드 수, 트리 깊이 및 재렌더링 빈도에 따라 달라집니다. Flutter DevTools("Rendering" 탭)은 실시간으로 RenderObject Tree를 분석할 수 있게 해줍니다: 재그림 회수, layout과 paint 시간, 레이어 수 및 그 크기. 정기적인 분석은 병목을 식별하는 데 도움이 됩니다.
Overdraw는 하나의 픽셀이 프레임당 여러 번 그려지는 상황입니다. 예를 들어 반투명 위젯이 다른 위젯을 격쳐섴 때 GPU는 두 레이어를 모두 그립니다. 비투명 요소에는 불투명 플래그(decoration 대신 color를 사용한 Container)를 사용하여 Flutter가 보이지 않는 레이어를 건너뛌도록 하세요. Flutter 팀에 따르면 overdraw 감소로 paint 시간을 최대 30% 줄일 수 있습니다.
각 RepaintBoundary와 일부 위젯(Opacity, ClipRRect, Transform)은 별도 레이어(PictureLayer)를 생성합니다. 너무 많은 레이어는 콤포지션 시간을 증가시킵니다. 그룹핑을 사용하세요: 개별 요소에 여러 Opacity 위젯을 사용하는 대신 컨테이너에 하나의 Opacity를 적용하세요. 각 요소에 ClipRRect 대신 공통 컨테이너에 ClipRRect를 사용하세요.
위젯이 const로 선언되면 Flutter는 구성이 변경되지 않을 것을 알고 해당 RenderObject를 재생성 없이 재사용할 수 있습니다. const 생성자는 가비지 컬렉션 부하를 줄이고 첫 프레임을 가속합니다. 고정 매개변수가 있는 위젯(아이콘, 제목, 장식 요소)에 const를 사용하세요.
const Text("Title", style: TextStyle(fontSize: 24));
const Icon(Icons.home, color: Colors.blue);
const SizedBox.shrink();
자주 묻는 질문
Widget Tree는 재빌드마다 재생성되는 가볍은 인터페이스 구성입니다. RenderObject Tree는 지속되며 기하학적 구조나 외관이 변경될 때만 업데이트되는 무거운 렌더 객체 트리입니다.
Flutter DevTools를 사용하세요 — "Rendering" 탭으로 이동하세요. 렌더링 레이어, layout과 paint 시간, 그리고 각 RenderObject에 대한 상세 정보(크기, 제약, dirty 플래그)를 볼 수 있습니다.
Dirty는 업데이트가 필요하다고 표시된 RenderObject입니다. 구성이 변경될 때 _needsLayout 또는 _needsPaint 플래그가 설정되고, Flutter는 다음 프레임에서 해당 노드에 대한 layout 또는 paint를 수행합니다.
네, RenderBox의 하위 클래스를 만들고 performLayout과 paint 메서드를 재정의하세요. 커스텀 RenderObject를 Widget Tree에 임베드하려면 RenderObjectWidget을 사용하세요. 이것은 비표준 렌더링을 위한 고급 기법입니다.
RepaintBoundary는 RenderRepaintBoundary를 생성하여 RenderObject Tree의 일부를 별도 레이어로 검리합니다. 콘텐츠가 변경되면 해당 레이어만 재그려지고 나머지 트리는 그대로 유지됩니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.