Selaa lähdekoodia

fix 首页洗牌动画bug

guoziyun 4 kuukautta sitten
vanhempi
sitoutus
0ca9ca07ae
3 muutettua tiedostoa jossa 59 lisäystä ja 60 poistoa
  1. 49 50
      lib/homepage/home_board_play.dart
  2. 4 4
      lib/models/data.dart
  3. 6 6
      lib/play/board_play.dart

+ 49 - 50
lib/homepage/home_board_play.dart

@@ -64,7 +64,6 @@ class HomeBoardPlayState extends State<HomeBoardPlay> with TickerProviderStateMi
   // 每个卡片移动时间(ms)
   final int _dealingPieceDuration = 200;
 
-
   // 发牌动画总时长(需要考虑到最后一个卡片的动画)
   // 共有 board.rows * board.cols 个卡片
   int get _totalDealingDuration => (board.rows * board.cols - 1) * _dealingPieceInterval + _dealingPieceDuration;
@@ -73,8 +72,6 @@ class HomeBoardPlayState extends State<HomeBoardPlay> with TickerProviderStateMi
 
   // ✅ 优化:Picture 缓存静止背景,避免 flip 动画期间重绘 24 张静止碎片
   ui.Picture? _staticBackgroundPicture;
-  // ✅ 优化:跟踪上次录制的关卡,实现增量更新(只更新新完成的碎片)
-  int? _lastRecordedLevel;
 
   // ✅ 优化:发牌动画每个卡片的预录制 Picture
   List<ui.Picture>? _dealingPictures;
@@ -263,7 +260,6 @@ class HomeBoardPlayState extends State<HomeBoardPlay> with TickerProviderStateMi
 
     // ✅ 清理 Picture 缓存
     _staticBackgroundPicture = null;
-    _lastRecordedLevel = null;
 
     board.dispose(); // 调用优化后的 dispose
     confettiLayer.dispose();
@@ -346,22 +342,35 @@ class HomeBoardPlayState extends State<HomeBoardPlay> with TickerProviderStateMi
   }
 
   void startFlipAnimation() {
-    // ✅ 优化:每次 flip 前都重新录制背景(因为已完成关卡数在变化)
-    // 此时 TextPainter 缓存已预热,录制成本大幅降低
-    if (board.image != null && board.cardImage != null) {
-      _recordStaticBackgroundPicture();
+    // Make sure the widget is painted with the latest level/collectionIndex
+    // before we start the flip.  Without this there was a small window where
+    // the parent rebuild (triggered by completedWorks) raced with the
+    // animation start causing the painter to still hold old values and
+    // visually flash the previous collection.
+    if (mounted) {
+      setState(() {});
     }
 
-    MemoryMonitor.logMemoryUsage('Collection flip animation');
-    _flipController.forward(from: 0.0);
-    audio.playSfx(SfxType.flip);
-    if (data.currentLevel != 0 && (data.currentCollectionIndex + 1) * 25 == data.currentLevel && currentCollectionItem != null) {
-      data.collectionDone(currentCollectionItem!);
-      audio.playSfx(SfxType.star);
-      confettiLayer.play();
-      // 合集完成时清理内存
-      // MemoryMonitor().manualCleanup();
-    }
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      if (!mounted) return;
+
+      // ✅ 优化:每次 flip 前都重新录制背景(因为已完成关卡数在变化)
+      // 此时 TextPainter 缓存已预热,录制成本大幅降低
+      if (board.image != null && board.cardImage != null) {
+        _recordStaticBackgroundPicture();
+      }
+
+      MemoryMonitor.logMemoryUsage('Collection flip animation');
+      _flipController.forward(from: 0.0);
+      audio.playSfx(SfxType.flip);
+      if (data.currentLevel != 0 && (data.currentCollectionIndex + 1) * 25 == data.currentLevel && currentCollectionItem != null) {
+        data.collectionDone(currentCollectionItem!);
+        audio.playSfx(SfxType.star);
+        confettiLayer.play();
+        // 合集完成时清理内存
+        // MemoryMonitor().manualCleanup();
+      }
+    });
   }
 
   // ✅ 新增:录制所有静止碎片到 Picture,供 flip 动画复用
@@ -370,32 +379,19 @@ class HomeBoardPlayState extends State<HomeBoardPlay> with TickerProviderStateMi
 
     final recorder = ui.PictureRecorder();
     final canvas = Canvas(recorder, Rect.fromLTWH(0, 0, widget.canvasWidth, widget.canvasHeight));
