You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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显示,确保截图时图片已完全加载到内存,不受缓存机制影响:

  1. 添加预加载方法:
Future<Uint8List> loadAssetImage() async {
  final ByteData data = await rootBundle.load('assets/images/img.png');
  return data.buffer.asUint8List();
}
  1. 在State中声明变量存储图片数据,并在initState中预加载:
Uint8List? _assetImageBytes;

@override
void initState() {
  super.initState();
  loadAssetImage().then((bytes) {
    setState(() {
      _assetImageBytes = bytes;
    });
  });
}
  1. 替换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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 19:07:36