如何在Flutter中拖动小部件时实现定位的线条?

3

我正在尝试在拖动时获取定位的行(紫色线)。请参阅附件以了解更多信息。

enter image description here


将您的小部件用DecoratedBox或Container包装起来,并添加装饰! - Naveen Avidi
你能给一些例子帮忙吗? - jazzbpn
你有答案吗?如果找到了,请与我分享,我也卡了3天。 - HandyPawan
1个回答

3

使用canvas.drawPoints()paint()方法的末尾绘制线条 - 你可以更改这个方法以绘制虚线

现在,使用实心黑色线条看起来像这样:

enter image description here

class FooResizer extends StatefulWidget {
  @override
  _FooResizerState createState() => _FooResizerState();
}

class _FooResizerState extends State<FooResizer> with TickerProviderStateMixin, ChangeNotifier {
  Sizer currentSizer;
  double angle = 0.0;
  AnimationController ctrl;

  final Map<String, Sizer> sizers = {
    'l': Sizer('l', Alignment.centerLeft, {'t': 0.5, 'b': 0.5, 'M': 0.5}),
    't': Sizer('t', Alignment.topCenter, {'l': 0.5, 'r': 0.5, 'M': 0.5}),
    'r': Sizer('r', Alignment.centerRight, {'t': 0.5, 'b': 0.5, 'R': 1.0, 'M': 0.5}),
    'b': Sizer('b', Alignment.bottomCenter, {'l': 0.5, 'r': 0.5, 'R': 1.0, 'M': 0.5}),
    'R': Sizer('R', Alignment.bottomRight, {}),
    'M': Sizer('M', Alignment.center, {}),
  };

  @override
  void initState() {
    super.initState();
    ctrl = AnimationController(vsync: this, duration: Duration(milliseconds: 300), value: 1.0);
  }

  @override
  Widget build(BuildContext context) {
    return ClipRect(
      child: ColoredBox(
        color: Colors.black12,
        // CustomMultiChildLayoutPainter:
        // https://gist.github.com/pskink/0f82724b41d9ebe89604782fbf62fe03#file-multi_layout_painter-dart-L447
        child: CustomMultiChildLayoutPainter(
          delegate: _FooResizerDelegate(sizers, this),
          children: [
            LayoutId(
              id: 'body',
              child: AnimatedBuilder(
                animation: this,
                builder: (ctx, child) {
                  return Transform.rotate(
                    angle: angle,
                    child: child,
                  );
                },
                child: Material(color: Colors.grey[300], elevation: 4, child: FlutterLogo()),
              ),
            ),
            ...sizers.values.map(_sizerBuilder),
          ],
        ),
      ),
    );
  }

  Widget _sizerBuilder(Sizer sizer) {
    final colors = {
      'M': Colors.orange,
      'R': Colors.teal,
    };
    return LayoutId(
      id: sizer.id,
      child: GestureDetector(
        onPanStart: (details) => _panStart(sizer),
        onPanUpdate: _panUpdate,
        onPanEnd: _panEnd,
        child: AnimatedBuilder(
          animation: ctrl,
          builder: (context, child) {
            final color = colors[sizer.id] ?? Colors.green;
            return Opacity(
              opacity: currentSizer == sizer? 1.0 : Curves.ease.transform(ctrl.value),
              child: Container(
                decoration: ShapeDecoration(
                  shape: CircleBorder(side: BorderSide(width: lerpDouble(0.0, 2.0, ctrl.value), color: Colors.black38)),
                  color: currentSizer == sizer? Color.lerp(Colors.deepPurple, color, ctrl.value) : color,
                  shadows: [BoxShadow.lerp(BoxShadow(spreadRadius: 2, blurRadius: 4, offset: Offset(2, 2)), null, ctrl.value)],
                ),
              ),
            );
          }
        ),
      ),
    );
  }

  _panStart(Sizer sizer) {
    currentSizer = sizer;
    ctrl.reverse();
  }

