QinGang_interested/lib/tools/smart_image.dart

180 lines
4.5 KiB
Dart
Raw Normal View History

2026-02-28 14:38:07 +08:00
// lib/widgets/smart_image.dart
import 'package:flutter/material.dart';
/// SmartAssetImage
/// - 自动计算目标像素宽度devicePixelRatio * context.width
/// - 使用 ResizeImage 限制解码尺寸以降低首次解码开销
/// - 预缓存precacheImage并提供占位色/小占位Widget
/// - gaplessPlayback 避免重建时闪烁
class SmartAssetImage extends StatefulWidget {
final String assetPath;
final double? height;
final double? width;
final BoxFit fit;
final Color placeholderColor;
final Duration precacheTimeout;
const SmartAssetImage({
Key? key,
required this.assetPath,
this.height,
this.width,
this.fit = BoxFit.cover,
this.placeholderColor = const Color(0xFFE8F4FD),
this.precacheTimeout = const Duration(milliseconds: 800),
}) : super(key: key);
@override
State<SmartAssetImage> createState() => _SmartAssetImageState();
}
class _SmartAssetImageState extends State<SmartAssetImage> {
ImageProvider? _provider;
bool _done = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_prepareImage();
}
Future<void> _prepareImage() async {
// 避免重复处理
if (_done) return;
try {
final mq = MediaQuery.of(context);
final logicalWidth = widget.width ?? mq.size.width;
final devicePixelRatio = mq.devicePixelRatio;
final int targetWidth = (logicalWidth * devicePixelRatio).toInt();
// 使用 ResizeImage 限制解码到合适的像素宽度
final provider = ResizeImage(
AssetImage(widget.assetPath),
width: targetWidth,
);
// attempt to precache but don't block too long
final precacheFuture = precacheImage(provider, context);
// optional: bound the wait time to prevent waiting forever
await precacheFuture
.timeout(widget.precacheTimeout, onTimeout: () => null);
if (mounted) {
setState(() {
_provider = provider;
_done = true;
});
}
} catch (e) {
// 如果预缓存失败,仍然回退到常规 AssetImage
if (mounted) {
setState(() {
_provider = AssetImage(widget.assetPath);
_done = true;
});
}
}
}
@override
Widget build(BuildContext context) {
final placeholder = Container(
width: widget.width ?? double.infinity,
height: widget.height,
color: widget.placeholderColor,
);
if (_provider == null) {
return placeholder;
}
return Image(
image: _provider!,
width: widget.width ?? double.infinity,
height: widget.height,
fit: widget.fit,
gaplessPlayback: true,
);
}
}
/// SmartNetworkImage (无第三方包版本)
/// - 先显示 placeholder / local placeholder
/// - 再通过 precacheImage 加载 NetworkImage
class SmartNetworkImage extends StatefulWidget {
final String url;
final double? height;
final double? width;
final BoxFit fit;
final Widget? placeholder;
final Duration precacheTimeout;
const SmartNetworkImage({
Key? key,
required this.url,
this.height,
this.width,
this.fit = BoxFit.cover,
this.placeholder,
this.precacheTimeout = const Duration(milliseconds: 800),
}) : super(key: key);
@override
State<SmartNetworkImage> createState() => _SmartNetworkImageState();
}
class _SmartNetworkImageState extends State<SmartNetworkImage> {
ImageProvider? _provider;
bool _done = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_prepare();
}
Future<void> _prepare() async {
if (_done) return;
try {
final provider = NetworkImage(widget.url);
await precacheImage(provider, context).timeout(widget.precacheTimeout, onTimeout: () => null);
if (mounted) {
setState(() {
_provider = provider;
_done = true;
});
}
} catch (e) {
if (mounted) {
setState(() {
_provider = NetworkImage(widget.url); // fallback
_done = true;
});
}
}
}
@override
Widget build(BuildContext context) {
final placeholder = widget.placeholder ??
Container(
width: widget.width ?? double.infinity,
height: widget.height,
color: const Color(0xFFE8F4FD),
);
if (_provider == null) {
return placeholder;
}
return Image(
image: _provider!,
width: widget.width ?? double.infinity,
height: widget.height,
fit: widget.fit,
gaplessPlayback: true,
);
}
}