[flutter] widget tree, key and BuildContext
flutter framework와 rendering
여기에 framework 에 대한 설명이 있다. flutter framework는 gui를 화면에 display할때, 상태가 변하는 gui를 표현하기 위해선 반드시 필요한 framework이다. 단순한 그림이라면 화면에 해당하는 framebuffer에 그림만 올려놓으면 되지만, 시간에 따라 변하는 animation이나 이벤트에 반응해서 변경되는 UI는 모두 framebuffer에 올려지는 그림 데이터가 변경이 된다. 매번 60Hz의 주사율을 갖는 display를 갖는다고 정해놓은 flutter에서는 60번의 새로 고침이 일어나는데, flutter framework는 rendering을 해서 새로운 그림을 만들어서 framebuffer에 채워넣는것이다. 그리고 framebuffer에 채워넣은 그림은 새로고침에 의해서 display device에서 보여진다.
How is it works for rendering.
flutter에선 화면을 나타내는 UI를 widget을 사용해서 표현한다. widget으로 만들어지는 UI는 widget tree라는 tree 구조로 되어 있다. browser에서 보여지는 화면 UI는 DOM tree라는 tree구조로 되어 있는 것과 같다고 생각하면 된다. widget tree의 각각의 요소들이 가진 속성값을 꺼내서 속성값에 맞게 UI를 만들어내는 rendering작업은 시간이 오래걸리고 부하가 걸린다. 애니메이션을 표현하기 위해서 60 frame/seconds로 그림을 나타낸다고 하자. 이건 새로운 widget tree를 60번 rendering해서 framebuffer에 넣어야 한다는 것을 의미한다. widget tree의 모든 것을 새로 생성해서 그려주는 방식이 비효율적이기 때문에 flutter에서는 widget을 2가지로 나누었다. stateless widget과 stateful widget이다.
stateless widget과 stateful widget
flutter를 사용해서 우리가 programming하는 것은 화면을 만드는 것이다. 화면은 flutter가 제공하는 widget을 레고블럭처럼 조립하는거라고 생각해도 된다. 이렇게 조립해서 화면을 만들때, 우리는 어떤 식으로던, custom widget을 만들어야 한다. stateless widget을 만들던, stateful widget을 만들던, widget을 만들어야 하고, widget들은 모두 공통적인게 build()가 포함되어 있다. build함수에서는 widget을 return한다. return되는 widget은 다른 widget을 child는 children으로 지정해서 부모 자식간의 관계로 만든다. 이것은 tree형태로 되어진다. build함수에서 return하는 widget은 tree형식으로 연결되어 있는 widget tree를 return한다고 보면 된다. 화면 page를 구성하는 widget을 만들고, build()에서 widget tree를 return할수도 있고, 화면의 일부분을 widget으로 만들어 build()에서 widget tree로 return할 수도 있다.
widget tree
어떤 code를 보고서도 widget tree를 말할 수 있어야 한다. widget tree는 widget간의 부모 자식간의 관계로 이루어진다. 이 관계가 반드시 있어야 한다. 이 관계는 widget을 생성할때 지정될 수도 있고, build에서 만들어질 수 있다. 둘간의 차이는 있다. 생성자에서 부모자식간의 관계를 맺는 경우는 container widget들의 관계를 고정적으로 설정할 때 사용되고, build함수에서 return할때 객체들의 관계를 설정하는것은 동적인 경우라고 한다. 예를 들어보자.
아래는 생성자에서 child를 지정하는 경우다.
Container(
child: Column(
children: [
Text('위젯 1'),
Text('위젯 2'),
],
),
)
아래는 흔히 custom widget을 만들때 사용하는 build함수에서 return시에 widget을 생성하고 child를 지정하는 경우다.
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('위젯 1'),
Text('위젯 2'),
],
);
}
두 방식의 차이점은 있다고 한다. 그런데 여기서 focus하는것은 code를 보고 widget tree를 말 할수 있는가?이기 때문에 그것에 대한 예는 다음과 같다. 아래의 예는 다음 강좌 에서 참고했다.
void main() => runApp(
Center(
child: RichText(
text: TextSpan(
text: 'Hello World',
),
),
),
);
여기서 widget tree는 Center-> RichText 두개로 구성되어 있다. TextSpan이나 text의 인자인 Text widget은 widget tree를 구성하는 node가 될 수 없다. 또 다른 예를 들어보자.
void main() => runApp(
Container(
color: Color(0xFFFFFFFF),
child: Center(
child: RichText(
text: TextSpan(
text: 'hell world',
),
),
),
),
);
여기서 widget tree는 Container -> Center -> RichText다. 3개의 node로 되어 있는 tree다.
build()와 widget tree
widget이 가진 build()가 언제 호출될 것인가?가 궁금했다. widget 객체가 생성된다고 해서 build()가 호출되지는 않는다. 객체 생성때 호출되는 함수는 생성자 함수밖에 없다. 나머지 함수는 외부에서 호출된다. 그리고 build()는 원래 framework에서 호출되는 것이니 말이다. 그렇다면, framework가 알아서 build()호출한다고 생각하면 되는가? 그렇지는 않다. document에는 widget이 가진 build()가 언제 호출되는 지 나와 있다.
말할려면, 먼저 widget tree를 말해야 한다. 왜냐면 build()는 모든 widget이 가지고 있기 때문에, widget tree에서 어떤 widget의 build()가 호출 되느냐?가 명시되어야 하기 때문이다. 호출된 경우와, widget tree에서 부모의 build()가 호출된 경우 두가지다.
immutable widget
우리가 flutter로 프로그램을 만든다는 것은 custom한 widget을 만들고 이 widget의 child를 엮어서 widget tree를 만든다고 했다. 여기서 custom한 widget을 만들때, 선택할 수 있는 widget의 종류는 3가지다. stateless widget, stateful widget, inheritance widget 세가지다. 3가지 모두 widget을 상속 받는데, 모두 immutable하다. 즉 만든 widget은 고정적인 widget이란 뜻이다.