  _panUpdate(DragUpdateDetails details) {
    assert(currentSizer != null);
    if (currentSizer.id == 'M') {
      // move
      sizers.values.forEach((sizer) => sizer.center += details.delta);
    } else
    if (currentSizer.id == 'R') {
      // rotate
      final localCenter = sizers['M'].center;
      final globalCenter = (context.findRenderObject() as RenderBox).localToGlobal(localCenter);

      final angle0 = (details.globalPosition - details.delta - globalCenter).direction;
      final angle1 = (details.globalPosition - globalCenter).direction;
      final deltaAngle = angle1 - angle0;
      sizers.values
        .where((sizer) => sizer.id != 'M')
        .forEach((sizer) {
          final vector = sizer.center - localCenter;
          sizer.center = localCenter + Offset.fromDirection(vector.direction + deltaAngle, vector.distance);
        });
      angle += deltaAngle;
    } else {
      // resize
      final adjustedAngle = angle + currentSizer.angleAdjustment;
      final rotatedDistance = details.delta.distance * math.cos(details.delta.direction - adjustedAngle);
      final vector = Offset.fromDirection(adjustedAngle, rotatedDistance);
      currentSizer.center += vector;
      currentSizer.dependents.forEach((id, factor) => sizers[id].center += vector * factor);
    }
    notifyListeners();
  }

  _panEnd(DragEndDetails details) {
    assert(currentSizer != null);
    // currentSizer = null;
    ctrl.forward();
  }
}


class _FooResizerDelegate extends MultiChildLayoutPainterDelegate {
  static const SIZE = 48.0;
  final Map<String, Sizer> sizers;

  _FooResizerDelegate(this.sizers, Listenable relayout) : super(relayout: relayout);

  @override
  void performLayout(Size size) {
    sizers['M'].center ??= init(size);

    for (var sizer in sizers.values) {
      layoutChild(sizer.id, BoxConstraints.tight(Size(SIZE, SIZE)));
      positionChild(sizer.id, sizer.center - Offset(SIZE / 2, SIZE / 2));
    }
    final w = (sizers['l'].center - sizers['r'].center).distance;
    final h = (sizers['t'].center - sizers['b'].center).distance;
    layoutChild('body', BoxConstraints.tight(Size(w, h)));
    positionChild('body', sizers['M'].center - Offset(w / 2, h / 2));
  }

  Offset init(Size size) {
    final rect = (Offset.zero & size).deflate(24);
    print('init rect: $rect');
    for (var sizer in sizers.values) {
      sizer
        ..center = sizer.alignment.withinRect(rect)
        ..angleAdjustment = sizer.alignment.x == 0? math.pi / 2 : 0;
    }
    return sizers['M'].center;
  }

  @override
  bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => true;

  @override
  void foregroundPaint(Canvas canvas, Size size) {
  }

  @override
  void paint(Canvas canvas, Size size) {
    final w = (sizers['r'].center - sizers['l'].center).distance;
    final h = (sizers['b'].center - sizers['t'].center).distance;
    final rect = Rect.fromCenter(center: sizers['M'].center, width: w, height: h);
    final angle = (sizers['r'].center - sizers['l'].center).direction;
    final matrix = Matrix4.identity()
      ..translate(rect.center.dx, rect.center.dy)
      ..rotateZ(angle)
      ..translate(-rect.center.dx, -rect.center.dy);
    final transformedRect = MatrixUtils.transformRect(matrix, rect);
    final points = [
      Offset(transformedRect.left, 0), Offset(transformedRect.left, size.height),
      Offset(0, transformedRect.top), Offset(size.width, transformedRect.top),
      Offset(transformedRect.right, 0), Offset(transformedRect.right, size.height),
      Offset(0, transformedRect.bottom), Offset(size.width, transformedRect.bottom),
    ];
    canvas.drawPoints(PointMode.lines, points, Paint());
  }
}

class Sizer {
  final String id;
  final Alignment alignment;
  final Map<String, double> dependents;
  Offset center;
  double angleAdjustment;
  Sizer(this.id, this.alignment, this.dependents);
}

在 Sizer 类中,一些属性已经发生了变化: final String id; final Alignment alignment; final EdgeInsets insets; final Offset mask; Sizer(this.id, this.alignment, this.insets, this.mask); - jazzbpn
两个问题:1. 当小部件变得非常小时,如何停止调整大小?2. 如何在从角落拖动时同时按比例调整高度和宽度? - jazzbpn
让我们在聊天中继续这个讨论 - jazzbpn
嗨 @pskink,我尝试了上面的方法,遇到了一些问题,所以你能否请分享一下 MultiChildLayoutPainterDelegate 类给我? - undefined
@VigneshK https://gist.github.com/pskink/0f82724b41d9ebe89604782fbf62fe03 - undefined
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接