Axel
Axel

Reputation: 5111

Create a resizable view in flutter

I am creating a resizable view for an image. The code is as follows:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: SafeArea(
          child: ImageManager(),
        ),
      ),
    );
  }
}

final ballRadius = 7.5;

class ImageManager extends StatefulWidget {
  @override
  _ImageManagerState createState() => _ImageManagerState();
}

class _ImageManagerState extends State<ImageManager> {
  double _x = 0;
  double _y = 0;

  double _height = 200;
  double _width = 300;

  double _aspectRatio = 200 / 300;

  @override
  Widget build(BuildContext context) {
    return Stack(
      overflow: Overflow.visible,
      children: <Widget>[
        Positioned(
          top: _y,
          left: _x,
          child: GestureDetector(
            onPanUpdate: (DragUpdateDetails details) {
              setState(() {
                _x += details.delta.dx;
                _y += details.delta.dy;
              });
            },
            child: Image.network(
              "https://via.placeholder.com/300x200",
              width: _width,
            ),
          ),
        ),

        // top left
        Positioned(
          top: _y - ballRadius,
          left: _x - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {},
            onDragEnd: () {},
          ),
        ),

        // top middle
        Positioned(
          top: _y - ballRadius,
          left: _x + _width / 2 - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {},
            onDragEnd: () {},
          ),
        ),

        // top right
        Positioned(
          top: _y - ballRadius,
          left: _x + _width - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {},
            onDragEnd: () {},
          ),
        ),

        // middle left
        Positioned(
          top: _y + _height / 2 - ballRadius,
          left: _x - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {},
            onDragEnd: () {},
          ),
        ),

        // middle right
        Positioned(
          top: _y + _height / 2 - ballRadius,
          left: _x + _width - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {},
            onDragEnd: () {},
          ),
        ),

        // bottom left
        Positioned(
          top: _y + _height - ballRadius,
          left: _x - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {},
            onDragEnd: () {},
          ),
        ),

        // bottom middle
        Positioned(
          top: _y + _height - ballRadius,
          left: _x + _width / 2 - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {},
            onDragEnd: () {},
          ),
        ),

        // bottom right
        Positioned(
          top: _y + _height - ballRadius,
          left: _x + _width - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {
              var mid = (dx + dy) / 2;
              var newWidth = _width + 2 * mid;
              var newHeight = newWidth * _aspectRatio;

              setState(() {
                _width = newWidth;
                _height = newHeight;
                _y = _y - dy;
                _x = _x - 2 * dx;
              });
            },
            onDragEnd: () {},
          ),
        ),
      ],
    );
  }
}

class Ball extends StatelessWidget {
  final Function onDragStart;
  final Function onDrag;
  final Function onDragEnd;

  const Ball({this.onDragStart, this.onDrag, this.onDragEnd});

  void _onDragStart(DragStartDetails details) {
    if (onDragStart != null) onDragStart();
  }

  void _onDragUpdate(DragUpdateDetails details) {
    if (onDrag != null) onDrag(details.delta.dx, details.delta.dy);
  }

  void _onDragEnd(DragEndDetails details) {
    if (onDragEnd != null) onDragEnd();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanStart: _onDragStart,
      onPanUpdate: _onDragUpdate,
      onPanEnd: _onDragEnd,
      child: Container(
        height: 2 * ballRadius,
        width: 2 * ballRadius,
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(ballRadius),
          border: Border.all(
            width: 3,
            color: Colors.white,
          ),
        ),
      ),
    );
  }
}

My goal is to uniformly resize like the following:

enter image description here

However, currently, it looks like this.

enter image description here

As you can see that the x and y coordinates are messed up. The goal here is if you resize the image from the bottom right corner then the image will stay at the top left corner. Please help with this. Thanks.

Upvotes: 3

Views: 2473

Answers (4)

Rajesh
Rajesh

Reputation: 4000

@Sajjad answer worked well, and I've updated all the corners with the same approach.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: SafeArea(
          child: ImageManager(),
        ),
      ),
    );
  }
}

const double ballRadius = 7.5;

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

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

class _ImageManagerState extends State<ImageManager> {
  double _x = 0;
  double _y = 0;

