대부분 앱의 화면은 위젯을 여러 개 조합해서 구성한다. 그런데 한 화면을 구성하는 모든 위젯은 단일 트리 구조를 이룬다. 예를 들어 다음 그림에서 왼쪽처럼 화면을 구성했다면 위젯 객체는 오른쪽과 같은 트리 구조를 이룬다.
이 트리 구조대로 화면에 출력하려면 루트(root) 위젯 객체만 runApp() 함수에 전달하면 된다. 그러면 하위에 달린 위젯들까지 실행되어 화면을 구성한다.
/// 최상위 위젯 객체만 전달
void main() {
runApp(MyApp());
}
앞에서 위젯의 트리 구조를 알아봤는데 사실 플러터 프레임워크가 화면을 만들 때 2개의 트리 구조를 더 만든다. 바로 엘리먼트 트리(element tree)
와 렌더 트리 (render tree)
이다. 개발자는 위젯을 작성해야 하므로 위젯 트리만 알고 있으면 되지만, 프레임워크를 이해하는 차원으로 알아 두면 좋다.
앞에서 살펴봤듯이 위젯은 화면에 보일 뷰를 설명만 할 뿐 실제 화면에 출력할 대상은 아니다. 따라서 프레임워크에서 뷰 설명을 보고 위젯 트리를 참조해 실제 화면에 출력할 객체들을 별도의 트리 구조로 만든니다. 예를 들어 위젯을 아래처럼 구성했다고 가정해 보자.
Center(
child: Column(
children: [
Text("Hello"),
Text("World")
],
)
)
실제 앱을 개발할 때는 이보다 많은 위젯으로 복잡하게 화면을 구성하므로 코드만 보고 전체 위젯의 트리를 파악하기는 쉽지 않다. 이때는 안드로이드 스튜디오의 플러터 인스펙터(Flutter Inspector)
도구를 이용하면 된다.
코드를 작성한 후 안드로이드 스튜디오의 오른쪽 사이드 바에 있는 플러터 인스펙터(Flutter Inspector)
탭을 클릭하면 위젯의 트리 구조를 확인할 수 있다. 인스펙터 창에서 위젯 트리 영역에는 개발자가 작성한 위젯의 구조가 보이며, 이 가운데 하나를 클릭하면 위젯 상세 트리 영역에서 세부 정보를 확인할 수 있다.
코드에는 Center → Column → Text
위젯을 사용했지만 실제 위젯 트리에는 Text
하위에 RichText
라는 위젯까지 있다. Text
에서 RichText
위젯으로 화면에 출력할 문자열을 표현하기 때문이다.
그리고 각 위젯의 상세 설명에서 어떤 위젯은 renderObject
정보를 가지고 있고 어떤 위젯은 가지고 있지 않다. 이 renderObject
를 포함하는 위젯이 실제 화면에 그릴 정보를 가진 위젯이다.
인스펙터 창에서 본 위젯 트리를 그림으로 그려보면 다음과 같다. 이 위젯 트리는 개발자가 작성한 코드에 기초해 만들어진다. 위젯 트리에서 노랑 바탕으로 표시한 위젯은 renderObject
정보를 포함한다.
플러터 프레임워크는 이 위젯 트리를 기반으로 엘리먼트 트리를 만든다. 위젯 트리는 개발자가 작성하는 화면 구성 주문서라고 할 수 있다. 이 주문서를 보고 실제 화면을 구성하는 정보는 프레임워크 내부에서 엘리먼트 트리로 만든다. 위젯 객체 하나당 엘리먼트 객체를 하나씩 만들어 트리를 구성하며, 위젯보다는 더 상세한 정보가 담긴다.
엘리먼트 트리는 ComponentElement
와 RenderObjectElement
객체로 구성된다. ComponentElement
객체는 트리 구조에서 다른 객체를 포함하는 역할만 하며 화면에 출력할 정보를 가지지는 않는다. 실제 화면에 출력할 정보는 RenderObjectElement
에 담긴다.
그런데 엘리먼트 트리의 객체도 실제 화면에 무언가를 출력하지는 않는다. 단지 정보만 담고 있다. 이 엘리먼트 트리 정보를 바탕으로 실제 화면을 출력하는 렌더 트리가 만들어진다. 렌더 트리의 객체는 위젯 트리와 직접 연계되지 않는다. 또한 모든 엘리먼트 객체를 대상으로 렌더 트리의 객체가 하나씩 만들어지지 않고, 실제 화면에 출력할 정보를 가지는 RenderObjectElement
에 해당하는 객체로만 구성된다.
렌더 트리를 구성하는 객체는 실제 화면 출력하는 객체이다. 그림에서는 RenderObject
로만 표현했지만 실제로는 RenderObject
타입으로 표현되는 RenderDecorateBox
, RenderImage
, RenderFlex
등 다양한 객체가 사용된다.
위젯 트리 외에 엘리먼트 트리, 렌더 트리 등이 존재하는 이유는 화면 렌더링(출력) 속도 때문이다. 화면에 변경 사항이 발생할 때 얼마나 빨리 화면에 반영해 주는가는 프레임워크의 성능을 결정하는 중요한 요소이다. 예를 들어 사용자가 버튼을 클릭하면 보통은 버튼의 배경색을 살짝 변경했다가 원상 복구하는데, 이런 반응이 아예 없거나 1초 이상 걸린다면 어떨까? 사용자는 매우 답답할 것이다.
플러터는 네이티브 앱 수준의 성능을 목표로 한다. 따라서 화면에 변화가 있을 때 최적의 알고리즘으로 변경할 부분만 다시 렌더링해서 빠르게 반영하도록 설계됐다. 이런 부분을 개발자가 신경쓰지 않게 하려고 위젯 트리 이외에 엘리먼트 트리, 렌더 트리가 존재하는 것이다.