https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html
위젯 트리의 특정 부분을 LayOut, Painting, Composition으로 구성된 렌더링 파이프라인의 기본적인 부분.
이게 무슨 소리인고 하니, Flutter의 렌더링 파이프라인은 다음과 같이 구성되어 있다.

쉽게 이야기 하면, Flutter가 개발자가 만든 코드를 UI로 보여주는데 거치는 단계들을 렌더링 파이프라인이라고 이야기 할 수 있다.
LayOut
Widget의 크기와 위치를 결정하고 그 정보를 RenderObject 트리에 저장, 어디에 얼만큼의 크기로 위치해야 하는지를 결정
Painting
이 과정에서 그리기 작업량을 줄여서 성능을 향상시키기 위해 UI를 여러 그래픽 Layer로 분할, 각 레이어를 어떻게 그러야하는지를 결정
=> Layer Tree 가 생성
Composition(= submit)
생성된 레이어 트리를 GPU 스레드에 제출
여기서 RepaintBoudary 클래스가 하는 역할은
Painting 과정에서 개발자가 페이지의 어느부분을
다시 그릴 필요가 없는지를 결정할 수 있게 된다.

위에 사진처럼 RepaintBoundary를 개발자가 활용함으로써, 붉은 색으로 칠해진 노드의 객체들은 다시 그려지지 않고, 자기만의 레이어를 갖게 된다.
결국 결론은 다시 그리지 않을 부분을 그리지 않게 하여 성능의 향상을 갖고 올 수 있다는 것!
더 쉽게 이야기하면 도화지에 그림을 그려놓고, 그 그림을 처음부터 다시 그리는게 아니라 필요한 부분(RepaintBoundary)만 다시 그리는 것이라고 이해하면 되겠다.

데모에서는 드래그가 아주 스무스하게 되지만,
원래는 저 드래그가 끊겨서 사용자의 터치 입력을 바로 반영하지 못했다.
처음엔 Devtool Performance 를 보면서 디버깅을 시도했지만
Jank 조차 뜨지 않아서 띠용 했는데...
무거운 UI 작업 수행
퍼즐 조각의 위치를 다시 계산하는 작업과, 각 프레임에 렌더링하는 작업이 포함되어 있었다.
잦은 상태 변경
드래그 하는 동안 blocksNotifier를 업데이트 했고, setState()가 자주 호출되어서 많은 UI 부분이 다시 렌더링 되어 성능이 저하되었다.
렌더링 경계 및 위젯의 복잡성
퍼즐 조각이 CustomPaint, ClipPath, ValueListenableBuilder로 표시
각각 계산은 부하를 주며, 조각이 움직일때마다 다시 그려지기 때문에 GPU에 부담을 준다.
=> 드래그를 할때마다 위젯 트리를 다시 그렸기 때문에 프레임 드랍이 발생해서 끊김 현상이 발생한 것...
child: RepaintBoundary(
child: Container(
child:map.jigsawBlockWidget,
),
),
jigsawBlockWidget 이 드래그가 이루어지는, 즉 퍼즐을 맞추는 부분이라고 생각하면 된다. 다시 그릴 부분을 이렇게 제한하니 마법처럼 드래그 지연이 해결...
게임기능에 추후에 통신기능과 제어권을 돌아가면서 넘겨주는 기능도 추가해야기에
최대한 성능 최적화를 하려고 노력했다.
child: GestureDetector(
onPanStart: (details) {
if (map.value.jigsawBlockWidget.imageBox.isDone) {
return;
}
// 💡 드래그 시작 시 setState 최소화
// 드래그 시작 시 특정 조각만의 상태를 갱신하여 전체적인 리빌드를 피합니다.
setState(() {
print(_pos);
_pos = details.localPosition;
_index = map.key;
});
},
onPanUpdate: (details) {
// 💡 드래그 중인 조각의 상태만 갱신
// 🚨 Null check operator used on a null value 해결
if (_index != null) {
setState(() {
blockNotDone[_index!].offset += details.delta;
});
}
},
따라서 onPanStart, onPanUpdate로 구분해서
드래그 시작에 할 작업, 드래그 중일때 할 작업을 세분화 시켜서 코드를 수정했다.
각각을 별도 레이어로 렌더링 분리해서 진행하면 더 좋은거 아닌가...? 할수도 있겠지만,
으로 오히려 더 느려질 수 있으니, 정말로 빈번히 재렌더링 되는 경우 혹은 복잡한 UI 요소에만 사용하는 것으로 하자.
TEST 환경

Devtools 라는 아주 좋은 도구를 flutter는 제공해준다.
이를 활용하여 한 프레임의 TimeLine을 비교하기로 한다.
RepaintBoundary 적용 전

RepaintBoundary 적용 후

적용 전에는 PAINT 작업이 98.691 ms, BUILD 작업이 89.309 ms가 소요되었고, RepaintBoundary 적용 후에는 PAINT 작업이 7.443 ms, BUILD 작업이 24.873 ms가 소요!