  double _height = 200;
  double _width = 300;

  final double _aspectRatio = 200 / 300;

  @override
  Widget build(BuildContext context) {
    return Stack(
      clipBehavior: Clip.none,
      children: <Widget>[
        Positioned(
          top: _y,
          left: _x,
          child: GestureDetector(
            onPanUpdate: (DragUpdateDetails details) {
              setState(() {
                _x += details.delta.dx;
                _y += details.delta.dy;
              });
            },
            child: Image.network(
              'https://via.placeholder.com/300x200',
              height: _height,
              width: _width,
              fit: BoxFit.fill,
            ),
          ),
        ),

        // top left
        Positioned(
          top: _y - ballRadius,
          left: _x - ballRadius,
          child: Ball(
            onDrag: (double dx, double dy) {
              final double newWidth = _width - dx;
              final double newHeight = newWidth * _aspectRatio;
              setState(() {
                _y = _y + (_height - newHeight);
                _x = _x + dx;
                _width = newWidth;
                _height = newHeight;
              });
            },
          ),
        ),
        // top middle
        Positioned(
          top: _y - ballRadius,
          left: _x + _width / 2 - ballRadius,
          child: Ball(
            onDrag: (double dx, double dy) {
              setState(() {
                // Calculate the new height based on the drag direction
                final double newHeight = _height - dy;
                // Prevent the newHeight from becoming negative or too small
                if (newHeight > 0) {
                  _height = newHeight;
                  // Move the Y position to resize the widget upwards or downwards
                  _y += dy;
                }
              });
            },
          ),
        ),

        // top right
        Positioned(
          top: _y - ballRadius,
          left: _x + _width - ballRadius,
          child: Ball(
            onDrag: (double dx, double dy) {
              setState(() {
                final double newWidth = _width + dx;
                final double newHeight = newWidth * _aspectRatio;
                final double heightChange = _height - newHeight; // Calculate the height change

                // Update width and height
                _width = newWidth;
                _height = newHeight;
                // Adjust y position to maintain the top edge in place
                _y += heightChange;
              });
            },
          ),
        ),

        // middle left
        Positioned(
          top: _y + _height / 2 - ballRadius,
          left: _x - ballRadius,
          child: Ball(
            onDrag: (double dx, double dy) {
              final double newWidth = _width - dx;
              setState(() {
                _x = _x + dx;
                _width = newWidth;
              });
            },
          ),
        ),
        // middle right
        Positioned(
          top: _y + _height / 2 - ballRadius,
          left: _x + _width - ballRadius,
          child: Ball(
            onDrag: (double dx, double dy) {
              final double newWidth = _width + dx;
              setState(() {
                _width = newWidth;
              });
            },
          ),
        ),

        // bottom left
        Positioned(
          top: _y + _height - ballRadius,
          left: _x - ballRadius,
          child: Ball(
            onDrag: (double dx, double dy) {
              final double newHeight = _height + dy;
              final double newWidth = newHeight / _aspectRatio;
              setState(() {
                _x = _x + (_width - newWidth);
                _width = newWidth;
                _height = newHeight;
              });
            },
          ),
        ),

        // bottom middle
        Positioned(
          top: _y + _height - ballRadius,
          left: _x + _width / 2 - ballRadius,
          child: Ball(
            onDrag: (double dx, double dy) {
              setState(() {
                final double newHeight = _height + dy; // Adjust height based on dy
                // Ensure newHeight is positive to prevent invalid dimensions
                if (newHeight > 0) {
                  _height = newHeight;
                } else {
                  // Optionally, handle the case where newHeight would be invalid
                  // For example, set a minimum height if desired
                  _height = 0; // Or set to a minimum valid height
                }
              });
            },
          ),
        ),
        // bottom right
        Positioned(
          top: _y + _height - ballRadius,
          left: _x + _width - ballRadius,
          child: Ball(
            onDrag: (double dx, double dy) {
              setState(() {
                final double newWidth = _width + dx;
                final double newHeight = newWidth * _aspectRatio;

                _width = newWidth;
                _height = newHeight;
              });
            },
          ),
        ),
      ],
    );
  }
}

