[072] 플러터 (Flutter) 배우기 - 위젯을 이미지로 변환 및 저장하기(Widget to Image)

2024. 3. 23. 17:58모바일어플개발/Flutter

반응형

안녕하세요~ totally 개발자입니다.

 

플러터 앱을 개발하다보면 플러터 화면에 표시된 위젯들 중 일부 또는 전체를 이미지로 변환해서 저장하는 기능을 제공해야 하는 경우가 있습니다. 이를 구축하기 위해 아래 패키지를 사용할 수 있습니다.

https://pub.dev/packages/flutter_image_saver

 

flutter_image_saver | Flutter package

Simple and effective cross platform image saver for flutter, supported web and desktop.

pub.dev

 

Step 1: 먼저 pubspec.yaml 파일을 열어주시고 flutter_image_saver 패키지를 추가합니다.

 

 

Step 2: 패키지 dart.ui, rendering, flutter_image_saver를 각각 추가해줍니다.

 

 

Step 3: repaintBoundary 변수를 선언합니다.

 

 

Step 4: 아래처럼 간단하게 UI를 구성합니다. (모든 소스 코드는 맨 아래에 있습니다)

 

 

Step 5: body 부분에 SingleChildScrollView 바깥으로 RepaintBoundary 위젯을 감싸주시고 위에서 선언한 GlobalKey인 repaintBoundary 변수를 할당합니다. 즉 이 방식은 RepaintBoundary 위젯으로 감싸줌으로 원하는 범위를 설정할 수 있도록 해줍니다. 여기에서는 body 전체를 범위로 설정한 것입니다.

 

 

Step 6: 아래 save() 메소드를 선언해서 만들어줍니다. 맨 아래에 복사를 위한 전체 코드가 있습니다만 직접 타이핑 해보시면서 과정을 이해하는 것을 권장합니다. 먼저 38-39번째 줄에서 렌더링할 부분을 찾고, 40번째 줄에서 이 범위를 이미지로 변환하고, 41번째 줄에서 이를 바이트 데이터 png로 변환합니다. 42번쨰 줄에서 이 이미지를 flutter.png라는 이름으로 저장합니다. 43-45번째 줄을 통해 저장된 경로가 있는 경우 여부를 판단해서 결과를 표시합니다. (아래 코드는 pub dev 문서와 동일합니다)

 

 

Step 7: save 메소드를 호출할 수 있도록 버튼을 만들어줍니다.

 

 

Step 8: 안드로이드 설정을 위해 android > app > src > main > AndroidManifest.xml에 들어가셔서 아래 코드를 넣습니다.

android:requestLegacyExternalStorage="true"

 

 

 

Step 9: ios 설정을 위해 ios > Runner > info.plist에 다음 내용을 추가합니다 <string>에 있는 내용은 개발하시는 목적에 따라 문구를 적절하게 작성해주셔야 앱 심사할 때 거절되지 않습니다. 이 점 반드시 유의하시기 바랍니다.

 

<key>NSPhotoLibraryAddUsageDescription</key>

<string>내용을 이미지로 저장하도록 허용합니다.</string>

 

 

 

Step 10: 실행해서 테스트한 결과입니다.

 

 

버튼을 누르면 권한 관련 팝업이 나오고 저장이 됩니다.

 

아래처럼 갤러리에 들어가면 아래처럼 확인이 됩니다.

 

 

[전체 소스 코드]

 

import 'package:flutter/material.dart';
import 'dart:ui';
import 'package:flutter/rendering.dart';
import 'package:flutter_image_saver/flutter_image_saver.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test App',
home: const TestView(),
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
);
}
}
class TestView extends StatefulWidget {
const TestView({super.key});
@override
State<TestView> createState() => _TestViewState();
}
class _TestViewState extends State<TestView> {
final repaintBoundary = GlobalKey();
void save() async {
final boundary = repaintBoundary.currentContext!.findRenderObject()!
as RenderRepaintBoundary;
final image = await boundary.toImage(pixelRatio: 2);
final byteData = await image.toByteData(format: ImageByteFormat.png);
final path = await saveImage(byteData!.buffer.asUint8List(), 'flutter.png');
final message = path.isEmpty ? 'Saved' : 'Saved to $path';
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(message)));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Widget To Image"),
centerTitle: true,
),
floatingActionButton: FloatingActionButton(
onPressed: save,
child: const Icon(Icons.download),
),
body: RepaintBoundary(
key: repaintBoundary,
child: SingleChildScrollView(
child: Container(
color: Colors.white,
child: Column(
children: [
Text("제목 1", style: TextStyle(fontSize: 24)),
Text("내용 1-1"),
Text("내용 1-2"),
Container(
width: 200,
height: 200,
color: Colors.green,
),
Text("제목 2", style: TextStyle(fontSize: 24)),
Text("내용 2-1"),
Text("내용 2-2"),
Container(
width: 200,
height: 200,
color: Colors.purple,
),
],
),
),
),
),
);
}
}
view raw main.dart hosted with ❤ by GitHub

 

반응형