How can we achieve the positioned lines while dragging widget in flutter?

389

the lines are drawn with canvas.drawPoints() at the and of paint() method - you can change this to draw your dotted line

now, with solid, black lines it looks like this:

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);
}
Share:
389
jazzbpn
Author by

jazzbpn

Updated on December 30, 2022

Comments

  • jazzbpn
    jazzbpn over 1 year

    I am trying to get the positioned lines(purple one) while dragging. Please see that attachment to know more about the issue.

    enter image description here

    • Naveen Avidi
      Naveen Avidi almost 3 years
      Surround your widget with decoration like DecoratedBox or Container with decoration !
    • jazzbpn
      jazzbpn almost 3 years
      Will you please help with some example?
    • HandyPawan
      HandyPawan over 1 year
      have you got the answer? If found please share it with me I'm also stuck for 3 days
  • jazzbpn
    jazzbpn almost 3 years
    Some of the properties changed in Sizer class Sizer { final String id; final Alignment alignment; final EdgeInsets insets; final Offset mask; Sizer(this.id, this.alignment, this.insets, this.mask); }
  • jazzbpn
    jazzbpn almost 3 years
    Two question: 1. How to stop resizing when the widget become very small? 2. How do we resize both height and widget in same ratio while dragging from corner?
  • pskink
    pskink almost 3 years
    check _panUpdate method - in particular } else { branch with // resize comment - this is where currentSizer.center += vector; changes the currentSizer center position
  • jazzbpn
    jazzbpn almost 3 years
    I see but what will I have to check to make it work stop resizing at certain point?
  • pskink
    pskink almost 3 years
    you have to clamp vector so that the whole width / height does not go below some min values - its just a vector math - i am not familiar with that library but most likely it could be extremely useful here
  • jazzbpn
    jazzbpn almost 3 years
    I tried, but unable to make that out. Always shows the layout issues. I am not so expert in these type of canvas work. Can you please add that two features ? It would be so grateful.
  • jazzbpn
    jazzbpn almost 3 years