Flutter网络图片加载:设置连接超时、进度及错误占位图方案咨询
解决方案:带超时、加载进度和错误占位的网络图片组件
方案一:自定义ImageProvider(无需第三方库)
不用修改Flutter源码,通过自定义NetworkImage子类实现超时控制,同时结合状态管理实现加载进度和错误占位。
1. 创建带超时的自定义HttpClient
import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; final HttpClient _customHttpClient = HttpClient() ..connectionTimeout = const Duration(seconds: 5) // 连接超时 ..receiveTimeout = const Duration(seconds: 5); // 接收超时
2. 实现带超时的NetworkImage子类
class TimeoutNetworkImage extends NetworkImage { final Duration timeout; TimeoutNetworkImage( super.url, { super.scale, super.headers, this.timeout = const Duration(seconds: 5), }); @override ImageStreamCompleter loadImage(NetworkImage key, ImageDecoderCallback decode) { return MultiFrameImageStreamCompleter( codec: _loadAsync(key, decode), scale: key.scale, informationCollector: () => [ DiagnosticsProperty<ImageProvider>('Image provider', this), DiagnosticsProperty<NetworkImage>('Image key', key), ], ); } Future<Codec> _loadAsync(NetworkImage key, ImageDecoderCallback decode) async { assert(key == this); final Uri resolved = Uri.parse(key.url); final HttpClientRequest request = await _customHttpClient.getUrl(resolved); headers?.forEach((String name, String value) => request.headers.add(name, value)); // 触发超时逻辑 final HttpClientResponse response = await request.close(); if (response.statusCode != HttpStatus.ok) { throw NetworkImageLoadException(statusCode: response.statusCode, uri: resolved); } final Uint8List bytes = await consolidateHttpClientResponseBytes(response); if (bytes.isEmpty) throw Exception('Empty network image'); return decode(bytes); } }
3. 封装成可复用组件(带加载/错误状态)
class NetworkImageWithTimeout extends StatefulWidget { final String url; final Widget placeholder; final Widget errorWidget; final Duration timeout; const NetworkImageWithTimeout({ super.key, required this.url, required this.placeholder, required this.errorWidget, this.timeout = const Duration(seconds: 5), }); @override State<NetworkImageWithTimeout> createState() => _NetworkImageWithTimeoutState(); } class _NetworkImageWithTimeoutState extends State<NetworkImageWithTimeout> { ImageProvider? _image; bool _isLoading = true; bool _hasError = false; @override void initState() { super.initState(); _loadImage(); } Future<void> _loadImage() async { try { final image = TimeoutNetworkImage(widget.url, timeout: widget.timeout); // 预加载触发超时检查 await image.obtainKey(const ImageConfiguration()).then((key) { return image.load(key, (bytes) => PaintingBinding.instance.instantiateImageCodec(bytes)); }); if (mounted) { setState(() { _image = image; _isLoading = false; }); } } catch (_) { if (mounted) { setState(() { _hasError = true; _isLoading = false; }); } } } @override Widget build(BuildContext context) { if (_isLoading) return widget.placeholder; if (_hasError) return widget.errorWidget; return Image(image: _image!); } }
使用示例
NetworkImageWithTimeout( url: 'https://your-image-url.com/image.jpg', placeholder: const Center(child: CircularProgressIndicator()), errorWidget: const Icon(Icons.error_outline, color: Colors.red, size: 48), )
方案二:使用第三方库cached_network_image(更简单)
最新版本的cached_network_image支持传入自定义HttpClient,直接实现超时控制,同时内置加载/错误占位功能。
1. 配置自定义HttpClient
HttpClient _createTimeoutHttpClient() { return HttpClient() ..connectionTimeout = const Duration(seconds: 5) ..receiveTimeout = const Duration(seconds: 5); }
2. 使用组件
CachedNetworkImage( imageUrl: 'https://your-image-url.com/image.jpg', httpClient: _createTimeoutHttpClient(), // 传入带超时的客户端 placeholder: (context, url) => const CircularProgressIndicator(), errorWidget: (context, url, error) => const Icon(Icons.error), // 可选:配置缓存规则 cacheManager: CacheManager( Config( 'custom_image_cache', stalePeriod: const Duration(days: 7), ), ), )
关键说明
- 原生
NetworkImage使用全局共享的_sharedHttpClient,无法直接设置超时,自定义子类或第三方库的自定义客户端可以绕过这个限制。 - 两种方案都能实现5秒超时、加载进度显示和错误占位的需求,方案二更简洁,方案一无需依赖第三方库。
内容的提问来源于stack exchange,提问作者Šubyk
相关产品推荐
相关产品推荐

