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

Flutter嵌套Draggable的Container无法Resize,拖动触发位移问题求助

Flutter 拖动调整容器尺寸与拖动容器冲突的解决方案

问题根源

拖动右下角的容器B时,手势事件会向上冒泡到外层的Draggable组件,导致Draggable优先响应容器拖动移动的逻辑,而非执行容器A的尺寸调整。同时原代码中误用了StatefulBuilder的局部setState,会导致尺寸更新无法同步到父组件的视图。

解决步骤

  • 拦截手势事件:给容器B的GestureDetector添加behavior: HitTestBehavior.opaque,确保它优先接收并消费触摸事件,阻止事件传递给外层的Draggable。
  • 统一状态更新:移除多余的StatefulBuilder,直接使用父组件的setState来更新尺寸,避免局部状态引发的视图不同步问题。
  • 简化手势逻辑:将原有的垂直/水平拖动回调合并为onPanStart和onPanUpdate,统一处理所有方向的拖动操作。

修改后的完整代码

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Resize(),
    );
  }
}

class Resize extends StatefulWidget {
  const Resize({super.key});

  @override
  _ResizeState createState() => _ResizeState();
}

class _ResizeState extends State<Resize> {
  double height = 30;
  double width = 100;
  double x = 100;
  double y = 100;

  Widget container() {
    return Container(
      color: Colors.lightGreen,
      width: width,
      height: height,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
        title: const Text('Resize Demo'),
      ),
      body: Stack(
        children: [
          Positioned(
            top: y,
            left: x,
            child: Draggable(
              onDragEnd: (details) {
                setState(() {
                  x = details.offset.dx;
                  y = details.offset.dy;
                });
              },
              feedback: Container(
                color: Colors.lightGreen.withOpacity(0.8),
                height: height,
                width: width,
              ),
              child: resizeContainer(container()),
            ),
          ),
        ],
      ),
    );
  }

  Widget resizeContainer(Widget formField) {
    double initX = 0;
    double initY = 0;

    return Container(
      height: height + 30,
      width: width + 30,
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          CustomPaint(
            painter: DashedBorder(),
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: SizedBox(height: height, width: width, child: formField),
            ),
          ),
          Positioned(
            right: -2.5,
            bottom: -2.5,
            child: GestureDetector(
              // 关键:阻止手势事件冒泡到外层Draggable
              behavior: HitTestBehavior.opaque,
              onPanStart: (details) {
                initX = details.globalPosition.dx;
                initY = details.globalPosition.dy;
              },
              onPanUpdate: (details) {
                setState(() {
                  final dx = details.globalPosition.dx - initX;
                  final dy = details.globalPosition.dy - initY;
                  height = (height + dy).clamp(20, double.infinity);
                  width = (width + dx).clamp(50, double.infinity);
                  initX = details.globalPosition.dx;
                  initY = details.globalPosition.dy;
                });
              },
              child: Container(
                height: 30,
                width: 30,
                color: Colors.blue,
                child: Center(
                  child: Container(
                    height: 14,
                    width: 14,
                    color: Colors.red,
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class DashedBorder extends CustomPainter {
  DashedBorder({this.color});
  Color? color;

  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = color ?? Colors.blue
      ..strokeCap = StrokeCap.square
      ..strokeWidth = 1;
    _dashedLine(canvas, size, paint);
  }

  @override
  bool shouldRepaint(covariant DashedBorder oldDelegate) {
    return true;
  }

  void _dashedLine(Canvas canvas, Size size, Paint paint) {
    const int dashWidth = 4;
    const int dashSpace = 5;
    double startX = 0;
    const double y = 0;
    double startY = 0;

    while (size.width > size.height ? startX < size.width : startY < size.height) {
      if (startX < size.width) {
        canvas.drawLine(Offset(startX, y), Offset(startX + dashWidth, y), paint);
        canvas.drawLine(Offset(startX, size.height), Offset(startX + dashWidth, size.height), paint);
      }
      if (startY < size.height) {
        canvas.drawLine(Offset(y, startY), Offset(y, startY + dashWidth), paint);
        canvas.drawLine(Offset(size.width, startY), Offset(size.width, startY + dashWidth), paint);
      }
      startX += dashWidth + dashSpace;
      startY += dashWidth + dashSpace;
    }
  }
}

关键修改说明

  • 手势拦截:behavior: HitTestBehavior.opaque让容器B的手势检测器完全接管触摸事件,不会让事件流到外层的Draggable,确保拖动B时只触发尺寸调整。
  • 状态同步:移除StatefulBuilder后,直接调用父组件的setState,保证尺寸变化能同步更新包括Draggable反馈视图在内的所有相关组件。
  • 逻辑简化:合并手势回调后,代码更简洁,同时支持任意方向的拖动调整,体验更流畅。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 15:15:07