Flutter中含Image子组件的Widget转图片时子图消失的解决方法
解决RenderRepaintBoundary截图时Image.asset子图消失的问题
问题描述
使用RenderRepaintBoundary结合GlobalKey截取Widget快照时,Image.asset('assets/images/img.png')子组件在生成的图片中消失,但屏幕上其他文本、按钮等元素均能正常保留在截图里。
原代码
import 'package:flutter/material.dart'; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key? key, this.title}) : super(key: key); final String? title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; Uint8List? ssBytes; bool isShowSs = false; final GlobalKey genKey = GlobalKey(); Future<void> takePicture() async { RenderRepaintBoundary boundary = genKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; ui.Image image = await boundary.toImage(); ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); ssBytes = byteData!.buffer.asUint8List(); setState(() { isShowSs = true; }); } void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { if(isShowSs) { return Image.memory(ssBytes!); } else { return RepaintBoundary( key: genKey, child: Scaffold( appBar: AppBar( title: Text(widget.title!), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ElevatedButton( onPressed: () async { await takePicture(); }, child: Text( "Take Picture", )), Image.asset('assets/images/img.png'), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ), ); } } }
解决方案
原因分析
Image.asset默认依赖Flutter的asset缓存机制,调用boundary.toImage()时,如果图片未完成渲染或缓存未同步到离屏渲染上下文,就会导致截图丢失该图片。另外,默认toImage()的像素密度与屏幕不一致,也会引发渲染差异。
方法一:指定截图像素密度
修改takePicture方法中的toImage调用,传入当前设备的像素密度,确保截图渲染参数与屏幕一致:
Future<void> takePicture() async { RenderRepaintBoundary boundary = genKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; // 传入设备像素密度,消除渲染差异 ui.Image image = await boundary.toImage(pixelRatio: MediaQuery.of(context).devicePixelRatio); ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); ssBytes = byteData!.buffer.asUint8List(); setState(() { isShowSs = true; }); }
方法二:预加载Asset图片为内存数据
将Asset图片提前加载为Uint8List,用Image.memory显示,确保截图时图片已完全加载到内存,不受缓存机制影响:
- 添加预加载方法:
Future<Uint8List> loadAssetImage() async { final ByteData data = await rootBundle.load('assets/images/img.png'); return data.buffer.asUint8List(); }
- 在State中声明变量存储图片数据,并在
initState中预加载:
Uint8List? _assetImageBytes; @override void initState() { super.initState(); loadAssetImage().then((bytes) { setState(() { _assetImageBytes = bytes; }); }); }
- 替换
Image.asset为Image.memory:
// 原代码 // Image.asset('assets/images/img.png'), // 替换为 if (_assetImageBytes != null) Image.memory(_assetImageBytes!),
修改后的完整代码(方法二示例)
import 'package:flutter/material.dart'; import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key? key, this.title}) : super(key: key); final String? title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; Uint8List? ssBytes; bool isShowSs = false; Uint8List? _assetImageBytes; final GlobalKey genKey = GlobalKey(); Future<Uint8List> loadAssetImage() async { final ByteData data = await rootBundle.load('assets/images/img.png'); return data.buffer.asUint8List(); } @override void initState() { super.initState(); loadAssetImage().then((bytes) { setState(() { _assetImageBytes = bytes; }); }); } Future<void> takePicture() async { RenderRepaintBoundary boundary = genKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; ui.Image image = await boundary.toImage(pixelRatio: MediaQuery.of(context).devicePixelRatio); ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); ssBytes = byteData!.buffer.asUint8List(); setState(() { isShowSs = true; }); } void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { if(isShowSs) { return Image.memory(ssBytes!); } else { return RepaintBoundary( key: genKey, child: Scaffold( appBar: AppBar( title: Text(widget.title!), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ElevatedButton( onPressed: () async { await takePicture(); }, child: Text( "Take Picture", )), if (_assetImageBytes != null) Image.memory(_assetImageBytes!), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ), ); } } }
内容的提问来源于stack exchange,提问作者Ryan
相关产品推荐
相关产品推荐

