Flutter生成PDF文档插件flutter_to_pdf的使用
Flutter生成PDF文档插件flutter_to_pdf的使用
简介
FlutterToPDF
是一个可以将任何 Flutter 小部件导出为 PDF 文档的包,完全用 Dart 语言编写。它提供了多种功能,包括导出整个 PDF 文档、单个页面或特定的小部件,并允许你自定义导出选项,如页面格式、文本字段和复选框的样式。
安装
添加依赖
你可以通过运行以下命令来安装 flutter_to_pdf
:
$ flutter pub add flutter_to_pdf
这会在你的 pubspec.yaml
文件中添加如下内容(并隐式运行 flutter pub get
):
dependencies:
flutter_to_pdf: ^0.2.2
或者,如果你的编辑器支持 flutter pub get
,请查阅相关文档以获取更多信息。
导入包
在 Dart 代码中使用以下语句导入 flutter_to_pdf
包:
import 'package:flutter_to_pdf/flutter_to_pdf.dart';
功能特性
- 导出到 PDF 文档
- 导出到 PDF 页面
- 导出到 PDF 小部件
- 导出选项:包括页面格式、文本字段和复选框的选项
使用方法
基本用法
- 创建一个
ExportDelegate
实例。 - 使用
ExportFrame
包裹你想导出的小部件,并提供frameId
和exportDelegate
。 - 调用相应的导出函数(如
exportToPdfDocument
、exportToPdfPage
或exportToPdfWidget
),并传入frameId
。
示例代码
下面是一个完整的示例应用程序,展示了如何使用 flutter_to_pdf
插件将问卷表单和图表导出为 PDF 文档。
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_charts/flutter_charts.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_to_pdf/flutter_to_pdf.dart';
void main() => runApp(const Demo());
class Demo extends StatefulWidget {
const Demo({super.key});
@override
State<Demo> createState() => _DemoState();
}
class _DemoState extends State<Demo> {
final ExportDelegate exportDelegate = ExportDelegate(
ttfFonts: {
'LoveDays': 'assets/fonts/LoveDays-Regular.ttf',
'OpenSans': 'assets/fonts/OpenSans-Regular.ttf',
},
);
Future<void> saveFile(document, String name) async {
final Directory dir = await getApplicationDocumentsDirectory();
final File file = File('${dir.path}/$name.pdf');
await file.writeAsBytes(await document.save());
debugPrint('Saved exported PDF at: ${file.path}');
}
String currentFrameId = 'questionaireDemo';
@override
Widget build(BuildContext context) => MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
tabBarTheme: const TabBarTheme(
labelColor: Colors.black87,
),
),
home: DefaultTabController(
length: 2,
child: Scaffold(
key: GlobalKey<ScaffoldState>(),
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
title: const Text('Flutter to PDF - Demo'),
bottom: TabBar(
indicator: const UnderlineTabIndicator(),
tabs: const [
Tab(
icon: Icon(Icons.question_answer),
text: 'Questionnaire',
),
Tab(
icon: Icon(Icons.ssid_chart),
text: 'Charts & Custom Paint',
),
],
onTap: (int value) {
setState(() {
currentFrameId =
value == 0 ? 'questionaireDemo' : 'captureWrapperDemo';
});
},
),
),
bottomSheet: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () async {
final ExportOptions overrideOptions = ExportOptions(
textFieldOptions: TextFieldOptions.uniform(
interactive: false,
),
checkboxOptions: CheckboxOptions.uniform(
interactive: false,
),
);
final pdf = await exportDelegate.exportToPdfDocument(
currentFrameId,
overrideOptions: overrideOptions);
saveFile(pdf, 'static-example');
},
child: const Row(
children: [
Text('Export as static'),
Icon(Icons.save_alt_outlined),
],
),
),
TextButton(
onPressed: () async {
final pdf = await exportDelegate
.exportToPdfDocument(currentFrameId);
saveFile(pdf, 'interactive-example');
},
child: const Row(
children: [
Text('Export as interactive'),
Icon(Icons.save_alt_outlined),
],
),
),
],
),
body: TabBarView(
children: [
ExportFrame(
frameId: 'questionaireDemo',
exportDelegate: exportDelegate,
child: const QuestionnaireExample(),
),
ExportFrame(
frameId: 'captureWrapperDemo',
exportDelegate: exportDelegate,
child: const CaptureWrapperExample(),
),
],
),
),
),
);
}
class QuestionnaireExample extends StatefulWidget {
const QuestionnaireExample({super.key});
@override
State<QuestionnaireExample> createState() => _QuestionnaireExampleState();
}
class _QuestionnaireExampleState extends State<QuestionnaireExample> {
final firstNameController = TextEditingController();
final lastNameController = TextEditingController();
final dateOfBirthController = TextEditingController();
final placeOfBirthController = TextEditingController();
final countryOfBirthController = TextEditingController();
bool acceptLorem = false;
bool monday = false;
bool tuesday = false;
bool wednesday = false;
bool thursday = false;
bool friday = false;
bool saturday = false;
bool sunday = false;
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Dunef UG (haftungsbeschränkt)'),
Text(
'Questionnaire',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
),
),
],
),
if (MediaQuery.of(context).size.width > 425)
const Row(
children: [
Text(
'Date: ',
style: TextStyle(color: Colors.grey),
),
Text('04.04.2023'),
],
),
SizedBox(
height: 50,
child: Image.asset('assets/logo.png'),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Container(
height: 100,
width: 100,
decoration: const BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage('http://i.pravatar.cc/100'),
),
),
),
const SizedBox(width: 16),
Expanded(child: buildNameFields(MediaQuery.of(context).size.width)),
],
),
const SizedBox(height: 16),
buildBirthFields(MediaQuery.of(context).size.width),
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Divider(),
),
const Text(
'Lorem ipsum dolor sit amet...',
style: TextStyle(fontFamily: 'LoveDays'),
),
const SizedBox(height: 8),
Row(
children: [
Checkbox(
key: const Key('acceptLorem'),
value: acceptLorem,
onChanged: (newValue) => setState(() {
acceptLorem = newValue ?? false;
}),
),
const SizedBox(width: 8),
const Text(
'I hereby accept the terms of the Lorem Ipsum.',
style: TextStyle(fontFamily: 'OpenSans'),
),
],
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Divider(),
),
const Padding(
padding: EdgeInsets.only(bottom: 8.0),
child: Text(
'Please select your availability:',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
Table(
children: [
const TableRow(
children: [
Center(child: Text('Monday', maxLines: 1)),
Center(child: Text('Tuesday', maxLines: 1)),
Center(child: Text('Wednesday', maxLines: 1)),
Center(child: Text('Thursday', maxLines: 1)),
Center(child: Text('Friday', maxLines: 1)),
Center(child: Text('Saturday', maxLines: 1)),
Center(child: Text('Sunday', maxLines: 1)),
],
),
TableRow(
children: [
Checkbox(
key: const Key('monday'),
value: monday,
onChanged: (newValue) => setState(() {
monday = newValue ?? false;
}),
),
Checkbox(
key: const Key('tuesday'),
value: tuesday,
onChanged: (newValue) => setState(() {
tuesday = newValue ?? false;
}),
),
Checkbox(
key: const Key('wednesday'),
value: wednesday,
onChanged: (newValue) => setState(() {
wednesday = newValue ?? false;
}),
),
Checkbox(
key: const Key('thursday'),
value: thursday,
onChanged: (newValue) => setState(() {
thursday = newValue ?? false;
}),
),
Checkbox(
key: const Key('friday'),
value: friday,
onChanged: (newValue) => setState(() {
friday = newValue ?? false;
}),
),
Checkbox(
key: const Key('saturday'),
value: saturday,
onChanged: (newValue) => setState(() {
saturday = newValue ?? false;
}),
),
Checkbox(
key: const Key('sunday'),
value: sunday,
onChanged: (newValue) => setState(() {
sunday = newValue ?? false;
}),
),
],
),
],
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Divider(),
),
const Text(
'Thank you for filling out our questionnaire...',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 50),
],
),
),
);
Widget buildNameFields(double screenWidth) {
List<Widget> children = [
TextField(
key: const Key('firstName'),
controller: firstNameController,
decoration: const InputDecoration(
labelText: 'First name', border: OutlineInputBorder()),
),
TextField(
key: const Key('lastName'),
controller: lastNameController,
decoration: const InputDecoration(
labelText: 'Last name', border: OutlineInputBorder()),
),
];
if (screenWidth > 480) {
return SizedBox(
height: 50,
child: Row(
children: children
.map((Widget e) => Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
child: e,
)))
.toList(),
),
);
} else {
return Column(
children: children
.map((Widget e) => Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: SizedBox(
height: 50,
child: e,
),
))
.toList(),
);
}
}
Widget buildBirthFields(double screenWidth) {
List<Widget> children = [
TextField(
key: const Key('dateOfBirth'),
controller: dateOfBirthController,
decoration: const InputDecoration(
labelText: 'DateOfBirth', border: OutlineInputBorder()),
),
TextField(
key: const Key('placeOfBrith'),
controller: placeOfBirthController,
decoration: const InputDecoration(
labelText: 'Place of birth', border: OutlineInputBorder()),
),
TextField(
key: const Key('countryOfBirth'),
controller: countryOfBirthController,
decoration: const InputDecoration(
labelText: 'Country of birth', border: OutlineInputBorder()),
),
];
if (screenWidth > 480) {
return SizedBox(
height: 50,
child: Row(
children: children
.map((Widget e) => Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
child: e,
)))
.toList(),
),
);
} else {
return Column(
children: children
.map((Widget e) => Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: SizedBox(
height: 50,
child: e,
),
))
.toList(),
);
}
}
}
class CaptureWrapperExample extends StatelessWidget {
const CaptureWrapperExample({super.key});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: 400,
width: 400,
child: CaptureWrapper(
key: const Key('Chart'),
child: buildChart(),
),
),
CaptureWrapper(
key: const Key('CustomPaint'),
child: CustomPaint(
size: const Size(300, 300),
painter: HousePainter(),
),
),
],
),
);
}
Widget buildChart() {
LabelLayoutStrategy? xContainerLabelLayoutStrategy;
ChartData chartData;
ChartOptions chartOptions = const ChartOptions();
// Example shows a mix of positive and negative data values.
chartData = ChartData(
dataRows: const [
[2000.0, 1800.0, 2200.0, 2300.0, 1700.0, 1800.0],
[1100.0, 1000.0, 1200.0, 800.0, 700.0, 800.0],
[0.0, 100.0, -200.0, 150.0, -100.0, -150.0],
[-800.0, -400.0, -300.0, -400.0, -200.0, -250.0],
],
xUserLabels: const ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
dataRowsLegends: const [
'Big Corp',
'Medium Corp',
'Print Shop',
'Bar',
],
chartOptions: chartOptions,
);
var lineChartContainer = LineChartTopContainer(
chartData: chartData,
xContainerLabelLayoutStrategy: xContainerLabelLayoutStrategy,
);
return LineChart(
painter: LineChartPainter(
lineChartContainer: lineChartContainer,
),
);
}
}
class HousePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.brown
..style = PaintingStyle.fill;
// Draw the house body
const body = Rect.fromLTWH(50, 100, 200, 200);
canvas.drawRect(body, paint);
// Draw the roof
final roofPath = Path()
..moveTo(150, 20)
..lineTo(280, 100)
..lineTo(20, 100)
..close();
paint.color = Colors.red;
canvas.drawPath(roofPath, paint);
// Draw the door
const door = Rect.fromLTWH(125, 230, 50, 70);
paint.color = Colors.black;
canvas.drawRect(door, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
此示例展示了如何创建一个包含问卷表单和图表的应用程序,并提供了两个按钮用于将当前视图导出为静态或交互式的 PDF 文件。希望这个例子能帮助你更好地理解和使用 flutter_to_pdf
插件!
更多关于Flutter生成PDF文档插件flutter_to_pdf的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter生成PDF文档插件flutter_to_pdf的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个使用 flutter_to_pdf
插件在 Flutter 中生成 PDF 文档的示例代码。这个插件允许你在 Flutter 应用中创建和操作 PDF 文件。
首先,确保你已经在 pubspec.yaml
文件中添加了 flutter_to_pdf
依赖:
dependencies:
flutter:
sdk: flutter
flutter_to_pdf: ^4.0.0 # 请检查最新版本号
然后运行 flutter pub get
来获取依赖。
接下来是一个完整的示例代码,展示如何使用 flutter_to_pdf
插件生成一个简单的 PDF 文档:
import 'package:flutter/material.dart';
import 'package:flutter_to_pdf/flutter_to_pdf.dart';
import 'package:path_provider/path_provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter PDF Example'),
),
body: Center(
child: ElevatedButton(
onPressed: _generatePdf,
child: Text('Generate PDF'),
),
),
),
);
}
Future<void> _generatePdf() async {
// 获取应用文档目录
final directory = await getApplicationDocumentsDirectory();
final path = directory.path + '/sample.pdf';
// 创建 PDF 文档
final pdf = await FlutterToPdf.createPdf(
builder: (PdfDocument.Page page) {
page.drawText('Hello, Flutter PDF!', with: PdfCanvas(page), at: PdfPoint(100, 800));
page.drawText('This is a sample PDF document.', with: PdfCanvas(page), at: PdfPoint(100, 750));
},
);
// 保存 PDF 到文件
final file = File(path);
await file.writeAsBytes(pdf);
// 打开 PDF 文件(在 Android 和 iOS 上可能需要不同的处理方式)
if (Platform.isAndroid) {
// Android 上使用文件选择器打开
await FlutterToPdf.openPdf(path: path);
} else if (Platform.isIOS) {
// iOS 上使用 UIActivityViewController 分享
await FlutterToPdf.sharePdf(bytes: pdf, filename: 'sample.pdf');
}
}
}
代码解释
-
依赖导入:
flutter_to_pdf
用于生成 PDF 文档。path_provider
用于获取应用的文档目录路径。
-
主应用结构:
- 使用
MaterialApp
和Scaffold
创建一个简单的用户界面。 - 在中心位置放置一个按钮,点击按钮时调用
_generatePdf
方法。
- 使用
-
生成 PDF:
- 使用
getApplicationDocumentsDirectory
获取应用的文档目录路径。 - 使用
FlutterToPdf.createPdf
方法创建一个 PDF 文档。在builder
回调中,你可以使用PdfCanvas
和PdfPoint
等类来绘制内容。 - 将生成的 PDF 保存到文件中。
- 使用
-
打开或分享 PDF:
- 根据平台的不同,使用
FlutterToPdf.openPdf
或FlutterToPdf.sharePdf
方法打开或分享生成的 PDF 文件。
- 根据平台的不同,使用
这个示例展示了如何使用 flutter_to_pdf
插件生成一个简单的 PDF 文档,并在设备上打开或分享它。你可以根据需要进一步自定义 PDF 的内容和样式。