chunked_widget_to_image 0.0.2 copy "chunked_widget_to_image: ^0.0.2" to clipboard
chunked_widget_to_image: ^0.0.2 copied to clipboard

A Flutter plugin to convert widgets to image files with support for large images via chunking.

example/lib/main.dart

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:path_provider/path_provider.dart';
import 'package:chunked_widget_to_image/chunked_widget_to_image.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // here
      navigatorObservers: [FlutterSmartDialog.observer],
      // here
      builder: FlutterSmartDialog.init(),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  WidgetToImageController widgetToImageController = WidgetToImageController();
  double width = 9999;
  final TextEditingController _widthController = TextEditingController();
  FocusNode textFieldFocusNode = FocusNode();
  String? exportedFilePath;

  @override
  void initState() {
    super.initState();
    _widthController.text = width.toString();

    // 添加焦点监听器
    textFieldFocusNode.addListener(() {
      if (!textFieldFocusNode.hasFocus) {
        setState(() {
          width = double.tryParse(_widthController.text) ?? width;
          width = width.clamp(2048, 16384);
          _widthController.text = width.toString();
        });
      }
    });
  }

  @override
  void dispose() {
    textFieldFocusNode.dispose();
    _widthController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        // 点击屏幕任意位置收起键盘
        textFieldFocusNode.unfocus();
      },
      child: Scaffold(
        appBar: AppBar(title: const Text('Widget to Image example')),
        body: Column(
          children: [
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: TextField(
                focusNode: textFieldFocusNode,
                controller: _widthController,
                decoration: const InputDecoration(
                  labelText: '图片宽度',
                  border: OutlineInputBorder(),
                ),
                keyboardType: TextInputType.number,
                textInputAction: TextInputAction.done,
              ),
            ),
            Expanded(
              child: FittedBox(
                child: WidgetToImage(
                  controller: widgetToImageController,
                  child: Image.asset(
                    'assets/test_img.jpg',
                    width: width,
                    fit: BoxFit.fitWidth,
                    frameBuilder:
                        (context, child, frame, wasSynchronouslyLoaded) {
                          if (frame == null) {
                            return const SizedBox(
                              width: 400,
                              height: 400,
                              child: Center(child: CircularProgressIndicator()),
                            );
                          }
                          return child;
                        },
                  ),
                ),
              ),
            ),
            if (exportedFilePath != null)
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: SelectableText(
                  '导出路径: $exportedFilePath',
                  style: const TextStyle(fontSize: 12, color: Colors.green),
                ),
              ),
          ],
        ),
        bottomNavigationBar: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    onPressed: () => _exportImage(ImageFormat.png),
                    child: const Text('导出 PNG'),
                  ),
                  ElevatedButton(
                    onPressed: () => _exportImage(ImageFormat.jpg),
                    child: const Text('导出 JPEG'),
                  ),
                ],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    onPressed: () => _exportImageOffScreen(ImageFormat.png),
                    child: const Text('离屏 PNG'),
                  ),
                  ElevatedButton(
                    onPressed: () => _exportImageOffScreen(ImageFormat.jpg),
                    child: const Text('离屏 JPEG'),
                  ),
                ],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    onPressed: () => _exportLongImageOffScreen(ImageFormat.png),
                    child: const Text('离屏长图PNG'),
                  ),
                  ElevatedButton(
                    onPressed: () => _exportLongImageOffScreen(ImageFormat.jpg),
                    child: const Text('离屏长图JPEG'),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }

  /// 导出图片
  Future<void> _exportImage(ImageFormat format) async {
    // 记录开始时间
    final startTime = DateTime.now();
    // 获取临时目录
    Directory tempDir = await getTemporaryDirectory();
    if (Platform.isAndroid) {
      tempDir = (await getExternalStorageDirectory())!;
    } else {
      tempDir = await getTemporaryDirectory();
    }
    String path =
        '${tempDir.path}/${DateTime.now().millisecondsSinceEpoch}.${format.name}';
    print('输出路径:$path');
    SmartDialog.showLoading(msg: '正在导出');
    widgetToImageController.toImageFile(
      outPath: path,
      pixelRatio: 1,
      format: format,
      callback: (result, message) {
        // 计算耗时
        final endTime = DateTime.now();
        final duration = endTime.difference(startTime);
        SmartDialog.dismiss();
        if (result) {
          setState(() {
            exportedFilePath = path;
          });
          if (width > 10000) {
            SmartDialog.showToast(
              '导出成功,低端机上不能显示太大图',
              alignment: Alignment.center,
            );
            return;
          }
          _showFullScreenPreview(path, duration);
        } else {
          setState(() {
            exportedFilePath = null;
          });
          SmartDialog.showToast(message, alignment: Alignment.center);
        }
      },
    );
  }

  /// 离屏导出图片
  Future<void> _exportImageOffScreen(ImageFormat format) async {
    // 记录开始时间
    final startTime = DateTime.now();
    // 获取临时目录
    Directory tempDir = await getTemporaryDirectory();
    if (Platform.isAndroid) {
      tempDir = (await getExternalStorageDirectory())!;
    } else {
      tempDir = await getTemporaryDirectory();
    }
    String path =
        '${tempDir.path}/${DateTime.now().millisecondsSinceEpoch}.${format.name}';
    print('输出路径:$path');
    SmartDialog.showLoading(msg: '正在导出');
    widgetToImageController.toImageFileFromWidget(
      Image.asset('assets/test_img.jpg', width: width, fit: BoxFit.fitWidth),
      outPath: path,
      pixelRatio: 1,
      format: format,
      targetSize: Size(width, width),
      delay: Duration(seconds: 2),
      callback: (result, message) {
        // 计算耗时
        final endTime = DateTime.now();
        final duration = endTime.difference(startTime);
        SmartDialog.dismiss();
        if (result) {
          setState(() {
            exportedFilePath = path;
          });
          if (width > 10000) {
            SmartDialog.showToast(
              '导出成功,低端机上不能显示太大图',
              alignment: Alignment.center,
            );
            return;
          }
          _showFullScreenPreview(path, duration);
        } else {
          setState(() {
            exportedFilePath = null;
          });
          SmartDialog.showToast(message, alignment: Alignment.center);
        }
      },
    );
  }

  /// 离屏长图导出图片
  Future<void> _exportLongImageOffScreen(ImageFormat format) async {
    var randomItemCount = 200;
    var myLongWidget = Builder(builder: (context) {
      return Container(
          padding: const EdgeInsets.all(30.0),
          decoration: BoxDecoration(
            border:
            Border.all(color: Colors.blueAccent, width: 5.0),
            color: Colors.redAccent,
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              for (int i = 0; i < randomItemCount; i++)
                Text("Tile Index $i",style: TextStyle(fontSize: 60.0),),
            ],
          ));
    });
    // 记录开始时间
    final startTime = DateTime.now();
    // 获取临时目录
    Directory tempDir = await getTemporaryDirectory();
    if (Platform.isAndroid) {
      tempDir = (await getExternalStorageDirectory())!;
    } else {
      tempDir = await getTemporaryDirectory();
    }
    String path =
        '${tempDir.path}/${DateTime.now().millisecondsSinceEpoch}.${format.name}';
    print('输出路径:$path');
    SmartDialog.showLoading(msg: '正在导出');
    widgetToImageController
        .toImageFileFromLongWidget(
      Material(
        child: myLongWidget,
      ),
      outPath: path,
      pixelRatio: 1,
      format: format,
      delay: Duration(seconds: 1),
      context: context,
      callback: (result, message) {
        // 计算耗时
        final endTime = DateTime.now();
        final duration = endTime.difference(startTime);
        SmartDialog.dismiss();
        if (result) {
          setState(() {
            exportedFilePath = path;
          });
          if (width > 10000) {
            SmartDialog.showToast(
              '导出成功,低端机上不能显示太大图',
              alignment: Alignment.center,
            );
            return;
          }
          _showFullScreenPreview(path, duration);
        } else {
          setState(() {
            exportedFilePath = null;
          });
          SmartDialog.showToast(message, alignment: Alignment.center);
        }
      },
    );
  }

  /// 图片预览
  Future<void> _showFullScreenPreview(
    String imagePath,
    Duration duration,
  ) async {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return Dialog(
          backgroundColor: Colors.black87,
          child: Stack(
            alignment: Alignment.center,
            children: [
              InteractiveViewer(alignment: Alignment.center,child: Image.file(File(imagePath))),
              Positioned(
                top: 10,
                right: 10,
                child: Column(
                  children: [
                    IconButton(
                      icon: const Icon(
                        Icons.close,
                        color: Colors.blue,
                        size: 20,
                      ),
                      onPressed: () => Navigator.of(context).pop(),
                    ),
                    Container(
                      padding: const EdgeInsets.all(8.0),
                      color: Colors.black54,
                      child: Text(
                        '耗时: ${duration.inMilliseconds} ms',
                        style: const TextStyle(color: Colors.white),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}
1
likes
140
points
76
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin to convert widgets to image files with support for large images via chunking.

Repository (GitHub)
View/report issues

Topics

#image #chunked-exporter #widget-to-image #libpng #libjpeg

Documentation

API reference

License

MIT (license)

Dependencies

ffi, flutter, plugin_platform_interface

More

Packages that depend on chunked_widget_to_image

Packages that implement chunked_widget_to_image