flutterdartflutter-positioned

Flutter: How to keep offset when resize widget after rotated?


My main goal is, to create a flexible widget with features.

  1. manage widget position.
  2. rotate widget.
  3. Resize the widget from all sides.

1 and 2, I've managed to make. but the 3rd point, when the widget finishes rotating and resizing the widget, the position will change and be erratic.

i really need you guys help. thanks

enter image description here

this is my code.

import 'package:flutter/material.dart';

void main() {
  return runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      home: const TestingDesign2(),
    );
  }
}

class Item{
  Size size;
  Offset offset;
  double rotation;
  var parentKey;
  Item({required this.parentKey, required this.size, required this.offset, required this.rotation });
}

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

  @override
  State<TestingDesign2> createState() => _TestingDesign2State();
}

class _TestingDesign2State extends State<TestingDesign2> {

    
  Item item = new Item(
  parentKey: new GlobalKey(),
  offset: Offset(10,10),
    size: Size(150,150),
    rotation: 0
  );
  

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("Testing design 2")),
        body: Container(
          color: Colors.blueAccent.withOpacity(0.5),
         
          child: Stack(
            alignment: Alignment.center,
           
            children: [ItemTesting(key: item.parentKey, item:item)],
          ),
        ));
  }
}

class ItemTesting extends StatefulWidget {
  Item item;
  ItemTesting({super.key, required this.item});

  @override
  State<ItemTesting> createState() => _ItemTestingState();
}

class _ItemTestingState extends State<ItemTesting> {
  
  double offsetAngle = 0;
  bool isRotate = false;

  @override
  Widget build(BuildContext context) {
    return _Item();
  }

  Widget _Item() {
    return Positioned(
      left: widget.item.offset.dx,
      top: widget.item.offset.dy,
      child: Transform.rotate(
        angle: widget.item.rotation,
        child: SizedBox(
          height: widget.item.size.height,
          width: widget.item.size.width,
          child: Stack(
            children: [
              GestureDetector(
                onPanStart: onPanStart,
                onPanUpdate: onPanUpdate,
                behavior: HitTestBehavior.translucent,
                onPanEnd: (details) {
                  isRotate = false;
                },
                onPanCancel: () {
                  isRotate = false;
                },
                child: Container(
                  color: Colors.red,
                  height: widget.item.size.height,
                  width: widget.item.size.width,
                ),
              ),
              IgnorePointer(
                child: Align(
                  alignment: Alignment.topRight,
                  child: ClipOval(
                      child: Container(
                          height: 25,
                          width: 25,
                          color: Colors.blue,
                          child: Icon(Icons.rotate_right_outlined))),
                ),
              ),
              Align(
                  alignment: Alignment.centerRight,
                  child: GestureDetector(
                      onPanUpdate: resizeRight,
                      child: Container(
                          height: 25,
                          width: 25,
                          color: Colors.yellow,
                          child: Icon(Icons.arrow_forward)))),
              Align(
                  alignment: Alignment.bottomCenter,
                  child: GestureDetector(
                      onPanUpdate: resizeBottom,
                      child: Container(
                          height: 25,
                          width: 25,
                          color: Colors.yellow,
                          child: Icon(Icons.arrow_downward))))
            ],
          ),
        ),
      ),
    );
  }

  var touchPosition = Offset.zero;
  onPanStart(DragStartDetails details) {
    Offset centerOfGestureDetector = Offset(widget.item.size.width / 2, widget.item.size.height / 2);
    final touchPositionFromCenter =
        details.localPosition - centerOfGestureDetector;
    offsetAngle = touchPositionFromCenter.direction - widget.item.rotation;

    final RenderBox referenceBox =
        widget.item.parentKey.currentContext.findRenderObject();
    var x = referenceBox.globalToLocal(details.globalPosition);

    touchPosition = Offset(x.dx, x.dy + 55);
    // top right
    if (details.localPosition.dx > (widget.item.size.width - 25) &&
        details.localPosition.dy <= 25) {
      isRotate = true;
    }
  }

  onPanUpdate(DragUpdateDetails details) {
    if (isRotate) {
      Offset centerOfGestureDetector = Offset(widget.item.size.width / 2, widget.item.size.height / 2);
      final touchPositionFromCenter =
          details.localPosition - centerOfGestureDetector;

      widget.item.rotation = touchPositionFromCenter.direction - offsetAngle;
    } else {
      var positionG = widget.item.offset + details.globalPosition;
      var positiong2 = positionG - touchPosition;
      widget.item.offset = (positiong2 - widget.item.offset);
    }
    setState(() {});
  }

//------------------------------------------------ resize widget
  void resizeRight(DragUpdateDetails details) {
    setState(() {
      widget.item.size = Size(widget.item.size.width + details.delta.dx, widget.item.size.height);
    });
  }

  void resizeBottom(DragUpdateDetails details) {
    setState(() {
      widget.item.size = Size(widget.item.size.width, widget.item.size.height + details.delta.dy);
    });
  }
}

https://dartpad.dev/?id=b1cb4617d4dd1a25e815a01135f95222


Solution

  • Rotational Offsets

    You can compensate for the rotation by offsetting the position proportional to the cos and sin of the angle.

    First, to use cos and sin functions, import the 'dart:math' library at the beginning:

    import 'dart:math';
    

    Then in your resizeRight function:

      void resizeRight(DragUpdateDetails details) {
        setState(() {
          widget.item.size = Size(widget.item.size.width + details.delta.dx, widget.item.size.height);
          var angle = widget.item.rotation;
          var rotationalOffset = Offset(cos(angle) - 1, sin(angle)) * details.delta.dx / 2;
          widget.item.offset += rotationalOffset;
        });
      }
    

    Likewise, in your resizeBottom function:

      void resizeBottom(DragUpdateDetails details) {
        setState(() {
          widget.item.size = Size(widget.item.size.width, widget.item.size.height + details.delta.dy);
          var angle = widget.item.rotation;
          var rotationalOffset = Offset(-sin(angle), cos(angle) - 1) * details.delta.dy / 2;
          widget.item.offset += rotationalOffset;
        });
      }