22

enter image description here

I am currently working on a ScreenView with features like draggable and resizable views with corners and sides like in the image above. The problem I have now is that I want to resize the view by touch gestures in the corners. Therefore, I thought of a Point which I add to a view on selection, which can be dragged to resize the selected view. Answer updated!!

Resizable-Widget ReactNative Demo: React Native PLUGIN example

Modified Workable Example:

  import 'package:flutter/material.dart';

  class ResizeWidget extends StatefulWidget {
    @override
    _ResizeWidgetState createState() => _ResizeWidgetState();
  }

  class _ResizeWidgetState extends State<ResizeWidget> {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        home: Scaffold(
          backgroundColor: Colors.black,
          body: Container(
            // padding: EdgeInsets.only(top: 50),
            child: ResizebleWidget(
              child: Container(
                padding: EdgeInsets.all(10),
                child: Text(
                  'Waao!! you can really dance.',
                  style: TextStyle(
                      color: Colors.white,
                      fontStyle: FontStyle.italic,
                      fontSize: 18),
                ),
              ),
            ),
          ),
        ),
      );
    }
  }

  class ResizebleWidget extends StatefulWidget {
    ResizebleWidget({this.child});

    final Widget child;
    @override
    _ResizebleWidgetState createState() => _ResizebleWidgetState();
  }

  const ballDiameter = 10.0;

  class _ResizebleWidgetState extends State<ResizebleWidget> {
    double height = 100;
    double width = 200;
    bool isCorner = false;

    double top = 0;
    double left = 0;

    @override
    Widget build(BuildContext context) {
      return Stack(
        children: <Widget>[
          Positioned(
            top: top,
            left: left,
            child: Container(
              height: height,
              width: width,

              decoration: BoxDecoration(
                color: Colors.blueGrey,
                border: Border.all(
                  width: 2,
                  color: Colors.white70,
                ),
                borderRadius: BorderRadius.circular(0.0),
              ),

              // need tp check if draggable is done from corner or sides
              child: isCorner
                  ? FittedBox(
                      child: widget.child,
                    )
                  : Center(
                      child: widget.child,
                    ),
            ),
          ),
          // top left
          Positioned(
            top: top - ballDiameter / 2,
            left: left - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var mid = (dx + dy) / 2;
                var newHeight = height - 2 * mid;
                var newWidth = width - 2 * mid;

                setState(() {
                  isCorner = true;
                  height = newHeight > 0 ? newHeight : 0;
                  width = newWidth > 0 ? newWidth : 0;
                  top = top + mid;
                  left = left + mid;
                });
              },
              handlerWidget: HandlerWidget.VERTICAL,
            ),
          ),
          // top middle
          Positioned(
            top: top - ballDiameter / 2,
            left: left + width / 2 - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var newHeight = height - dy;

                setState(() {
                  isCorner = false;

                  height = newHeight > 0 ? newHeight : 0;
                  top = top + dy;
                });
              },
              handlerWidget: HandlerWidget.HORIZONTAL,
            ),
          ),
          // top right
          Positioned(
            top: top - ballDiameter / 2,
            left: left + width - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var mid = (dx + (dy * -1)) / 2;

                var newHeight = height + 2 * mid;
                var newWidth = width + 2 * mid;

                setState(() {
                  isCorner = true;
                  height = newHeight > 0 ? newHeight : 0;
                  width = newWidth > 0 ? newWidth : 0;
                  top = top - mid;
                  left = left - mid;
                });
              },
              handlerWidget: HandlerWidget.VERTICAL,
            ),
          ),
          // center right
          Positioned(
            top: top + height / 2 - ballDiameter / 2,
            left: left + width - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var newWidth = width + dx;

                setState(() {
                  isCorner = false;

                  width = newWidth > 0 ? newWidth : 0;
                });
              },
              handlerWidget: HandlerWidget.HORIZONTAL,
            ),
          ),
          // bottom right
          Positioned(
            top: top + height - ballDiameter / 2,
            left: left + width - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var mid = (dx + dy) / 2;

                var newHeight = height + 2 * mid;
                var newWidth = width + 2 * mid;

                setState(() {
                  isCorner = true;

                  height = newHeight > 0 ? newHeight : 0;
                  width = newWidth > 0 ? newWidth : 0;
                  top = top - mid;
                  left = left - mid;
                });
              },
              handlerWidget: HandlerWidget.VERTICAL,
            ),
          ),
          // bottom center
          Positioned(
            top: top + height - ballDiameter / 2,
            left: left + width / 2 - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var newHeight = height + dy;

                setState(() {
                  isCorner = false;

                  height = newHeight > 0 ? newHeight : 0;
                });
              },
              handlerWidget: HandlerWidget.HORIZONTAL,
            ),
          ),
          // bottom left
          Positioned(
            top: top + height - ballDiameter / 2,
            left: left - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var mid = ((dx * -1) + dy) / 2;

                var newHeight = height + 2 * mid;
                var newWidth = width + 2 * mid;

                setState(() {
                  isCorner = true;

                  height = newHeight > 0 ? newHeight : 0;
                  width = newWidth > 0 ? newWidth : 0;
                  top = top - mid;
                  left = left - mid;
                });
              },
              handlerWidget: HandlerWidget.VERTICAL,
            ),
          ),
          //left center
          Positioned(
            top: top + height / 2 - ballDiameter / 2,
            left: left - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var newWidth = width - dx;

                setState(() {
                  isCorner = false;

                  width = newWidth > 0 ? newWidth : 0;
                  left = left + dx;
                });
              },
              handlerWidget: HandlerWidget.HORIZONTAL,
            ),
          ),
          // center center
          Positioned(
            top: top + height / 2 - ballDiameter / 2,
            left: left + width / 2 - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                setState(() {
                  isCorner = false;

                  top = top + dy;
                  left = left + dx;
                });
              },
              handlerWidget: HandlerWidget.VERTICAL,
            ),
          ),
        ],
      );
    }
  }

  class ManipulatingBall extends StatefulWidget {
    ManipulatingBall({Key key, this.onDrag, this.handlerWidget});

    final Function onDrag;
    final HandlerWidget handlerWidget;

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

  enum HandlerWidget { HORIZONTAL, VERTICAL }

  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);
    }

    @override
    Widget build(BuildContext context) {
      return GestureDetector(
        onPanStart: _handleDrag,
        onPanUpdate: _handleUpdate,
        child: Container(
          width: ballDiameter,
          height: ballDiameter,
          decoration: BoxDecoration(
            color: Colors.white,
            shape: this.widget.handlerWidget == HandlerWidget.VERTICAL
                ? BoxShape.circle
                : BoxShape.rectangle,
          ),
        ),
      );
    }
  }

