Skip to content

Instantly share code, notes, and snippets.

@jazzbpn
Created June 17, 2021 08:03
Show Gist options
  • Save jazzbpn/9ac1cd5ccea543dc3c16c0c20afdddec to your computer and use it in GitHub Desktop.
Save jazzbpn/9ac1cd5ccea543dc3c16c0c20afdddec to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
class ResizebleWidget extends StatefulWidget {
final Widget child;
final String widgetId;
final Size size;
final bool editMode, doubleTapped;
final GestureTapCallback onSingleTapWidget, onDoubleTapWidget;
ResizebleWidget(
{this.child,
this.widgetId,
this.size,
this.editMode,
this.doubleTapped,
this.onSingleTapWidget,
this.onDoubleTapWidget});
@override
_ResizebleWidgetState createState() => _ResizebleWidgetState();
}
const ballDiameter = 25.0;
class _ResizebleWidgetState extends State<ResizebleWidget> {
double top = 0;
double left = 0;
double height = 0;
double width = 0;
bool editable;
double rotateAngle = 0;
@override
void initState() {
height = widget.size.height;
width = widget.size.width;
super.initState();
}
void onDrag(double dx, double dy) {
var newHeight = height + dy;
var newWidth = width + dx;
setState(() {
height = newHeight > 0 ? newHeight : 0;
width = newWidth > 0 ? newWidth : 0;
});
}
showBoxDecoration() {
return BoxDecoration(
border: Border.all(
color: editable == true ? Colors.lightBlue : Colors.transparent,
width: editable == true ? 1 : 0,
),
);
}
@override
Widget build(BuildContext context) {
editable = widget.editMode;
return Transform.rotate(
angle: rotateAngle,
child: Stack(
children: <Widget>[
Positioned(
top: top,
left: left,
child: GestureDetector(
onTap: widget.onSingleTapWidget,
child: Container(
decoration: showBoxDecoration(),
height: height,
width: width,
child: widget.child,
),
),
),
// center center
editable && widget.doubleTapped == false
? Positioned(
top: top, // look 20 form transparent draggable widget
left: left,
child: InkWell(
onDoubleTap: () {
widget.onDoubleTapWidget();
},
child: ManipulatingBall(
height: height,
width: width,
color: Colors.red.withOpacity(0.6),
onDrag: (dx, dy, details) {
setState(() {
top = top + dy;
left = left + dx;
});
},
),
),
)
: Container(),
// top left
editable
? Positioned(
top: top - ballDiameter / 2,
left: left - ballDiameter / 2,
child: ManipulatingBall(
onDrag: (dx, dy, details) {
var mid = (dx + dy) / 2;
var newHeight = height - 2 * mid;
var newWidth = width - 2 * mid;
setState(() {
height = newHeight > 0 ? newHeight : 0;
width = newWidth > 0 ? newWidth : 0;
top = top + mid;
left = left + mid;
});
},
),
)
: Container(),
// top middle
editable
? Positioned(
top: top - ballDiameter / 2,
left: left + width / 2 - ballDiameter / 2,
child: ManipulatingBall(
onDrag: (dx, dy, details) {
var newHeight = height - dy;
setState(() {
height = newHeight > 0 ? newHeight : 0;
top = top + dy;
});
},
),
)
: Container(),
// top right
editable
? Positioned(
top: top - ballDiameter / 2,
left: left + width - ballDiameter / 2,
child: ManipulatingBall(
onDrag: (dx, dy, details) {
var mid = (dx + (dy * -1)) / 2;
var newHeight = height + 2 * mid;
var newWidth = width + 2 * mid;
setState(() {
height = newHeight > 0 ? newHeight : 0;
width = newWidth > 0 ? newWidth : 0;
top = top - mid;
left = left - mid;
});
},
),
)
: Container(),
// center right
editable
? Positioned(
top: top + height / 2 - ballDiameter / 2,
left: left + width - ballDiameter / 2,
child: ManipulatingBall(
onDrag: (dx, dy, details) {
var newWidth = width + dx;
setState(() {
width = newWidth > 0 ? newWidth : 0;
});
},
),
)
: Container(),
// bottom right
editable
? Positioned(
top: top + height - ballDiameter / 2,
left: left + width - ballDiameter / 2,
child: ManipulatingBall(
onDrag: (dx, dy, details) {
var mid = (dx + dy) / 2;
var newHeight = height + 2 * mid;
var newWidth = width + 2 * mid;
setState(() {
height = newHeight > 0 ? newHeight : 0;
width = newWidth > 0 ? newWidth : 0;
top = top - mid;
left = left - mid;
});
},
),
)
: Container(),
// bottom center
editable
? Positioned(
top: top + height - ballDiameter / 2,
left: left + width / 2 - ballDiameter / 2,
child: ManipulatingBall(
onDrag: (dx, dy, details) {
var newHeight = height + dy;
setState(() {
height = newHeight > 0 ? newHeight : 0;
});
},
),
)
: Container(),
// bottom left
editable
? Positioned(
top: top + height - ballDiameter / 2,
left: left - ballDiameter / 2,
child: ManipulatingBall(
onDrag: (dx, dy, details) {
var mid = ((dx * -1) + dy) / 2;
var newHeight = height + 2 * mid;
var newWidth = width + 2 * mid;
setState(() {
height = newHeight > 0 ? newHeight : 0;
width = newWidth > 0 ? newWidth : 0;
top = top - mid;
left = left - mid;
});
},
),
)
: Container(),
//left center
editable
? Positioned(
top: top + height / 2 - ballDiameter / 2,
left: left - ballDiameter / 2,
child: ManipulatingBall(
onDrag: (dx, dy, details) {
var newWidth = width - dx;
setState(() {
width = newWidth > 0 ? newWidth : 0;
left = left + dx;
});
},
),
)
: Container(),
// bottom center center to Rotate-resizable-widget
editable
? Positioned(
top: top + height + ballDiameter,
left: left + width / 2 - ballDiameter / 2,
child: Stack(
children: [
Icon(
Icons.refresh,
color: Colors.black,
size: 40,
),
ManipulatingBall(
onDrag: (dx, dy, details) {
final touchPositionFromCenter = details.localPosition;
setState(
() {
rotateAngle = touchPositionFromCenter.direction;
print('Rotate--> ' + rotateAngle.toString());
},
);
},
),
],
))
: Container(),
],
),
);
}
}
class ManipulatingBall extends StatefulWidget {
ManipulatingBall({Key key, this.onDrag, this.height, this.color, this.width});
final Function onDrag;
final double height, width;
final Color color;
@override
_ManipulatingBallState createState() => _ManipulatingBallState();
}
class _ManipulatingBallState extends State<ManipulatingBall> {
double initX;
double initY;
_handleDrag(details) {
setState(() {
initX = details.globalPosition.dx;
initY = details.globalPosition.dy;
});
}
_handleUpdate(details) {
var dx = details.globalPosition.dx - initX;
var dy = details.globalPosition.dy - initY;
initX = details.globalPosition.dx;
initY = details.globalPosition.dy;
widget.onDrag(dx, dy, details);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: _handleDrag,
onPanUpdate: _handleUpdate,
child: Container(
width: widget.width == null ? ballDiameter : widget.width,
height: widget.height == null ? ballDiameter : widget.height,
decoration: BoxDecoration(
color: widget.color == null
? Colors.blue.withOpacity(0.5)
: widget.color,
shape: widget.width == null ? BoxShape.circle : BoxShape.rectangle,
),
),
);
}
}
@jazzbpn
Copy link
Author