+    final currentCollectionIndex = data.currentCollectionIndex;
 
-    // ✅ 优化:增量更新(只更新新完成的碎片)
-    if (_staticBackgroundPicture != null && _lastRecordedLevel != null && data.currentLevel == _lastRecordedLevel! + 1) {
-      // 增量模式:复用上次的 Picture,只绘制新完成的碎片
-      canvas.drawPicture(_staticBackgroundPicture!);
-
-      // 只更新新完成的碎片(新翻转的碎片在上一层绘制,覆盖背景中的对应位置)
-      final int newFlippedIndex = data.currentLevel - 1 - (data.currentCollectionIndex * board.count);
-      if (newFlippedIndex >= 0 && newFlippedIndex < board.count) {
-        final int row = newFlippedIndex ~/ board.cols;
-        final int col = newFlippedIndex % board.cols;
-        _drawStaticPieceToRecorder(canvas, row, col, true, newFlippedIndex);
-      }
-    } else {
-      // 首次录制或状态不连续(集合切换等),全量重新录制
-      for (var i = 0; i < board.rows; i++) {
-        for (var j = 0; j < board.cols; j++) {
-          final int curIndex = i * board.rows + j;
-          final bool flipped = data.currentLevel > data.currentCollectionIndex * board.count + curIndex;
-          _drawStaticPieceToRecorder(canvas, i, j, flipped, curIndex);
-        }
+    // ✅ 简化:总是全量录制所有碎片。TextPainter 已缓存,成本可控。
+    // 避免跨 collection 时的状态混淆和 picture 复用导致的 bug。
+    for (var i = 0; i < board.rows; i++) {
+      for (var j = 0; j < board.cols; j++) {
+        final int curIndex = i * board.cols + j;
+        final bool flipped = data.currentLevel > currentCollectionIndex * board.count + curIndex;
+        _drawStaticPieceToRecorder(canvas, i, j, flipped, curIndex, currentCollectionIndex);
       }
     }
 
     _staticBackgroundPicture = recorder.endRecording();
-    _lastRecordedLevel = data.currentLevel;
   }
 
   // ✅ 新增:录制发牌动画所需的每张卡片 Picture
@@ -447,7 +443,7 @@ class HomeBoardPlayState extends State<HomeBoardPlay> with TickerProviderStateMi
   }
 
   // ✅ 辅助方法:绘制单个碎片到 PictureRecorder
-  void _drawStaticPieceToRecorder(Canvas canvas, int row, int col, bool flipped, int curIndex) {
+  void _drawStaticPieceToRecorder(Canvas canvas, int row, int col, bool flipped, int curIndex, int currentCollectionIndex) {
     final img = board.image;
     final cardImg = board.cardImage;
     if (img == null || cardImg == null) return;
@@ -472,7 +468,8 @@ class HomeBoardPlayState extends State<HomeBoardPlay> with TickerProviderStateMi
     } else {
       canvas.drawImageRect(cardImg, cardSourceRect, rect, Paint()..isAntiAlias = true);
 
-      // ✅ 修复:绘制背卡上的数字(直接创建临时 TextPainter)
+      // ✅ 修复:使用正确的 displayIndex(包含 collection 偏移)
+      final int displayIndex = currentCollectionIndex * board.count + curIndex;
       final textStyle = TextStyle(
         color: Colors.white,
         fontSize: h * 0.25,
@@ -480,7 +477,7 @@ class HomeBoardPlayState extends State<HomeBoardPlay> with TickerProviderStateMi
         shadows: const [Shadow(offset: Offset(1, 1), blurRadius: 2, color: Colors.black38)],
       );
       final textPainter = TextPainter(
-        text: TextSpan(text: (curIndex + 1).toString(), style: textStyle),
+        text: TextSpan(text: (displayIndex + 1).toString(), style: textStyle),
         textDirection: TextDirection.ltr,
         textAlign: TextAlign.center,
       );
@@ -515,7 +512,6 @@ class HomeBoardPlayState extends State<HomeBoardPlay> with TickerProviderStateMi
     }
     // ✅ 清理 Picture 缓存,因为集合改变了
     _staticBackgroundPicture = null;
-    _lastRecordedLevel = null;
     _dealingPictures = null;
     board.switchToNextCollection(currentCollectionItem!);
   }
