You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中通过可拖拽中点实现图片裁剪与裁剪区域缩放

Flutter 裁剪框中点拖拽逻辑实现

现有Flutter代码已支持通过四个角拖拽调整裁剪框大小(保持图片宽高比),需完善四条边中点的拖拽逻辑,实现通过边中点拖拽调整裁剪范围,且裁剪后的区域可按比例缩放(效果参考示例图)。

示例效果参考

示例图片1
示例图片2
示例图片3

现有代码片段

//top left
if (_isSelected)
        Positioned(
          top: top - ballDiameter /2 ,
          left: left - ballDiameter /2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width + dx;
              var newHeight = newWidth * aspectRatio ;
              setState(() {
                if (newHeight > 10 && newWidth > 10) {
                  top = top + (height - newHeight) ;
                  left = left + dx;
                  height = newHeight;
                  width = newWidth;
                }
              });
            },
            handlerWidget: HandlerWidget.VERTICAL,
          ),
        ),
      //top mid
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
             .....
            },
            handlerWidget: HandlerWidget.HORIZONTAL,
          ),
        ),
      // top right
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width + dx;
              var newHeight = newWidth * aspectRatio ;
              setState(() {
                if (newHeight > 10 && newWidth > 10) {
                  top = top + (height - newHeight);
                  height = newHeight;
                  width = newWidth;
                }
              });
            },
            handlerWidget: HandlerWidget.VERTICAL,
          ),
        ),
      //left mid
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top + height / 2 - ballDiameter/2,
          left: left  - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
           ....
            },
            handlerWidget: HandlerWidget.HORIZONTAL,
          ),
        ),
      //bottom right
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width + dx;
              var newHeight = newWidth * aspectRatio ;
              setState(() {
                if (newHeight > 10 && newWidth > 10) {
                  height = newHeight;
                  width = newWidth;
                }
              });
            },
            handlerWidget: HandlerWidget.VERTICAL,
          ),
        ),
      //right mid
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top + height / 2 - ballDiameter/2,
          left: left  + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
         ....
          },
            handlerWidget: HandlerWidget.HORIZONTAL,
          ),
        ),
      //bottom left
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width - dx;
              var newHeight = newWidth * aspectRatio ;
              setState(() {
                if (newHeight > 10 && newWidth > 10) {
                  height = newHeight;
                  width = newWidth;
                  top = top + (height - newHeight) ;
                  left = left + dx;
                }
              });
            },
            handlerWidget: HandlerWidget.VERTICAL,
          ),
        ),
      //bottom mid
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top + height - ballDiameter/2,
          left: left + width /2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
            },
            handlerWidget: HandlerWidget.HORIZONTAL,
          ),
        ),

完善后的完整代码(含中点拖拽逻辑)

以下代码补全了四条边中点的拖拽逻辑,确保裁剪框始终保持图片宽高比,同时匹配拖拽操作的直观体验:

//top left
if (_isSelected)
        Positioned(
          top: top - ballDiameter /2 ,
          left: left - ballDiameter /2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width + dx;
              var newHeight = newWidth * aspectRatio ;
              setState(() {
                if (newHeight > 10 && newWidth > 10) {
                  top = top + (height - newHeight) ;
                  left = left + dx;
                  height = newHeight;
                  width = newWidth;
                }
              });
            },
            handlerWidget: HandlerWidget.VERTICAL,
          ),
        ),
      //top mid
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              // 顶部中点拖拽:调整顶部位置,同步按比例修改宽高,保持裁剪框水平居中
              var newHeight = height - dy;
              var newWidth = newHeight * aspectRatio;
              if (newHeight > 10 && newWidth > 10) {
                setState(() {
                  top += dy;
                  height = newHeight;
                  width = newWidth;
                  left += (width - newWidth) / 2;
                });
              }
            },
            handlerWidget: HandlerWidget.HORIZONTAL,
          ),
        ),
      // top right
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width + dx;
              var newHeight = newWidth * aspectRatio ;
              setState(() {
                if (newHeight > 10 && newWidth > 10) {
                  top = top + (height - newHeight);
                  height = newHeight;
                  width = newWidth;
                }
              });
            },
            handlerWidget: HandlerWidget.VERTICAL,
          ),
        ),
      //left mid
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top + height / 2 - ballDiameter/2,
          left: left  - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              // 左侧中点拖拽:调整左侧位置,同步按比例修改宽高,保持裁剪框垂直居中
              var newWidth = width + dx;
              var newHeight = newWidth * aspectRatio;
              if (newHeight > 10 && newWidth > 10) {
                setState(() {
                  left += dx;
                  width = newWidth;
                  height = newHeight;
                  top += (height - newHeight) / 2;
                });
              }
            },
            handlerWidget: HandlerWidget.HORIZONTAL,
          ),
        ),
      //bottom right
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width + dx;
              var newHeight = newWidth * aspectRatio ;
              setState(() {
                if (newHeight > 10 && newWidth > 10) {
                  height = newHeight;
                  width = newWidth;
                }
              });
            },
            handlerWidget: HandlerWidget.VERTICAL,
          ),
        ),
      //right mid
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top + height / 2 - ballDiameter/2,
          left: left  + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              // 右侧中点拖拽:调整右侧宽度,同步按比例修改高度,保持裁剪框垂直居中
              var newWidth = width + dx;
              var newHeight = newWidth * aspectRatio;
              if (newHeight > 10 && newWidth > 10) {
                setState(() {
                  width = newWidth;
                  height = newHeight;
                  top += (height - newHeight) / 2;
                });
              }
            },
            handlerWidget: HandlerWidget.HORIZONTAL,
          ),
        ),
      //bottom left
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width - dx;
              var newHeight = newWidth * aspectRatio ;
              setState(() {
                if (newHeight > 10 && newWidth > 10) {
                  height = newHeight;
                  width = newWidth;
                  top = top + (height - newHeight) ;
                  left = left + dx;
                }
              });
            },
            handlerWidget: HandlerWidget.VERTICAL,
          ),
        ),
      //bottom mid
      if (_isSelected && (width > 10 && height > 10))
        Positioned(
          top: top + height - ballDiameter/2,
          left: left + width /2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              // 底部中点拖拽:调整底部高度,同步按比例修改宽度,保持裁剪框水平居中
              var newHeight = height + dy;
              var newWidth = newHeight * aspectRatio;
              if (newHeight > 10 && newWidth > 10) {
                setState(() {
                  height = newHeight;
                  width = newWidth;
                  left += (width - newWidth) / 2;
                });
              }
            },
            handlerWidget: HandlerWidget.HORIZONTAL,
          ),
        ),

逻辑说明

  • 顶部中点:拖拽时修改顶部位置,同步按图片比例缩小/放大裁剪框宽度,同时调整左侧位置保持水平居中
  • 左侧中点:拖拽时修改左侧位置,同步按图片比例缩小/放大裁剪框高度,同时调整顶部位置保持垂直居中
  • 右侧中点:拖拽时修改裁剪框宽度,同步按图片比例调整高度,同时调整顶部位置保持垂直居中
  • 底部中点:拖拽时修改裁剪框高度,同步按图片比例调整宽度,同时调整左侧位置保持水平居中
  • 所有操作均保留最小尺寸限制(宽高大于10),避免裁剪框过小

内容的提问来源于stack exchange,提问作者Harsh Rajpara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 03:25:53