jazzbpn commented Jun 17, 2021

        import 'dart:math';

  import 'package:flutter/material.dart';

  final SIZE = 64.0;
  final sizerSize = Size.square(SIZE);

  final sizers = [
    Sizer(Alignment.centerLeft, EdgeInsets.only(left: 1), Offset(1, 0)),
    Sizer(Alignment.topCenter, EdgeInsets.only(top: 1), Offset(0, 1)),
    Sizer(Alignment.centerRight, EdgeInsets.only(right: 1), Offset(-1, 0)),
    Sizer(Alignment.bottomCenter, EdgeInsets.only(bottom: 1), Offset(0, -1)),
  ];

  class Sizer {
    final Alignment alignment;
    final EdgeInsets insets;
    final Offset mask;
    Sizer(this.alignment, this.insets, this.mask);
  }

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

  class _FooResizerState extends State<FooResizer> with TickerProviderStateMixin {
    ValueNotifier<Rect> rect;
    ValueNotifier<int> serial = ValueNotifier<int>(0);
    Sizer currentSizer;
    Rect savedRect;
    Offset savedOffset;
    AnimationController controller;
    GlobalKey stackKey = GlobalKey();
    double angle = 0.0;

    @override
    Widget build(BuildContext context) {
      controller ??=
          AnimationController(vsync: this, duration: Duration(milliseconds: 300));
      return LayoutBuilder(
        builder: (context, constraints) {
          _init(constraints.biggest);
          return AnimatedBuilder(
            animation: Listenable.merge([rect, serial, controller]),
            builder: (context, child) {
              return Transform.rotate(
                // angle: pi / 8,
                angle: angle,
                child: Stack(
                  key: stackKey,
                  overflow: Overflow.visible,
                  children: [
                    Positioned.fromRect(
                      rect: rect.value,
                      child: Material(
                        borderRadius: BorderRadius.circular(12),
                        color: Colors.grey[300],
                        elevation: 4,
                        child: FlutterLogo(),
                      ),
                    ),
                    ...sizers.map(_sizerBuilder),
                  ],
                ),
              );
            },
          );
        },
      );
    }

    Widget _sizerBuilder(Sizer sizer) {
      var isCurrent = currentSizer == sizer;
      var finalRect = sizer.alignment
          .inscribe(sizerSize, rect.value)
          .shift(sizer.mask * SIZE * -0.33);
      var centerRect = Alignment.center.inscribe(sizerSize, rect.value);
      var interpolated =
          Rect.lerp(finalRect, centerRect, isCurrent ? 0 : controller.value);
      return Positioned.fromRect(
        rect: interpolated,
        child: GestureDetector(
          onPanStart: (details) => _panStart(sizer, details),
          onPanUpdate: _panUpdate,
          onPanEnd: _panEnd,
          child: Opacity(
            opacity: isCurrent ? 1 : 1 - controller.value,
            child: AnimatedContainer(
              duration: isCurrent ? Duration(milliseconds: 500) : Duration.zero,
              decoration: ShapeDecoration(
                shape: isCurrent ? RoundedRectangleBorder() : CircleBorder(),
                color: isCurrent ? Colors.orange[800] : Colors.green,
              ),
            ),
          ),
        ),
      );
    }

    _panStart(Sizer sizer, DragStartDetails details) {
      // timeDilation = 10;
      currentSizer = sizer;
      savedRect = rect.value;
      savedOffset = _invertedOffset(details.globalPosition);
      serial.value++;
      controller.forward();
    }

    _panUpdate(DragUpdateDetails details) {
      assert(currentSizer != null);
      var delta = savedOffset - _invertedOffset(details.globalPosition);
      var insets = EdgeInsets.fromLTRB(
        currentSizer.insets.left * delta.dx * currentSizer.mask.dx,
        currentSizer.insets.top * delta.dy * currentSizer.mask.dy,
        currentSizer.insets.right * delta.dx * currentSizer.mask.dx,
        currentSizer.insets.bottom * delta.dy * currentSizer.mask.dy,
      );
      // setState(() {
      // });
      angle = details.localPosition.direction;

      rect.value = insets.inflateRect(savedRect);
    }

    _panEnd(DragEndDetails details) {
      assert(currentSizer != null);
      currentSizer = null;
      serial.value++;
      controller.reverse();
    }

    Offset _invertedOffset(Offset offset) {
      RenderBox rb = stackKey.currentContext.findRenderObject();
      return rb.globalToLocal(offset);
    }

    void _init(Size size) {
      var r = Offset.zero & size;
      rect = ValueNotifier(r.deflate(100));
    }
  }

@jazzbpn
Copy link
Author

jazzbpn commented Jun 17, 2021

I want the end result like this https://streamable.com/faq9ia.
After resizing widget

  1. rotation should work from center and
  2. the resize pointer should work fine.

Please note after using FooResizer() I keep getting same result like https://streamable.com/oogp5f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment