Flutter列表视图截图插件listview_screenshot的使用

发布于 1周前 作者 zlyuanteng 来自 Flutter

Flutter列表视图截图插件listview_screenshot的使用

标题

listview_screenshot

内容

flutter全平台针对ListView等滚动视图实现长截图,

Getting started

dart pub add listview_screenshot

Usage

  • 参考示例代码:列表截图示例
  • 关键是 WidgetShotRenderRepaintBoundary.screenshot
final GlobalKey _shotKey = GlobalKey();
final ScrollController _scrollController = ScrollController();

child: WidgetShot(
  key: _shotKey,
  controller: _scrollController,
  child: ListView.builder(
    controller: _scrollController,
    // ...
  ),
),
void onScreenshot() async {
  EasyLoading.show(status: '正在创建截图,请勿操作');
  var context = _shotKey.currentContext!;
  WidgetShotRenderRepaintBoundary repaintBoundary =
      context.findRenderObject() as WidgetShotRenderRepaintBoundary;
  Uint8List pngBytes;
  try {
    pngBytes = await repaintBoundary.screenshotPng( // 或者调用screenshotImage得到image库的Image对象,
      backgroundColor: Colors.white,
      workerName: 'imageMergeTransform', // web异步线程合并要生成对应js文件,否则不传,
      onProcess: (current, total) {
        if (current == 0) {
          EasyLoading.show(status: '正在合并截图,请勿操作');
        } else {
          EasyLoading.showProgress(current / total,
              status: '正在创建截图,请勿操作, $current/$total');
        }
      },
    );
  } catch (e) {
    EasyLoading.dismiss();
    EasyLoading.showError('生成截图失败: ${e.toString()}');
    return;
  }
  EasyLoading.show(status: '正在保存长截图...');
  // ... save pngBytes to png file,
}
  • web异步线程合并支持
    • 下载到flutter项目web目录下,使用如下代码编译出js文件。
    • 生成的js文件名填写到screenshot方法参数workerName,同时生成的js.deps和js.map文件仅调试使用,不必须。
dart compile js imageMergeTransform.dart -o imageMergeTransform.js -O4
  • html渲染模式无法截图
    • 默认手机浏览器访问就是html渲染模式,可以在index.html设置指定强制使用canvaskit模式。
onEntrypointLoaded: function(engineInitializer) {
  engineInitializer.initializeEngine({
    renderer: "canvaskit",
  }).then(function(appRunner) {
    appRunner.runApp();
  });
}

TODO

  • 可能出现一像素空隙。

示例代码

import 'package:flutter/material.dart';

import 'package:intl/intl.dart';
import 'package:logging/logging.dart';

import 'src/app.dart';
import 'src/settings/settings_controller.dart';
import 'src/settings/settings_service.dart';

void main() async {
  final format = DateFormat('HH:mm:ss');
  // 配置日志用于调试
  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((record) {
    if (record.loggerName != 'listview_screenshot') {
      return;
    }
    debugPrint('${format.format(record.time)}: ${record.loggerName}: ${record.level.name} ${record.message}');
  });

  // 设置用户偏好主题控制器,将用户设置与多个Flutter组件绑定
  final settingsController = SettingsController(SettingsService());

  // 在启动屏幕加载用户偏好主题,防止初次显示时突然主题变化
  await settingsController.loadSettings();

  // 运行应用并传递设置控制器。 应用监听设置控制器的变化,然后将其传递给设置视图
  runApp(MyApp(settingsController: settingsController));
}
class MyApp extends StatelessWidget {
  final SettingsController settingsController;

  MyApp({required this.settingsController});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  [@override](/user/override)
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final GlobalKey _shotKey = GlobalKey();
  final ScrollController _scrollController = ScrollController();

  void onScreenshot() async {
    EasyLoading.show(status: '正在创建截图,请勿操作');
    var context = _shotKey.currentContext!;
    WidgetShotRenderRepaintBoundary repaintBoundary =
        context.findRenderObject() as WidgetShotRenderRepaintBoundary;
    Uint8List pngBytes;
    try {
      pngBytes = await repaintBoundary.screenshotPng( // 或者调用screenshotImage得到image库的Image对象,
        backgroundColor: Colors.white,
        workerName: 'imageMergeTransform', // web异步线程合并要生成对应js文件,否则不传,
        onProcess: (current, total) {
          if (current == 0) {
            EasyLoading.show(status: '正在合并截图,请勿操作');
          } else {
            EasyLoading.showProgress(current / total,
                status: '正在创建截图,请勿操作, $current/$total');
          }
        },
      );
    } catch (e) {
      EasyLoading.dismiss();
      EasyLoading.showError('生成截图失败: ${e.toString()}');
      return;
    }
    EasyLoading.show(status: '正在保存长截图...');
    // ... save pngBytes to png file,
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Listview Screenshot'),
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              controller: _scrollController,
              itemCount: 1,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text('Item $index'),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

更多关于Flutter列表视图截图插件listview_screenshot的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter列表视图截图插件listview_screenshot的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,关于如何在Flutter项目中使用listview_screenshot插件来实现列表视图的截图功能,下面是一个详细的代码示例。这个示例展示了如何集成该插件并捕获ListView的截图。

首先,确保你已经在pubspec.yaml文件中添加了listview_screenshot依赖:

dependencies:
  flutter:
    sdk: flutter
  listview_screenshot: ^最新版本号  # 请替换为当前最新版本号

然后,运行flutter pub get来安装依赖。

接下来,是一个完整的示例代码,展示了如何使用listview_screenshot插件:

import 'package:flutter/material.dart';
import 'package:listview_screenshot/listview_screenshot.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter ListView Screenshot Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<String> items = List<String>.generate(50, (i) => "Item $i");
  File? screenshotFile;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ListView Screenshot Demo'),
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: ListViewScreenshot(
              controller: ScrollController(),
              child: ListView.builder(
                itemCount: items.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(items[index]),
                  );
                },
              ),
              onCaptured: (File image) {
                setState(() {
                  screenshotFile = image;
                });
                print("Screenshot saved to ${screenshotFile!.path}");
              },
            ),
          ),
          ElevatedButton(
            onPressed: () async {
              // 这里我们假设已经捕获了截图,并显示或处理它
              if (screenshotFile != null) {
                // 可以在这里添加代码来显示截图,例如使用Image.file(screenshotFile!)
                // 或者将截图分享、保存等
                print("Screenshot is ready to use: ${screenshotFile!.path}");
              } else {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('No screenshot captured yet!')),
                );
              }
            },
            child: Text('Use Screenshot'),
          ),
        ],
      ),
    );
  }
}

在这个示例中:

  1. 我们创建了一个包含50个条目的ListView。
  2. 使用ListViewScreenshot包装ListView,并为其提供一个ScrollController
  3. onCaptured回调中,当截图完成时,保存截图文件并打印其路径。
  4. 添加了一个按钮,用于在截图捕获后执行一些操作(在这个示例中只是打印截图路径,但你可以根据需要进行其他操作,如显示截图、分享等)。

请注意,实际应用中可能需要处理权限问题(如存储权限),特别是在Android和iOS平台上保存截图文件时。此外,确保在发布应用前测试截图功能在不同设备和屏幕分辨率下的表现。

回到顶部