初始化
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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<LikeBurstView> createState() => _LikeBurstViewState();
|
||||
}
|
||||
|
||||
class _LikeBurstViewState extends State<LikeBurstView>
|
||||
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<double>([
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user