class Ball extends StatelessWidget {
  final Function onDrag;

  const Ball({
    super.key,
    required this.onDrag,
  });

  void _onDragUpdate(DragUpdateDetails details) {
    if (onDrag != null) {
      onDrag(details.delta.dx, details.delta.dy);
    }
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: _onDragUpdate,
      child: Container(
        height: 2 * ballRadius,
        width: 2 * ballRadius,
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(ballRadius),
          border: Border.all(
            width: 3,
            color: Colors.white,
          ),
        ),
      ),
    );
  }
}

Upvotes: 0

Kalpesh Khandla
Kalpesh Khandla

Reputation: 1

  Complete code will look like a below.

  import 'package:flutter/material.dart';
  import 'package:flutter_text_to_image/utils/app_colors.dart';
  import 'package:flutter/foundation.dart';
  import 'package:flutter_text_to_image/utils/app_string.dart';
  import 'package:flutter_text_to_image/widgets/draggable_text_form_field_widget.dart';
  
 /*
  Title: DraggableTextFormFieldScreen
  Purpose: DraggableTextFormFieldScreen
  Created On : 16/11/2022
  Last Updated On : 16/11/2022
  Author : Kalpesh Khandla
  */

  class DraggableTextFormFieldScreen extends StatefulWidget {
  @override
  State
  <DraggableTextFormFieldScreen>
  createState() =>
  _DraggableTextFormFieldScreenState();
  }
  class _DraggableTextFormFieldScreenState
  extends State
  <DraggableTextFormFieldScreen>
  {
  TextEditingController wordController = TextEditingController();
  @override
  Widget build(BuildContext context) {
  return Scaffold(
  body: SafeArea(
  child: DraggableWidget(),
  ),
  );
  }
  }
  class DraggableWidget extends StatefulWidget {
  @override
  _DraggableWidgetState createState() => _DraggableWidgetState();
  }
  TextEditingController wordController = TextEditingController();
  const ballDiameter = 7.5;
  class _DraggableWidgetState extends State
  <DraggableWidget>
  {
  double height = 70;
  double width = 250;
  double topPosition = 250;
  double leftPosition = 70;
  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 Scaffold(
  resizeToAvoidBottomInset: true,
  body: SafeArea(
  child: GestureDetector(
  onPanUpdate: (details) {
  setState(() {
  topPosition += details.delta.dy;
  leftPosition += details.delta.dx;
  });
  },
  child: Stack(
  children: 
  <Widget>
  [
  Positioned(
  top: topPosition,
  left: leftPosition,
  child: Container(
  height: height,
  width: width,
  decoration: BoxDecoration(
  color: AppColors.whiteColor,
  border: Border.all(
  color: AppColors.textfieldBorderColor.withOpacity(0.7),
  ),
  ),
  child: Center(
  child: DraggableTextFormFieldWidget(
  controllerName: wordController,
  hintTxt: AppStrings.textfieldHintTxt,
  keyboardType: TextInputType.text,
  cursorColor: AppColors.textfieldBorderColor,
  contentPadding: 25,
  onChange: (p0) {},
  onSaved: (p0) {},
  validatorData: (p0) {},
  ),
  ),
  ),
  ),
  // top left
  Positioned(
  top: topPosition - ballDiameter / 2,
  left: leftPosition - ballDiameter / 2,
  child: SelectorWidget(
  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;
  topPosition = topPosition + mid;
  leftPosition = leftPosition + mid;
  });
  },
  ),
  ),
  // top middle
  Positioned(
  top: topPosition - ballDiameter / 2,
  left: leftPosition + width / 2 - ballDiameter / 2,
  child: SelectorWidget(
  onDrag: (dx, dy) {
  var newHeight = height - dy;
  setState(() {
  height = newHeight > 0 ? newHeight : 0;
  topPosition = topPosition + dy;
  });
  },
  ),
  ),
  // top right
  Positioned(
  top: topPosition - ballDiameter / 2,
  left: leftPosition + width - ballDiameter / 2,
  child: SelectorWidget(
  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;
  topPosition = topPosition - mid;
  leftPosition = leftPosition - mid;
  });
  },
  ),
  ),
  // center right
  Positioned(
  top: topPosition + height / 2 - ballDiameter / 2,
  left: leftPosition + width - ballDiameter / 2,
  child: SelectorWidget(
  onDrag: (dx, dy) {
  var newWidth = width + dx;
  setState(() {
  width = newWidth > 0 ? newWidth : 0;
  });
  },
  ),
  ),
  // bottom right
  Positioned(
  top: topPosition + height - ballDiameter / 2,
  left: leftPosition + width - ballDiameter / 2,
  child: SelectorWidget(
  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;
  topPosition = topPosition - mid;
  leftPosition = leftPosition - mid;
  });
  },
  ),
  ),
  // bottom center
  Positioned(
  top: topPosition + height - ballDiameter / 2,
  left: leftPosition + width / 2 - ballDiameter / 2,
  child: SelectorWidget(
  onDrag: (dx, dy) {
  var newHeight = height + dy;
  setState(() {
  height = newHeight > 0 ? newHeight : 0;
  });
  },
  ),
  ),
  // bottom left
  Positioned(
  top: topPosition + height - ballDiameter / 2,
  left: leftPosition - ballDiameter / 2,
  child: SelectorWidget(
  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;
  topPosition = topPosition - mid;
  leftPosition = leftPosition - mid;
  });
  },
  ),
  ),
  //left center
  Positioned(
  top: topPosition + height / 2 - ballDiameter / 2,
  left: leftPosition - ballDiameter / 2,
  child: SelectorWidget(
  onDrag: (dx, dy) {
  var newWidth = width - dx;
  setState(() {
  width = newWidth > 0 ? newWidth : 0;
  leftPosition = leftPosition + dx;
  });
  },
  ),
  ),
  //  center center
  ],
  ),
  ),
  ),
  );
  }
  }
  class SelectorWidget extends StatefulWidget {
  SelectorWidget({
  required this.onDrag,
  });
  final Function onDrag;
  @override
  _SelectorWidgetState createState() => _SelectorWidgetState();
  }
  class _SelectorWidgetState extends State
  <SelectorWidget>
  {
  late double initX;
  late 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(
  height: 10,
  width: 10,
  decoration: BoxDecoration(
  borderRadius: BorderRadius.circular(2),
  color: AppColors.whiteColor,
  border: Border.all(
  width: 1.5,
  color: AppColors.textfieldBorderColor.withOpacity(0.9),
  ),
  ),
  ),
  );
  }
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  super.debugFillProperties(properties);
  properties.add(DoubleProperty('X Cordinates', initX));
  }
  }