@@ -692,7 +688,6 @@ class CanvasPainter extends CustomPainter {
     }
   }
 
-
   void _paintUnlocking(Canvas canvas, Size size) {
     final img = board.image;
     if (img == null) return;
@@ -742,10 +737,14 @@ class CanvasPainter extends CustomPainter {
 
       // 只在翻转碎片上绘制 3D 变换版本(覆盖 Picture 中该位置的内容)
       // 但首先需要清空预录制背景中该位置的内容,使翻转过程中显示为空白
-      final int curFlippingIndex = level - 1;
-      if (curFlippingIndex >= 0) {
-        final int row = curFlippingIndex ~/ board.cols;
-        final int col = curFlippingIndex % board.cols;
+      // 计算当前正在翻转的碎片的集合内局部索引(避免使用全局 level-1 导致行越界)
+      final int globalIndex = level - 1;
+      final int localIndex = globalIndex - collectionIndex * board.count;
+
+      // 只有当局部索引在当前 collection 的范围内时才绘制翻转碎片
+      if (localIndex >= 0 && localIndex < board.count) {
+        final int row = localIndex ~/ board.cols;
+        final int col = localIndex % board.cols;
 
         // 计算碎片在画布上的区域并清空该区域
         final double w = size.width / board.cols;
@@ -758,7 +757,7 @@ class CanvasPainter extends CustomPainter {
         final rrect = RRect.fromRectAndRadius(clearRect, const Radius.circular(4.0));
         canvas.drawRRect(rrect, Paint()..color = Colors.white);
 
-        bool flipped = level > collectionIndex * board.count + curFlippingIndex;
+        bool flipped = level > collectionIndex * board.count + localIndex;
         _drawPiece(canvas, size, row, col, flipped);
       }
     } else {
@@ -790,7 +789,7 @@ class CanvasPainter extends CustomPainter {
     final imageSourceRect = Rect.fromLTWH(pieceWidth * col, pieceHeight * row, pieceWidth, pieceHeight);
     final cardSourceRect = Rect.fromLTWH(0, 0, cardImg.width.toDouble(), cardImg.height.toDouble());
 
-    final curIndex = collectionIndex * board.count + row * board.rows + col;
+    final curIndex = collectionIndex * board.count + row * board.cols + col; // ✅ 修复:使用 cols
     double flipProgress = (flipAnimation.isAnimating && curIndex == level - 1) ? flipAnimation.value : 0.0;
 
     // ⚠️ 优化:只有当前翻转的碎片才计算 3D 变换

+ 4 - 4
lib/models/data.dart

@@ -18,10 +18,10 @@ class Data {
 
   Future<void> loadDataFromPersistence() async {
     // for test 为了测试合集完成动画
-    var works = _persistence.completedWorks;
-    works = works.sublist(0, works.length - 1);
-    _persistence.completedWorks = works;
-    _persistence.completedCollections = [];
+    // var works = _persistence.completedWorks;
+    // works = works.sublist(0, works.length - 1);
+    // _persistence.completedWorks = works;
+    // _persistence.completedCollections = [];
 
     // 1. 先初始化内置索引(独立、确定、不依赖缓存判断)
     await _initBuiltinRegistry();

+ 6 - 6
lib/play/board_play.dart

@@ -267,11 +267,11 @@ class _BoardPlayState extends AdsState<BoardPlay> with TickerProviderStateMixin
 
     setState(() => _isLoading = true);
 
-    // // 如果是低端设备,跳过发牌动画以节省资源(借鉴 homepage 优化)
-    // if (device.isLowEndDevice) {
-    //   showDealing = false;
-    //   _log.info('Low-end device detected, skipping dealing animation');
-    // }
+    // 如果是低端设备,跳过发牌动画以节省资源(借鉴 homepage 优化)
+    if (device.isLowEndDevice) {
+      showDealing = false;
+      _log.info('Low-end device detected, skipping dealing animation');
+    }
 
     try {
       final dpr = device.effectivePixelRatio;
@@ -800,7 +800,7 @@ class _BoardPlayState extends AdsState<BoardPlay> with TickerProviderStateMixin
 
     // 🔥 先清理 banner 广告资源,再清理其他资源
     cleanBanner();
-    
+
     // 🔥 先保存进度,再清理资源
     saveProgress();