Output:

enter image description here

jazzbpn
  • 6,441
  • 16
  • 63
  • 99
  • When dragging from the four-corners both height and width should increase/decrease in same ratio. It should behave like zoom-in/out. How to achieve that? – jazzbpn Apr 02 '20 at 05:18

5 Answers5

59

Updated

I've made a simple prototype to show the idea.

  1. Draw size handlers and a container;
  2. Use GestureDetector to detect dragging;
  3. Refresh the main container size and coordinates.

enter image description here

    import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Text Overflow Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: Demo(),
      ),
    );
  }
}

class Demo extends StatefulWidget {
  @override
  _DemoState createState() => _DemoState();
}

class _DemoState extends State<Demo> {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(60),
      child: ResizebleWidget(
        child: Text(
'''I've just did simple prototype to show main idea.
  1. Draw size handlers with container;
  2. Use GestureDetector to get new variables of sizes
  3. Refresh the main container size.''',
        ),
      ),
    );
  }
}

class ResizebleWidget extends StatefulWidget {
  ResizebleWidget({this.child});

  final Widget child;
  @override
  _ResizebleWidgetState createState() => _ResizebleWidgetState();
}

const ballDiameter = 30.0;

class _ResizebleWidgetState extends State<ResizebleWidget> {
  double height = 400;
  double width = 200;

