import 'package:flutter/material.dart'; import 'package:hgdj/assets_tool/images.dart'; /// 双击点赞爱心动效的触发器。onDoubleTapDown 里 [markAt] 记落点,onDoubleTap 里 [burst] 放动画 class LikeBurstController extends ChangeNotifier { Offset? at; /// 记下双击落点(相对 Stack 左上角) void markAt(Offset offset) => at = offset; /// 在落点弹一颗爱心;没记过落点就不弹 void burst() { if (at == null) return; notifyListeners(); } } /// 双击处弹出的爱心:弹起放大 → 停顿 → 淡出。必须直接挂在 Stack 里 class LikeBurstView extends StatefulWidget { const LikeBurstView({ super.key, required this.controller, this.icon = 'like_red.png', this.size = 90, }); final LikeBurstController controller; final String icon; // assets/images/common 下的图名 final double size; @override State createState() => _LikeBurstViewState(); } class _LikeBurstViewState extends State with SingleTickerProviderStateMixin { late final AnimationController _ctr; Offset? _at; @override void initState() { super.initState(); _ctr = AnimationController( vsync: this, duration: const Duration(milliseconds: 600)) ..addStatusListener((status) { if (status == AnimationStatus.completed && mounted) setState(() => _at = null); }); widget.controller.addListener(_onBurst); } @override void didUpdateWidget(covariant LikeBurstView oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.controller != widget.controller) { oldWidget.controller.removeListener(_onBurst); widget.controller.addListener(_onBurst); } } @override void dispose() { widget.controller.removeListener(_onBurst); _ctr.dispose(); super.dispose(); } void _onBurst() { setState(() => _at = widget.controller.at); _ctr.forward(from: 0); // 连点就从头重放 } @override Widget build(BuildContext context) { final at = _at; if (at == null) return const SizedBox.shrink(); return Positioned( left: at.dx - widget.size / 2, top: at.dy - widget.size / 2, child: IgnorePointer( child: FadeTransition( opacity: TweenSequence([ TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.0), weight: 20), TweenSequenceItem(tween: ConstantTween(1.0), weight: 40), TweenSequenceItem(tween: Tween(begin: 1.0, end: 0.0), weight: 40), ]).animate(_ctr), child: ScaleTransition( scale: Tween(begin: 0.5, end: 1.3).animate( CurvedAnimation(parent: _ctr, curve: Curves.easeOutBack)), child: Image.asset(widget.icon.commonImgPath, width: widget.size, height: widget.size), ), ), ), ); } }