Flutter:如何在平面内对角旋转Rect以实现单词搜索应用的斜向标记绘制
单词搜索应用对角线标记绘制优化方案
问题核心
你当前实现的单词搜索应用已完成横、竖向单词选中、校验、标记绘制全流程逻辑,斜向标记失败的核心原因是:
- 硬编码RotationTransition的旋转角度,没有根据斜向类型动态调整
- 直接使用Rect.expandToInclude生成的外接矩形作为标记范围,斜向下的外接矩形会覆盖非选中单元格,不符合显示要求
- 未将选中方向同步到标记组件,无法针对性调整标记样式
优化步骤
- 在
WordMarker类中新增direction字段,存储当前标记对应的选中方向,可选值:horizontal/vertical/mainDiagonal(左上到右下对角线)/counterDiagonal(右上到左下对角线) - 取消RotationTransition的固定角度配置,根据标记的
direction字段动态计算旋转角度 - 调整标记宽高计算逻辑,斜向标记不再使用外接矩形的宽高,根据选中单元格数量、单个单元格尺寸计算适配的宽高
- 在拖拽开始后首次触发
onPanUpdate时确定选中方向,同步到当前标记
核心修改代码示例
标记组件重构
Widget buildWordMarker(WordMarker marker) { // 动态计算旋转角度 double turn = 0; if(marker.direction == 'mainDiagonal') turn = 45 / 360; if(marker.direction == 'counterDiagonal') turn = -45 / 360; int selectedCellCount = selection.length; // 动态计算标记宽高 double markerWidth; double markerHeight; if(marker.direction.contains('Diagonal')){ // 斜向宽度为单元格数 * 单元格宽度 * 根号2,高度自定义标记粗细即可 markerWidth = selectedCellCount * cellWidth * 1.414; markerHeight = 8; }else{ markerWidth = marker.rect!.width; markerHeight = marker.rect!.height; } return Positioned( left: marker.rect!.left, top: marker.rect!.top, child: RotationTransition( alignment: Alignment.topLeft, // 旋转锚点改到左上角,和起始单元格位置对齐 turns: AlwaysStoppedAnimation(turn), child: Container( width: markerWidth, height: markerHeight, decoration: BoxDecoration( color: marker.colorSelection!.withOpacity(0.4), borderRadius: BorderRadius.circular(4), ), ), ), ); }
onPanUpdate新增方向初始化逻辑
void onPanUpdate(DragUpdateDetails details) { final currentIndex = computeLetterIndex(details.localPosition); if(currentIndex < 0 || currentIndex > 99) return; final key = uniqueLetters[currentIndex]['key']; final RenderBox renderBox = key.currentContext.findRenderObject(); markerRect = renderBox.localToGlobal(Offset.zero, ancestor: context.findRenderObject()) & renderBox.size; // 新增:首次偏移时确定选中方向 if(direction.isEmpty) { if(checkSameRow(startIndex, currentIndex)) direction = "horizontal"; else if(checkSameCol(startIndex, currentIndex)) direction = "vertical"; else if(checkSameMainDiagonal(startIndex, currentIndex)) direction = "mainDiagonal"; else if(checkSameCounterDiagonal(startIndex, currentIndex)) direction = "counterDiagonal"; // 方向同步到当前标记 markers.last = markers.last.copyWith(direction: direction); } List<int> _selection = []; // 原有方向校验、选中范围生成逻辑保持不变 if (checkSameRow(startIndex, currentIndex) && direction == "horizontal") { markers.last = adjustedMarker(markers.last, markerRect!); _selection = genSelection(startIndex, currentIndex, 1); } else if (checkSameCol(startIndex, currentIndex) && direction == "vertical") { markers.last = adjustedMarker(markers.last, markerRect!); _selection = genSelection(startIndex, currentIndex, nCols); } else if (checkSameMainDiagonal(startIndex, currentIndex) && direction == "mainDiagonal") { markers.last = adjustedMarker(markers.last, markerRect!); _selection = genSelection(startIndex, currentIndex, nCols + 1); } else if (checkSameCounterDiagonal(startIndex, currentIndex) && direction == "counterDiagonal") { markers.last = adjustedMarker(markers.last, markerRect!); _selection = genSelection(startIndex, currentIndex, nCols - 1); } setState(() { selection = _selection; }); }
效果说明
优化后可完美适配四个方向的选中标记绘制,标记仅覆盖选中的对应方向单元格,不会出现多余范围覆盖问题,原有单词校验逻辑无需改动。
内容的提问来源于stack exchange,提问作者Lucas santos
相关产品推荐
相关产品推荐

