|
|
@@ -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 变换
|