// Author: fruit.com.cn // 绘制个人数据区统一的红色叠层质感图标。 import 'package:flutter/material.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; const double _designSize = 48; const double _depth = 2; const double _edgeWidth = 0.8; const double _ringWidth = 1.1; const double _groundBlur = 3; const double _sparkleWaist = 0.28; const double _screenTilt = -0.12; const double _bookmarkTilt = 0.1; const Offset _artCenter = Offset(24, 24); const Rect _groundBounds = Rect.fromLTWH(8, 41, 32, 4); const LinearGradient _faceGradient = LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ AppColors.mineStatHighlight, AppColors.brandRed, AppColors.mineStatShade, ], ); const LinearGradient _backGradient = LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppColors.brandRed, AppColors.mineStatDepth], ); /// 个人数据的三种图标语义。 enum MineStatIconType { coins, history, favorites } /// 无位图依赖的品牌红图标,统一亮面、厚度与高光语言。 class MineStatIcon extends StatelessWidget { /// 根据数据类型绘制图标。 const MineStatIcon({super.key, required this.type}); final MineStatIconType type; @override Widget build(BuildContext context) { return SizedBox.square( dimension: AppDimens.mineStatsIcon, child: DecoratedBox( decoration: const BoxDecoration( gradient: RadialGradient( colors: [AppColors.mineStatGlow, AppColors.transparent], ), ), child: CustomPaint(painter: _MineStatPainter(type)), ), ); } } class _MineStatPainter extends CustomPainter { const _MineStatPainter(this.type); final MineStatIconType type; @override void paint(Canvas canvas, Size size) { canvas.save(); canvas.scale(size.width / _designSize, size.height / _designSize); canvas.drawOval( _groundBounds, Paint() ..color = AppColors.mineStatGlow ..maskFilter = const MaskFilter.blur(BlurStyle.normal, _groundBlur), ); switch (type) { case MineStatIconType.coins: _coins(canvas); case MineStatIconType.history: _tilt(canvas, _screenTilt); _screen(canvas); case MineStatIconType.favorites: _tilt(canvas, _bookmarkTilt); _bookmark(canvas); } canvas.restore(); } void _coins(Canvas canvas) { const backBounds = Rect.fromLTWH(23, 6, 19, 19); const frontBounds = Rect.fromLTWH(5, 15, 29, 29); const innerCenter = Offset(19.5, 29.5); const innerRadius = 11.3; const stampRadius = 6.5; const glintCenter = Offset(39, 33); const double glintRadius = 3; _surface(canvas, Path()..addOval(backBounds), backBounds, _backGradient); _surface(canvas, Path()..addOval(frontBounds), frontBounds, _faceGradient); canvas.drawCircle( innerCenter, innerRadius, Paint() ..color = AppColors.mineStatEdge ..style = PaintingStyle.stroke ..strokeWidth = _ringWidth, ); _sparkle(canvas, innerCenter, stampRadius); _sparkle(canvas, glintCenter, glintRadius); } void _screen(Canvas canvas) { const backBounds = Rect.fromLTWH(12, 6, 29, 26); const frontBounds = Rect.fromLTWH(6, 12, 32, 27); const innerBounds = Rect.fromLTWH(10, 16, 24, 19); const outerRadius = Radius.circular(6); const innerRadius = Radius.circular(3); const playPoints = [Offset(19, 20), Offset(19, 31), Offset(28, 25.5)]; const reflectionStart = Offset(13, 14); const reflectionEnd = Offset(26, 14); final back = Path() ..addRRect(RRect.fromRectAndRadius(backBounds, outerRadius)); final front = Path() ..addRRect(RRect.fromRectAndRadius(frontBounds, outerRadius)); _surface(canvas, back, backBounds, _backGradient); _surface(canvas, front, frontBounds, _faceGradient); canvas.drawRRect( RRect.fromRectAndRadius(innerBounds, innerRadius), Paint()..shader = _backGradient.createShader(innerBounds), ); canvas.drawPath( _polygon(playPoints), Paint()..color = AppColors.mineStatGlint, ); _reflection(canvas, reflectionStart, reflectionEnd); } void _bookmark(Canvas canvas) { const bounds = Rect.fromLTWH(12, 7, 23, 35); const radius = Radius.circular(4); const cutout = [Offset(11, 44), Offset(23.5, 34), Offset(36, 44)]; const rearOffset = Offset(5, -3); const stampCenter = Offset(23.5, 21); const double stampRadius = 6; const reflectionStart = Offset(17, 10); const reflectionEnd = Offset(28, 10); final cover = Path()..addRRect(RRect.fromRectAndRadius(bounds, radius)); final front = Path.combine( PathOperation.difference, cover, _polygon(cutout), ); _surface( canvas, front.shift(rearOffset), bounds.shift(rearOffset), _backGradient, ); _surface(canvas, front, bounds, _faceGradient); _sparkle(canvas, stampCenter, stampRadius); _reflection(canvas, reflectionStart, reflectionEnd); } void _surface(Canvas canvas, Path path, Rect bounds, Gradient gradient) { canvas.save(); canvas.translate(0, _depth); canvas.drawPath(path, Paint()..color = AppColors.mineStatDepth); canvas.restore(); canvas.drawPath(path, Paint()..shader = gradient.createShader(bounds)); canvas.drawPath( path, Paint() ..color = AppColors.mineStatEdge ..style = PaintingStyle.stroke ..strokeJoin = StrokeJoin.round ..strokeWidth = _edgeWidth, ); } void _sparkle(Canvas canvas, Offset center, double radius) { final waist = radius * _sparkleWaist; final points = [ center + Offset(0, -radius), center + Offset(waist, -waist), center + Offset(radius, 0), center + Offset(waist, waist), center + Offset(0, radius), center + Offset(-waist, waist), center + Offset(-radius, 0), center + Offset(-waist, -waist), ]; canvas.drawPath(_polygon(points), Paint()..color = AppColors.mineStatGlint); } Path _polygon(List points) { return Path()..addPolygon(points, true); } void _reflection(Canvas canvas, Offset start, Offset end) { canvas.drawLine( start, end, Paint() ..color = AppColors.mineStatEdge ..strokeCap = StrokeCap.round ..strokeWidth = _ringWidth, ); } void _tilt(Canvas canvas, double angle) { canvas.translate(_artCenter.dx, _artCenter.dy); canvas.rotate(angle); canvas.translate(-_artCenter.dx, -_artCenter.dy); } @override bool shouldRepaint(covariant _MineStatPainter oldDelegate) { return oldDelegate.type != type; } }