  double top = 0;
  double left = 0;

  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;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          top: top,
          left: left,
          child: Container(
            height: height,
            width: width,
            color: Colors.red[100],
            child: widget.child,
          ),
        ),
        // top left
        Positioned(
          top: top - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              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;
              });
            },
          ),
        ),
        // top middle
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newHeight = height - dy;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                top = top + dy;
              });
            },
          ),
        ),
        // top right
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              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;
              });
            },
          ),
        ),
        // center right
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width + dx;

              setState(() {
                width = newWidth > 0 ? newWidth : 0;
              });
            },
          ),
        ),
        // bottom right
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              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;
              });
            },
          ),
        ),
        // bottom center
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newHeight = height + dy;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
              });
            },
          ),
        ),
        // bottom left
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              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;
              });
            },
          ),
        ),
        //left center
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width - dx;

              setState(() {
                width = newWidth > 0 ? newWidth : 0;
                left = left + dx;
              });
            },
          ),
        ),
        // center center
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              setState(() {
                top = top + dy;
                left = left + dx;
              });
            },
          ),
        ),
      ],
    );
  }
}

class ManipulatingBall extends StatefulWidget {
  ManipulatingBall({Key key, this.onDrag});

  final Function onDrag;

  @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);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanStart: _handleDrag,
      onPanUpdate: _handleUpdate,
      child: Container(
        width: ballDiameter,
        height: ballDiameter,
        decoration: BoxDecoration(
          color: Colors.blue.withOpacity(0.5),
          shape: BoxShape.circle,
        ),
      ),
    );
  }
}
Kherel
  • 14,882
  • 5
  • 50
  • 80
  • Thanks for the example. But the required result should have a feature like double tap zoom, pinch to zoom and zoom-in/out from sides and corner as well. – jazzbpn Apr 01 '20 at 07:05
  • 2
    Implementing a whole library would take more time. But there are existing libraries for pinch zoom. You can find it pub.com. – Kherel Apr 01 '20 at 07:16
  • Ok. But, How to make the borders like in the react-native plugin? ManipulatingBall on four-sides and in middle-left and middle-right of the widget(like in the react-native plugin). – jazzbpn Apr 01 '20 at 08:50
  • 1
    yes, nine handlers. All of them has their own drag handlers. Corner handlers and a center handler react on x and y change. Side handlers react on x or y change. Main container has size and coordiante. Left and and top size handlers change coordinate and size. Right and bottom size handlers change size. The center handler change only coordinate. – Kherel Apr 01 '20 at 09:17
  • When dragging from the four-corners both height and width should increase/decrease in same ratio. It should behave like zoom-in/out. How to achieve that? – jazzbpn Apr 02 '20 at 04:59
  • I've created a chat https://chat.stackoverflow.com/rooms/info/210788/creating-resizable-view-that-resizes-when-pinch-or-drag-from-corners-and-sides-in?tab=general – Kherel Apr 02 '20 at 07:59
  • Sure I will upvote. But before that do you mind helping me with one more issue? Please check this video: https://drive.google.com/file/d/1IXi6D6P4hrD7qpPYHnwvABdQYlklqgL6/view?usp=sharing 1. AutoMultiline TextView when dragging from sides (DONE) 2. Zoomable feature for Text or EditText when dragging from corner( Not-Done). The output should be like when the user zoom-in/out the widget will zoom-in/out without being in multiline. Could you please help me with this last issue? Thank you!! – jazzbpn Apr 02 '20 at 10:36
  • @jazzbpn check this: https://gist.github.com/kherel/19ecf4fc9818592fa2b37a6c69bbca55 – Kherel Apr 06 '20 at 19:23
  • 1
    @Kherel I've tried your code, but I have a problem when I use transform.rotate and rotated (for example) 45 degrees it doesn't behave the same way. is it because it now have different left and top position? – AgentRed Nov 18 '21 at 14:20
  • Very nice solution. However I believe the ``onDrag`` in the ``_ResizebleWidgetState`` is unused right? I deleted it and everything still working. – jksevend Aug 22 '22 at 17:28
4

