如何将Widget转换为Image UInt8List作为FadeInImage.memoryNetwork占位符
实现方案
你可以通过两种方式实现需求,可根据实际场景选择:
方案1:将自定义Widget转换为Uint8List传入placeholder
该方案适合需要对占位图做缓存、复用的场景,通过Flutter自带的渲染能力将Widget转为图片字节数据:
- 首先用
RepaintBoundary包裹你的自定义占位Widget,绑定GlobalKey用于获取渲染对象:
import 'package:flutter/material.dart'; final GlobalKey _repaintKey = GlobalKey(); // 自定义首字母占位Widget Widget _buildPlaceholderWidget(String nameInitial) { return RepaintBoundary( key: _repaintKey, child: ClipOval( child: Container( width: 160, height: 160, color: Colors.blue, padding: const EdgeInsets.symmetric(vertical: 40), child: Text( nameInitial, style: const TextStyle(color: Colors.white, fontSize: 70), textAlign: TextAlign.center, ), ), ), ); }
- 将占位Widget提前加入Widget树完成渲染,可通过Offstage隐藏不影响页面展示:
@override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // 提前渲染占位Widget,不会显示在页面 Offstage(child: _buildPlaceholderWidget("L M")), // 你的其他页面业务代码 // ... ], ), ); }
- 编写转换方法将渲染完成的Widget转为Uint8List:
import 'dart:ui' as ui; import 'dart:async'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; Future<Uint8List> widgetToUint8List() async { final completer = Completer<Uint8List>(); // 等待帧渲染完成后再取渲染对象 SchedulerBinding.instance.addPostFrameCallback((_) async { final RenderRepaintBoundary boundary = _repaintKey.currentContext?.findRenderObject() as RenderRepaintBoundary; // pixelRatio设为2-3可避免转换后的图片模糊 final ui.Image image = await boundary.toImage(pixelRatio: 3.0); final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); completer.complete(byteData!.buffer.asUint8List()); }); return completer.future; }
- 拿到转换后的Uint8List后,传入
FadeInImage.memoryNetwork的placeholder参数即可。
方案2:直接使用placeholderBuilder参数(更推荐)
如果只是需要自定义占位UI,不需要单独存Uint8List,完全不用做格式转换,直接用FadeInImage自带的placeholderBuilder参数返回自定义组件即可,实现更简单无额外渲染开销:
ClipOval( child: FadeInImage( placeholder: const MemoryImage(kTransparentImage), image: NetworkImage(imageUrl), fit: BoxFit.fill, width: 160, height: 160, // 直接返回自定义的首字母占位组件 placeholderBuilder: (context, child) { return ClipOval( child: Container( width: 160, height: 160, color: Colors.blue, padding: const EdgeInsets.symmetric(vertical: 40), child: const Text( "L M", style: TextStyle(color: Colors.white, fontSize: 70), textAlign: TextAlign.center, ), ), ); }, ), )
内容的提问来源于stack exchange,提问作者Ryan Heitner
相关产品推荐
相关产品推荐

