// Author: fruit.com.cn // 职责:绘制收藏点赞周围的紧凑双向短线,无圆环或阴影,不参与手势。 import 'dart:math' as math; import 'package:flutter/material.dart'; const int _rayCount = 8; const double _startProgress = 0.1; const double _radiusStart = 0.34; const double _radiusTravel = 0.14; const double _rayLength = 0.07; const double _strokeFraction = 0.025; const double _rayAlpha = 0.65; const double _fullCircle = math.pi * 2; const double _rayRotation = math.pi / _rayCount; /// 收藏/点赞的装饰反馈:选中外扩,取消收束。 class RailTapBurst extends StatelessWidget { /// [progress] 来自按钮的点击动画,不创建独立计时器。 const RailTapBurst({ super.key, required this.progress, required this.color, required this.contracting, }); final double progress; final Color color; final bool contracting; @override Widget build(BuildContext context) => IgnorePointer( child: CustomPaint( painter: _RailBurstPainter( progress: progress, color: color, contracting: contracting, ), ), ); } class _RailBurstPainter extends CustomPainter { const _RailBurstPainter({ required this.progress, required this.color, required this.contracting, }); final double progress; final Color color; final bool contracting; @override void paint(Canvas canvas, Size size) { final phase = ((progress - _startProgress) / (1 - _startProgress)).clamp( 0.0, 1.0, ); if (phase == 0 || phase == 1) return; final eased = Curves.easeOutCubic.transform(phase); final travel = contracting ? 1 - eased : eased; final base = size.shortestSide; final radius = base * (_radiusStart + _radiusTravel * travel); final alpha = math.sin(math.pi * phase); final paint = Paint() ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round ..strokeWidth = base * _strokeFraction ..color = color.withValues(alpha: alpha * _rayAlpha); final center = size.center(Offset.zero); final length = base * _rayLength * (1 - phase); for (var index = 0; index < _rayCount; index++) { final angle = _fullCircle * index / _rayCount + _rayRotation; canvas.drawLine( center + Offset.fromDirection(angle, radius), center + Offset.fromDirection(angle, radius + length), paint, ); } } @override bool shouldRepaint(covariant _RailBurstPainter oldDelegate) => oldDelegate.progress != progress || oldDelegate.color != color || oldDelegate.contracting != contracting; }