Upvotes: 1

Sajjad
Sajjad

Reputation: 3218

i change the the top left positioned like this

 // top left
    Positioned(
      top: _y - ballRadius,
      left: _x - ballRadius,
      child: Ball(
        onDragStart: () {},
        onDrag: (double dx, double dy) {
          var newWidth = _width - dx;
          var newHeight = newWidth * _aspectRatio;
          setState(() {
            _y = _y + (_height - newHeight);
            _x = _x + dx;
            _width = newWidth ;
            _height = newHeight;
          });
        },
        onDragEnd: () {},
      ),
    ),

and bottom right positioned (just for completing Answer)

        Positioned(
      top: _y + _height - ballRadius,
      left: _x + _width - ballRadius,
      child: Ball(
        onDragStart: () {},
        onDrag: (double dx, double dy) {

          var newWidth = _width + dx;
          var newHeight = newWidth * _aspectRatio;

          setState(() {
            _width = newWidth ;
            _height = newHeight;
          });
        },
        onDragEnd: () {},
      ),
    ),

and adding this parameter to image.network

fit: BoxFit.fill,

full code at here : https://dartpad.dev/44adae92cecbd2dddc00f264293e5c3a

Upvotes: 4

camelCase1492
camelCase1492

Reputation: 672

Take a look at a brand new widget implemented in Flutter 1.20, InteractiveViewer. It allows you to zoom and pan a child.

You could also use a combination of GestureDetector and StatefulBuilder to update a child every time you drag your finger

Upvotes: 0

Related Questions