Reputation: 5111
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:
However, currently, it looks like this.
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
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
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
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
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