将整个可滚动 Flutter Widget 打印为 PDF - 需要帮助解决问题

问题描述 投票:0回答:1

我目前正在开发一个 Flutter 应用程序,我需要生成整个屏幕的 PDF,包括可滚动内容。我的方法是截取小部件的屏幕截图并将其转换为 PDF。但是,我面临两个主要问题:

  1. 打印整个小部件:当前的实现仅捕获屏幕上可见的内容,我需要一个解决方案来打印整个小部件,即使它由于滚动而超出了可见区域。

  2. 分割大图像:如果小部件的内容太大,我想将图像分割成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),
        ),
      ),
    );
  }
}

如果有人有见解或解决方案,特别是对于第一个问题,我将非常感谢您的帮助。如果您有第一个问题的信息但没有第二个问题的信息,您的意见仍然很有价值。预先感谢您!

flutter dart pdf-generation
1个回答
0
投票

首先,测试结果,看看它是否对您有帮助! 为了解决捕获整个小部件的第一个问题,包括由于滚动而导致的离屏内容,您可以使用 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,用于控制捕获图像时的滚动位置。如果您想要捕获特定的滚动位置,您可以相应地调整滚动控制器。

© www.soinside.com 2019 - 2024. All rights reserved.