User Input & Animation : user에게 Input을 새로받거나, 새로운 이벤트로 인해 build()가 불립니다.
Build :Build가 불리면 Widget Tree를 구성하고, 이는 Element Tree와 Render Tree를 구성합니다. ===============Rendering!==================
Layout : Render Tree의 Object(RenderObject)가 레이아웃을 구성합니다. 이 때 Constraints 혹은 BoxConstraints를 활용해 계산된 사이즈를 child에게 차례대로 건네 줍니다.
Paint : Render Tree의 Object(RenderObject)는 말 그대로 pixel단위로 캔버스라는 곳에 페인팅을 진행합니다.
Compositing : 합성이라는 뜻으로, 스크롤바 처럼 이어지는 동작의 경우 여러가지 Paint된 캔버스들을 합성하는 단계입니다. 이때 Layer tree라는 것을 활용하며, Layer Tree의 하나의 Object가 캔버스라고 할 수 있습니다. ** 이때 Flutter는 RenderObject 여러개를 하나의 레이어로 그룹화하므로, 레이어가 구분되어 불필요한 추가 페인팅 작업을 줄일 수 있습니다. 근데, 사용자가 RepaintBoundary() 위젯을 통해 자체적인 레이어를 구분해준다면, 해당 레이어를 기준으로 페인팅 작업이 이루어지기 때문에, 성능이 향상될 수도 있습니다.
Rasterize : 이제 Layer Tree를 전달받아 프린트 가능한 픽셀 형태로 전환하는 작업을 진행합니다.
RenderRepaintBoundary : Global key를 활용해 Render Object를 찾아 냅니다. 이 때 선언되는 RenderRepaintBoundary 클래스는 Render Object를 나타내는 클래스입니다.
이를 활용해 toImage()함수로 Image를 얻어 냅니다.
MediaQuery.of(context).devicePixelRatio : toImage의 파라미터로 pixelRatio를 넣어주는데, 이는 Logical Pixel과 Output Image간의 배율을 의미합니다. 사실 MediaQuery에서 얻어지는 값을 굳이 줄 필요는 없지만, physical Pixel 개수를 넣어주기 위해 MediaQuery를 활용했습니다.
이후에 Uint8 List로 바꾸어준다음에 File에 기록을 해주면 파일형태의 이미지가 완성됩니다.
import 'package:flutter/rendering.dart';
import 'package:flutter_native_image/flutter_native_image.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:ui' as ui;
import 'dart:io'
...
final path = join(
(await getTemporaryDirectory()).path,
'${DateTime.now()}.png',
);
if(gkeytemp!=null){
final RenderRepaintBoundary rojecet = gkeytemp.currentContext!.findRenderObject() as RenderRepaintBoundary;
final ui.Image tempscreen = await rojecet.toImage(
pixelRatio:MediaQuery.of(gkeytemp.currentContext!).devicePixelRatio
);
final ByteData? byteData = await tempscreen.toByteData(format:ui.ImageByteFormat.png);
final Uint8List png8Byttes =byteData!.buffer.asUint8List();
final File file = File(path);
await file.writeAsBytes(png8Byttes);
}else{
print("ERROR!!");
}
...