overlayer.dart 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import 'dart:ui' as ui;
  2. import 'package:flutter/material.dart';
  3. import 'package:logging/logging.dart';
  4. import 'package:puzzleweave/play/board.dart';
  5. final _log = Logger('overlayer.dart');
  6. class HintItem {
  7. final ui.Image finger;
  8. final Rect srcRect;
  9. final Rect destRect;
  10. final CurveTween tween;
  11. HintItem(this.finger, this.srcRect, this.destRect) : tween = CurveTween(curve: Curves.easeInOut);
  12. Rect getCurrent(double t) {
  13. return Rect.lerp(srcRect, destRect, tween.transform(t))!;
  14. }
  15. // 动画的前 10% 和后 10% 渐入渐出,中间 80% 完全不透明
  16. double getOpacity(double t) {
  17. if (t < 0.1) {
  18. return t * 10;
  19. } else if (t >= 0.1 && t <= 0.9) {
  20. return 1;
  21. } else {
  22. return (1 - t) * 10;
  23. }
  24. }
  25. }
  26. class OverLayer {
  27. final Board board;
  28. final TickerProvider tickerProvider;
  29. OverlayEntry? _overlayEntry;
  30. bool _isDisposed = false;
  31. final ValueNotifier<int> _notifier = ValueNotifier(0);
  32. late AnimationController hintAnimation;
  33. Listenable get notifiers => Listenable.merge([_notifier, hintAnimation]);
  34. OverLayer(this.board, this.tickerProvider) {
  35. hintAnimation = AnimationController(value: 0, vsync: tickerProvider, duration: const Duration(milliseconds: 1200));
  36. }
  37. void invalidate() {
  38. _notifier.value++;
  39. }
  40. void setup(BuildContext context) {
  41. if (_overlayEntry != null) return;
  42. // 创建并插入 OverlayEntry
  43. _overlayEntry = OverlayEntry(
  44. builder: (context) {
  45. return Positioned.fill(
  46. // 关键:IgnorePointer 确保手势引导动画不会阻碍底层的 BoardPlay 交互
  47. child: IgnorePointer(
  48. child: RepaintBoundary(child: CustomPaint(painter: OverLayerPainter(this))),
  49. ),
  50. );
  51. },
  52. );
  53. Overlay.of(context).insert(_overlayEntry!);
  54. }
  55. HintItem? _hintItem;
  56. HintItem? get hintItem => _hintItem;
  57. void doHint(HintItem hintItem) {
  58. ///上一次hint没有完成
  59. if (_isDisposed || _hintItem != null) {
  60. return;
  61. }
  62. _hintItem = hintItem;
  63. hintAnimation.reset();
  64. hintAnimation.repeat(); // 重复播放动画
  65. }
  66. void stopHint() {
  67. if (!_isDisposed && _hintItem != null) {
  68. hintAnimation.stop();
  69. _hintItem = null;
  70. invalidate();
  71. }
  72. }
  73. bool get isHinting => _hintItem != null && hintAnimation.isAnimating;
  74. destroy() {
  75. _isDisposed = true; // 标记已销毁
  76. _overlayEntry?.remove();
  77. _overlayEntry = null;
  78. hintAnimation.dispose();
  79. }
  80. }
  81. class OverLayerPainter extends CustomPainter {
  82. final OverLayer overLayer;
  83. OverLayerPainter(this.overLayer) : super(repaint: overLayer.notifiers);
  84. @override
  85. void paint(Canvas canvas, Size size) {
  86. // canvas.clipRect(Offset.zero & size);
  87. if (overLayer.hintItem != null) {
  88. _paintHintItem(canvas, overLayer.hintItem!);
  89. }
  90. }
  91. void _paintHintItem(Canvas canvas, HintItem hintItem) {
  92. Rect srcRect = Offset.zero & Size(hintItem.finger.width.toDouble(), hintItem.finger.height.toDouble());
  93. Rect currentRect = hintItem.getCurrent(overLayer.hintAnimation.value);
  94. // 完善点: 计算并应用透明度
  95. double opacity = hintItem.getOpacity(overLayer.hintAnimation.value);
  96. // 创建 Paint,通过 color.withOpacity 控制透明度
  97. final paint = Paint()
  98. ..color = Colors.white.withOpacity(opacity)
  99. ..isAntiAlias = true;
  100. // 绘制图片
  101. canvas.drawImageRect(hintItem.finger, srcRect, currentRect, paint);
  102. }
  103. @override
  104. bool shouldRepaint(covariant CustomPainter oldDelegate) {
  105. return false;
  106. }
  107. }