I did a discrete version (ie snaps every 50 units) of the code above if it helps anyone:

    import 'package:flutter/material.dart';
    
    class DiscreteResizableComponent extends StatefulWidget {
      const DiscreteResizableComponent({Key key, this.child}):super(key:key);
    
      final Widget child;
      @override
      _ResizebleWidgetState createState() => _ResizebleWidgetState();
    }
    
    const ballDiameter = 30.0;
    const discreteStepSize = 50;
    
    class _ResizebleWidgetState extends State<DiscreteResizableComponent> {
      double height = 400;
      double width = 200;
    
      double top = 0;
      double left = 0;
    
      double cumulativeDy=0;
      double cumulativeDx=0;
      double cumulativeMid = 0;
    
      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;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            Positioned(
              top: top,
              left: left,
              child: Container(
                height: height,
                width: width,
                color: Colors.red[100],
                child: widget.child,
              ),
            ),
            // top left
            Positioned(
              top: top - ballDiameter / 2,
              left: left - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  var mid = (dx + dy) / 2;
                  cumulativeMid -= 2*mid;
                  if(cumulativeMid>=discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height+discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width +discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                  else if(cumulativeMid<=-discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height - discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width - discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                },
              ),
            ),
            // top middle
            Positioned(
              top: top - ballDiameter / 2,
              left: left + width / 2 - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  cumulativeDy -= dy;
                  if(cumulativeDy>=discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height+discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      cumulativeDy=0;
                    });
                  }
                  else if(cumulativeDy<=-discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height - discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      cumulativeDy=0;
                    });
                  }
                },
              ),
            ),
            // top right
            Positioned(
              top: top - ballDiameter / 2,
              left: left + width - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  var mid = (dx + (dy * -1)) / 2;
                  cumulativeMid += 2*mid;
                  if(cumulativeMid>=discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height+discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width +discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                  else if(cumulativeMid<=-discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height - discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width - discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                },
              ),
            ),
            // center right
            Positioned(
              top: top + height / 2 - ballDiameter / 2,
              left: left + width - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  cumulativeDx += dx;
    
                  if(cumulativeDx>=discreteStepSize)
                  {
                    setState(() {
                      var newWidth = width+discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeDx=0;
                    });
                  }
                  else if(cumulativeDx<=-discreteStepSize)
                  {
                    setState(() {
                      var newWidth = width-discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeDx=0;
                    });
                  }
                },
              ),
            ),
            // bottom right
            Positioned(
              top: top + height - ballDiameter / 2,
              left: left + width - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  var mid = (dx + dy) / 2;
    
                  cumulativeMid += 2*mid;
                  if(cumulativeMid>=discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height+discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width +discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                  else if(cumulativeMid<=-discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height - discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width - discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                },
              ),
            ),
            // bottom center
            Positioned(
              top: top + height - ballDiameter / 2,
              left: left + width / 2 - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  cumulativeDy += dy;
    
                  if(cumulativeDy>=discreteStepSize)
                    {
                      setState(() {
                        var newHeight = height+discreteStepSize;
                        height = newHeight > 0 ? newHeight : 0;
                        cumulativeDy=0;
                      });
                    }
                  else if(cumulativeDy<=-discreteStepSize)
                    {
                      setState(() {
                        var newHeight = height-discreteStepSize;
                        height = newHeight > 0 ? newHeight : 0;
                        cumulativeDy=0;
                      });
                    }
                },
              ),
            ),
            // bottom left
            Positioned(
              top: top + height - ballDiameter / 2,
              left: left - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  var mid = ((dx * -1) + dy) / 2;
    
                  cumulativeMid += 2*mid;
                  if(cumulativeMid>=discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height+discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width +discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                  else if(cumulativeMid<=-discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height - discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width - discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                },
              ),
            ),
            //left center
            Positioned(
              top: top + height / 2 - ballDiameter / 2,
              left: left - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  cumulativeDx -= dx;
    
                  if(cumulativeDx>=discreteStepSize)
                  {
                    setState(() {
                      var newWidth = width+discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeDx=0;
                    });
                  }
                  else if(cumulativeDx<=-discreteStepSize)
                  {
                    setState(() {
                      var newWidth = width-discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeDx=0;
                    });
                  }
                },
              ),
            ),
          ],
        );
      }
    }
    
    class ManipulatingBall extends StatefulWidget {
      ManipulatingBall({Key key, this.onDrag});
    
      final Function onDrag;
    
      @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);
      }
    
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onPanStart: _handleDrag,
          onPanUpdate: _handleUpdate,
          child: Container(
            width: ballDiameter,
            height: ballDiameter,
            decoration: BoxDecoration(
              color: Colors.blue.withOpacity(0.5),
              shape: BoxShape.circle,
            ),
          ),
        );
      }
    }
