feat(media): 图片占位改为纯色块并去掉扫光
This commit is contained in:
1 parent
613147c20c
commit
7f65fe4d48
20 files changed
+48
-291
No files matched your search
@@ -1,86 +0,0 @@
|
||||
// Author: fruit.com.cn
|
||||
// 职责:给默认图片加载占位叠加可暂停、局部重绘的轻量扫光。
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const _sweepDuration = Duration(milliseconds: 1400);
|
||||
const double _bandWidthRatio = 0.65;
|
||||
const _highlight = Color(0x12FFFFFF);
|
||||
const _clear = Color(0x00FFFFFF);
|
||||
|
||||
/// 只用于等待真实图片的占位;减少动画或页面离屏时不运行扫光。
|
||||
class ImagePlaceholderShimmer extends StatefulWidget {
|
||||
/// 在 [child] 上方绘制不影响布局与点击区域的扫光。
|
||||
const ImagePlaceholderShimmer({super.key, required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<ImagePlaceholderShimmer> createState() =>
|
||||
_ImagePlaceholderShimmerState();
|
||||
}
|
||||
|
||||
class _ImagePlaceholderShimmerState extends State<ImagePlaceholderShimmer>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: _sweepDuration,
|
||||
);
|
||||
bool _active = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_active =
|
||||
TickerMode.valuesOf(context).enabled &&
|
||||
!MediaQuery.disableAnimationsOf(context);
|
||||
if (_active) {
|
||||
_controller.repeat();
|
||||
} else {
|
||||
_controller.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_active) return widget.child;
|
||||
return RepaintBoundary(
|
||||
child: ClipRect(
|
||||
child: CustomPaint(
|
||||
foregroundPainter: _SweepPainter(_controller),
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SweepPainter extends CustomPainter {
|
||||
_SweepPainter(this.progress) : super(repaint: progress);
|
||||
|
||||
final Animation<double> progress;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (size.isEmpty) return;
|
||||
final bandWidth = size.width * _bandWidthRatio;
|
||||
final left = -bandWidth + (size.width + bandWidth) * progress.value;
|
||||
final rect = Rect.fromLTWH(left, 0, bandWidth, size.height);
|
||||
final paint = Paint()
|
||||
..shader = const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [_clear, _highlight, _clear],
|
||||
).createShader(rect);
|
||||
canvas.drawRect(rect, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _SweepPainter oldDelegate) =>
|
||||
oldDelegate.progress != progress;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
// Author: fruit.com.cn
|
||||
// 职责:统一网络图片入口,补服务地址,并在加载中/失败时显示纯色占位块。
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../config/app_config.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
import 'image_placeholder_shimmer.dart';
|
||||
|
||||
const int _minImageDecodePixels = 96;
|
||||
const int _maxImageDecodePixels = 2048;
|
||||
@@ -32,8 +32,6 @@ class RemoteImage extends StatelessWidget {
|
||||
this.width,
|
||||
this.height,
|
||||
this.fit = BoxFit.cover,
|
||||
this.showFallbackIcon = false,
|
||||
this.fallbackIconKey,
|
||||
this.fallbackAspectRatio,
|
||||
this.fallbackColor = AppColors.raised,
|
||||
this.fallbackWidget,
|
||||
@@ -45,12 +43,10 @@ class RemoteImage extends StatelessWidget {
|
||||
final double? width;
|
||||
final double? height;
|
||||
final BoxFit fit;
|
||||
final bool showFallbackIcon;
|
||||
final Key? fallbackIconKey;
|
||||
final double? fallbackAspectRatio;
|
||||
final Color fallbackColor;
|
||||
|
||||
/// 自定义兜底:地址为空、加载中、加载失败时都显示它(优先于 [fallbackColor])。
|
||||
/// 自定义占位:地址为空、加载中、加载失败时都显示它(优先于 [fallbackColor])。
|
||||
final Widget? fallbackWidget;
|
||||
final int? decodeWidth;
|
||||
final int? decodeHeight;
|
||||
@@ -58,7 +54,7 @@ class RemoteImage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final url = AppConfig.mediaUrl(path);
|
||||
if (url.isEmpty) return _fallback(brand: showFallbackIcon);
|
||||
if (url.isEmpty) return _fallback();
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final pixelRatio = MediaQuery.devicePixelRatioOf(context);
|
||||
@@ -100,51 +96,23 @@ class RemoteImage extends StatelessWidget {
|
||||
?.clamp(_minImageDecodePixels, _maxImageDecodePixels)
|
||||
.toInt(),
|
||||
gaplessPlayback: true,
|
||||
errorBuilder: (_, _, _) => _fallback(brand: showFallbackIcon),
|
||||
errorBuilder: (_, _, _) => _fallback(),
|
||||
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
|
||||
if (wasSynchronouslyLoaded || frame != null) return child;
|
||||
return _loadingFallback();
|
||||
return _fallback();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _loadingFallback() {
|
||||
Widget _fallback() {
|
||||
final custom = fallbackWidget;
|
||||
if (custom != null) return custom;
|
||||
return ImagePlaceholderShimmer(child: _fallback(brand: showFallbackIcon));
|
||||
}
|
||||
|
||||
Widget _fallback({required bool brand}) {
|
||||
final custom = fallbackWidget;
|
||||
if (custom != null) return custom;
|
||||
final mark = brand
|
||||
? LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth.isFinite
|
||||
? constraints.maxWidth
|
||||
: 120.0;
|
||||
return Center(
|
||||
child: Image.asset(
|
||||
'assets/icon/splash_logo.png',
|
||||
key: fallbackIconKey,
|
||||
width: width * 0.72,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: null;
|
||||
final fallback = Container(
|
||||
width: width,
|
||||
height: height,
|
||||
color: brand ? AppColors.mediaBlack : fallbackColor,
|
||||
alignment: Alignment.center,
|
||||
child: mark,
|
||||
);
|
||||
final content = custom != null
|
||||
? SizedBox(width: width, height: height, child: custom)
|
||||
: Container(width: width, height: height, color: fallbackColor);
|
||||
final ratio = fallbackAspectRatio;
|
||||
if (ratio == null) return fallback;
|
||||
return AspectRatio(aspectRatio: ratio, child: fallback);
|
||||
if (ratio == null) return content;
|
||||
return AspectRatio(aspectRatio: ratio, child: content);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user