Files
ReelShort/lib/pages/mine/widgets/mine_stat_icon.dart
T

219 lines
6.6 KiB
Dart

// 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<Offset> 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;
}
}