Marik Ishtar
  • 2,899
  • 1
  • 13
  • 27
Seth Kitchen
  • 1,526
  • 19
  • 53
1

Updated Marik Ishtar's code. Fixed some resize errors and added discrete movement to center ball. Thanks Kherel and Marik

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Text Overflow Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: Demo(),
      ),
    );
  }
}

class Demo extends StatefulWidget {
  @override
  _DemoState createState() => _DemoState();
}

class _DemoState extends State<Demo> {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(60),
      child: DiscreteResizableComponent(
        child: Text(
          '''I've just did simple prototype to show main idea.
  1. Draw size handlers with container;
  2. Use GestureDetector to get new variables of sizes
  3. Refresh the main container size.''',
        ),
      ),
    );
  }
}

class DiscreteResizableComponent extends StatefulWidget {
  const DiscreteResizableComponent({Key? key, required this.child})
      : super(key: key);

  final Widget child;
  @override
  _ResizebleWidgetState createState() => _ResizebleWidgetState();
}

const ballDiameter = 30.0;
const discreteStepSize = 50;

class _ResizebleWidgetState extends State<DiscreteResizableComponent> {
  double height = 400;
  double width = 200;

  double top = 0;
  double left = 0;

  double cumulativeDy = 0;
  double cumulativeDx = 0;
  double cumulativeMid = 0;

  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;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          top: top,
          left: left,
          child: Container(
            height: height,
            width: width,
            color: Colors.red[100],
            child: widget.child,
          ),
        ),
        // top left
        Positioned(
          top: top - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = (dx + dy) / 2;
              cumulativeMid -= 2 * mid;
              if (cumulativeMid >= discreteStepSize) {
                setState(() {
                  var newHeight = height + discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  var newWidth = width + discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;
                   top-=discreteStepSize;
                  left-=discreteStepSize;                 
                  
                  cumulativeMid = 0;
                });
              } else if (cumulativeMid <= -discreteStepSize) {
                setState(() {
                  var newHeight = height - discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  var newWidth = width - discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;
                  top+=discreteStepSize;
                  left+=discreteStepSize;
                  cumulativeMid = 0;
                });
              }
            },
          ),
        ),
        // center center
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              // top
              // dy -
              //bottom
              // dy +
              cumulativeDy += dy;

              if (cumulativeDy >= discreteStepSize) {
                setState(() {
                  top += discreteStepSize;
                  cumulativeDy = 0;
                });
              } else if (cumulativeDy <= -discreteStepSize) {
                setState(() {
                  top -= discreteStepSize;
                  cumulativeDy = 0;
                });
              }
              // left -> -dx
              // right -> +dx
              cumulativeDx += dx;

              if (cumulativeDx >= discreteStepSize) {
                setState(() {
                  left += discreteStepSize;
                  cumulativeDx = 0;
                });
              } else if (cumulativeDx <= -discreteStepSize) {
                setState(() {
                  left -= discreteStepSize;
                  cumulativeDx = 0;
                });
              }
            },
          ),
        ),

        // top right
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = (dx + (dy * -1)) / 2;
              cumulativeMid += 2 * mid;
              if (cumulativeMid >= discreteStepSize) {
                setState(() {
                  var newHeight = height + discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  var newWidth = width + discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;
                  top -= discreteStepSize;

                  cumulativeMid = 0;
                });
              } else if (cumulativeMid <= -discreteStepSize) {
                setState(() {
                  var newHeight = height - discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  var newWidth = width - discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;
                  top += discreteStepSize;

                  cumulativeMid = 0;
                });
              }
            },
          ),
        ),

        // bottom right
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = (dx + dy) / 2;

              cumulativeMid += 2 * mid;
              if (cumulativeMid >= discreteStepSize) {
                setState(() {
                  var newHeight = height + discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  var newWidth = width + discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;
                  cumulativeMid = 0;
                });
              } else if (cumulativeMid <= -discreteStepSize) {
                setState(() {
                  var newHeight = height - discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  var newWidth = width - discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;
                  cumulativeMid = 0;
                });
              }
            },
          ),
        ),
        // top middle
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              cumulativeDy -= dy;
              if (cumulativeDy >= discreteStepSize) {
                setState(() {
                  var newHeight = height + discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  top -= discreteStepSize;

                  cumulativeDy = 0;
                });
              } else if (cumulativeDy <= -discreteStepSize) {
                setState(() {
                  var newHeight = height - discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  top += discreteStepSize;

                  cumulativeDy = 0;
                });
              }
            },
          ),
        ),
        // bottom center
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              cumulativeDy += dy;

              if (cumulativeDy >= discreteStepSize) {
                setState(() {
                  var newHeight = height + discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  cumulativeDy = 0;
                });
              } else if (cumulativeDy <= -discreteStepSize) {
                setState(() {
                  var newHeight = height - discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  cumulativeDy = 0;
                });
              }
            },
          ),
        ),
        // bottom left
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = ((dx * -1) + dy) / 2;

              cumulativeMid += 2 * mid;
              if (cumulativeMid >= discreteStepSize) {
                setState(() {
                  var newHeight = height + discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  var newWidth = width + discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;
                  left -= discreteStepSize;
                  cumulativeMid = 0;
                });
              } else if (cumulativeMid <= -discreteStepSize) {
                setState(() {
                  var newHeight = height - discreteStepSize;
                  height = newHeight > 0 ? newHeight : 0;
                  var newWidth = width - discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;
                  left += discreteStepSize;
                  cumulativeMid = 0;
                });
              }
            },
          ),
        ),
        //left center
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              cumulativeDx -= dx;

              if (cumulativeDx >= discreteStepSize) {
                setState(() {
                  var newWidth = width + discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;

                  left = left - discreteStepSize;

                  cumulativeDx = 0;
                });
              } else if (cumulativeDx <= -discreteStepSize) {
                setState(() {
                  var newWidth = width - discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;

                  left = left + discreteStepSize;
                  cumulativeDx = 0;
                });
              }
            },
          ),
        ),
        // center right
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              cumulativeDx += dx;

              if (cumulativeDx >= discreteStepSize) {
                setState(() {
                  var newWidth = width + discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;
                  cumulativeDx = 0;
                });
              } else if (cumulativeDx <= -discreteStepSize) {
                setState(() {
                  var newWidth = width - discreteStepSize;
                  width = newWidth > 0 ? newWidth : 0;
                  cumulativeDx = 0;
                });
              }
            },
          ),
        ),
      ],
    );
  }
}

