我目前正在开发一个 Flutter 应用程序,我需要生成整个屏幕的 PDF,包括可滚动内容。我的方法是截取小部件的屏幕截图并将其转换为 PDF。但是,我面临两个主要问题:
打印整个小部件:当前的实现仅捕获屏幕上可见的内容,我需要一个解决方案来打印整个小部件,即使它由于滚动而超出了可见区域。
分割大图像:如果小部件的内容太大,我想将图像分割成PDF中的单独页面以提高可读性。
它需要在网络和电脑上运行
我一直在使用以下代码进行测试:
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
import 'package:screenshot/screenshot.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
final ScreenshotController screenshotController = ScreenshotController();
MyApp({super.key});
void _printScreen() async {
Uint8List? uint8list = await screenshotController.capture();
Printing.layoutPdf(
onLayout: (PdfPageFormat format) async {
final doc = pw.Document();
doc.addPage(
pw.Page(
pageFormat: format,
build: (pw.Context context) {
return pw.Image(pw.MemoryImage(uint8list!));
},
),
);
return doc.save();
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Screenshot(
controller: screenshotController,
child: ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return Container(
height: 100,
color: Colors.grey,
child: Center(
child: Text(
generateRandomText(),
style: const TextStyle(color: Colors.white),
),
),
);
},
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _printScreen,
child: const Icon(Icons.print),
),
);
}
String generateRandomText() {
final random = Random();
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return String.fromCharCodes(
Iterable.generate(
20,
(_) => chars.codeUnitAt(
random.nextInt(chars.length),
),
),
);
}
}
如果有人有见解或解决方案,特别是对于第一个问题,我将非常感谢您的帮助。如果您有第一个问题的信息但没有第二个问题的信息,您的意见仍然很有价值。预先感谢您!
首先,测试结果,看看它是否对您有帮助! 为了解决捕获整个小部件的第一个问题,包括由于滚动而导致的离屏内容,您可以使用 RepaintBoundary 小部件来捕获整个小部件树,甚至是离屏部分。这是实现此目的的代码的更新版本:
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
final GlobalKey _globalKey = GlobalKey();
final ScrollController _scrollController = ScrollController();
void _printScreen() async {
RenderRepaintBoundary boundary = _globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
ui.Image image = await boundary.toImage();
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List? uint8list = byteData?.buffer.asUint8List();
Printing.layoutPdf(
onLayout: (PdfPageFormat format) async {
final doc = pw.Document();
// Determine the number of pages based on the image height and page height
int totalPages = (image.height! / format.height).ceil();
for (int page = 0; page < totalPages; page++) {
int startY = (page * format.height).toInt();
int endY = ((page + 1) * format.height).toInt();
endY = endY > image.height! ? image.height! : endY;
if (startY < endY) {
// Calculate the start and end positions for the Uint8List
int startOffset = startY * image.width! * 4;
int endOffset = endY * image.width! * 4;
// Ensure that the endOffset is within the bounds of the Uint8List
endOffset = endOffset > uint8list!.length ? uint8list.length : endOffset;
final imageData = Uint8List.sublistView(uint8list, startOffset, endOffset);
doc.addPage(
pw.Page(
pageFormat: format,
build: (pw.Context context) {
return pw.Image(pw.MemoryImage(imageData));
},
),
);
}
}
return doc.save();
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RepaintBoundary(
key: _globalKey,
child: ListView.builder(
controller: _scrollController,
itemCount: 20,
itemBuilder: (context, index) {
return Container(
height: 100,
color: Colors.grey,
child: Center(
child: Text(
generateRandomText(),
style: const TextStyle(color: Colors.white),
),
),
);
},
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _printScreen,
child: const Icon(Icons.print),
),
);
}
String generateRandomText() {
final random = Random();
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return String.fromCharCodes(
Iterable.generate(
20,
(_) => chars.codeUnitAt(
random.nextInt(chars.length),
),
),
);
}
}
在此代码中,将 Screenshot 小部件替换为 RepaintBoundary,并使用 toImage 方法捕获整个小部件的渲染图像。然后,根据页面格式将图像分割成 PDF 中的单独页面,同时考虑到图像的高度和页面高度。
注意:ListView.builder 中添加了 _scrollController,用于控制捕获图像时的滚动位置。如果您想要捕获特定的滚动位置,您可以相应地调整滚动控制器。