class ManipulatingBall extends StatefulWidget {
  ManipulatingBall({Key? key, required this.onDrag});

  final Function onDrag;

  @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);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanStart: _handleDrag,
      onPanUpdate: _handleUpdate,
      child: Container(
        width: ballDiameter,
        height: ballDiameter,
        decoration: BoxDecoration(
          color: Colors.blue.withOpacity(0.5),
          shape: BoxShape.circle,
        ),
      ),
    );
  }
}
0

Thanks all, Just Updated Kherel's answer as per Flutter 3.3.X because having few issues on it.

import 'package:flutter/material.dart';

class ResizableWidget extends StatefulWidget {
  const ResizableWidget({super.key, required this.child});

  final Widget child;
  @override
  ResizableWidgetState createState() => ResizableWidgetState();
}

const ballDiameter = 30.0;

class ResizableWidgetState extends State<ResizableWidget> {
  double height = 400;
  double width = 200;

  double top = 0;
  double left = 0;

  void onDrag(double dx, double dy) {
    final newHeight = height + dy;
    final newWidth = width + dx;

    setState(() {
      height = newHeight > 0 ? newHeight : 0;
      width = newWidth > 0 ? newWidth : 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          top: top,
          left: left,
          child: Container(
            height: height,
            width: width,
            color: Colors.red[100],
            child: widget.child,
          ),
        ),
        // top left
        Positioned(
          top: top - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              final mid = (dx + dy) / 2;
              final newHeight = height - 2 * mid;
              final newWidth = width - 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top + mid;
                left = left + mid;
              });
            },
          ),
        ),
        // top middle
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              final newHeight = height - dy;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                top = top + dy;
              });
            },
          ),
        ),
        // top right
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              final mid = (dx + (dy * -1)) / 2;

              final newHeight = height + 2 * mid;
              final newWidth = width + 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top - mid;
                left = left - mid;
              });
            },
          ),
        ),
        // center right
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              final newWidth = width + dx;

              setState(() {
                width = newWidth > 0 ? newWidth : 0;
              });
            },
          ),
        ),
        // bottom right
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              final mid = (dx + dy) / 2;

              final newHeight = height + 2 * mid;
              final newWidth = width + 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top - mid;
                left = left - mid;
              });
            },
          ),
        ),
        // bottom center
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              final newHeight = height + dy;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
              });
            },
          ),
        ),
        // bottom left
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              final mid = ((dx * -1) + dy) / 2;

              final newHeight = height + 2 * mid;
              final newWidth = width + 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top - mid;
                left = left - mid;
              });
            },
          ),
        ),
        //left center
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              final newWidth = width - dx;

              setState(() {
                width = newWidth > 0 ? newWidth : 0;
                left = left + dx;
              });
            },
          ),
        ),
        // center center
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              setState(() {
                top = top + dy;
                left = left + dx;
              });
            },
          ),
        ),
      ],
    );
  }
}

class ManipulatingBall extends StatefulWidget {
  const ManipulatingBall({
    super.key,
    required this.onDrag,
  });

  final void Function(double dx, double dy) onDrag;

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

class _ManipulatingBallState extends State<ManipulatingBall> {
  late double initX;
  late double initY;

  void _handleDrag(DragStartDetails details) {
    setState(() {
      initX = details.globalPosition.dx;
      initY = details.globalPosition.dy;
    });
  }

  void _handleUpdate(DragUpdateDetails details) {
    final dx = details.globalPosition.dx - initX;
    final dy = details.globalPosition.dy - initY;
    initX = details.globalPosition.dx;
    initY = details.globalPosition.dy;
    widget.onDrag(dx, dy);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanStart: _handleDrag,
      onPanUpdate: _handleUpdate,
      child: Container(
        width: ballDiameter,
        height: ballDiameter,
        decoration: BoxDecoration(
          color: Colors.blue.withOpacity(0.5),
          shape: BoxShape.circle,
        ),
      ),
    );
  }
}
-2

You can Use resizeable_widget package Here is official example

import 'package:flutter/material.dart';
import 'package:resizable_widget/resizable_widget.dart';

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ResizableWidget(
        children: [ // required
          Container(color: Colors.greenAccent),
          Container(color: Colors.yellowAccent),
          Container(color: Colors.redAccent),
        ],
        isHorizontalSeparator: false,   // optional
        isDisabledSmartHide: false,     // optional
        separatorColor: Colors.white12, // optional
        separatorSize: 4,               // optional
        percentages: [0.2, 0.5, 0.3],   // optional
        onResized: (infoList) =>        // optional
            print(infoList.map((x) => '(${x.size}, ${x.percentage}%)').join(", ")),
      ),
    );
  }
}

Resultant Gif

Mudasir Habib
  • 704
  • 7
  • 14