I have a Positioned widget that is draggable, by using Offset and wrapping it inside a Gesture Detector to update its position, and I want to constrain the area that this widget can move, so it cannot go beyond the boundaries. The structure is like this:
Scaffold -> Stack -> Positioned(Circle)
As it is shown below, I want the circle to move only in the are inside the gray lines. Is it possible?
Provide 2x value as limit, I did for touch position purpose.
also, both dx and dy axis can work separately. If you don't want it, you can combine two condition on a single setState.
Result
Widget
class HomeWidget extends StatefulWidget {
#override
_HomeWidgetState createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
double dx = 0;
double dy = 0;
get limit => 50;
get containerSize => 50;
#override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => Stack(
children: [
Positioned(
left: limit * .5,
child: Container(
height: constraints.maxHeight,
width: 5,
color: Colors.grey,
),
),
Positioned(
bottom: limit * .5,
child: Container(
width: constraints.maxWidth,
height: 5,
color: Colors.grey,
),
),
Positioned(
top: dy - containerSize * .5,
left: dx - containerSize * .5,
child: Container(
height: containerSize,
width: containerSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.deepPurple,
),
),
),
GestureDetector(
onPanUpdate: (details) {
if (details.localPosition.dx > limit)
setState(() {
dx = details.localPosition.dx;
});
if (details.localPosition.dy < constraints.maxHeight - limit)
setState(() {
dy = details.localPosition.dy;
});
print(" $dx, $dy ");
},
),
],
),
),
);
}
}
Related
My aim is to postion a text centrally on top of a picture by using a Stack widget. I want that the text stays in the middle independet of how the window is resized.
Based on my research should the solution be a the following structure:
Stack Widget
Impage Widget
Layout Builder
Text Widget
However when trying this my Text sticks to the top left corner. So i create a simpler version of the code and am still unable to reach the desired outcome. My current thinking is that maybe my Layoutbuilder does not correctly receive the size input of the window.
My example code is as follows
main.dart
import 'package:flutter/material.dart';
import 'package:imdb/screens/home_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomeScreen());
}
}
home_screen.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xfff3f3f3),
body: Stack(
children: [
Container(
height: 200,
width: 200,
color: Colors.red,
),
LayoutBuilder(builder: (context, constraints) {
return Positioned(
left: constraints.maxWidth * 0.5,
top: constraints.maxHeight*0.5,
child: Container(
height: 10,
width: 10,
color: Colors.yellowAccent
),
);
}),
],
));
}
}
As output I would expect that the yellow container is placed centrally on the Red box. However the output looks as follows:
I would suggest directly setting alignment on Stack if you need to use a stack (you could just add the overlapping element as a child):
Scaffold(
body: Container(
color: Colors.blue,
child: Stack(alignment: Alignment.center, children: [
Container(
color: Colors.red,
),
Container(
height: 10,
width: 10,
color: Colors.yellow,
),
],
),
),
);
Stacks that are not explicitly sized (via SizedBox, for example) will be the size of their largest child, unless they have constraints placed upon them ("Size goes up, constraints go down").
To answer your question though, Stacks must have positioned elements as direct children.
Edit: To address your comment, you can use an alignment offset from 0,0:
Scaffold(
body: SizedBox(
height: 200,
width: 200,
child: Stack(children: [
Container(
color: Colors.red,
),
Align(
alignment: Alignment(0,0.5),
child: Container(
height: 20,
width: 20,
color: Colors.yellow,
),
),
]),
),
);
If you don't need to use a stack, you could set the yellow box as a child of your parent container, and use alignment on the parent container:
Scaffold(
body: Container(
color: Colors.red,
height: 200,
width: 200,
alignment: const Alignment(0.5, 0.5),
child: Container(
height: 20,
width: 20,
color: Colors.yellow,
),
),
);
Align uses a fractional offset. If you want to use an absolute offset, you can wrap the child in padding or use Transform.translate. The only thing to keep in mind with Transforms is that difficult bugs can arise when it comes to pointer events (hit boxes are transformed somewhat unexpectedly)
Scaffold(
body: Container(
color: Colors.red,
height: 200,
width: 200,
alignment: Alignment.center,
child: Transform.translate(
offset: const Offset(50, 10),
child: Container(
height: 20,
width: 20,
color: Colors.yellow,
),
),
),
);
If you just want to center something, you can use the Center widget - no need to use LayoutBuilder.
So your code would look something like this:
Stack:
[
Image,
Center:
Text,
]
This way, your stack will match the size of your image, and the Text widget will be centered in the image.
Edit: Apparently the question wants to do more than just center. In that case, use Align widget instead. For the alignment property, you can pass in Alignment(0, 0) for center, or values between [-1.0, +1.0] for both x and y offsets. For example, "far-left" would be Alignment (-1.0, 0).
As long as the position of the y-axis is the same, the x-axis can be adjusted freely
this is example
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _switch = ValueNotifier<bool>(false);
#override
void initState() {
super.initState();
}
#override
void dispose() {
_switch.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var screenWidth = MediaQuery.of(context).size.width;
var screenHeight = MediaQuery.of(context).size.height;
var bgBoxHeight = screenHeight * .5;
var bgBoxWidth = screenWidth * .5;
var secendBoxHeight = bgBoxHeight * .2;
var secendBoxWidth = bgBoxWidth * .2;
// Just need to adjust the left and right offset
var xAxisStartPosition = ((bgBoxWidth / 2) - secendBoxWidth / 2) * .3;
var yAxisStartPosition = (bgBoxHeight / 2) - secendBoxHeight / 2;
var rightPading = bgBoxWidth * .1;
var bottomPading = bgBoxHeight * .1;
return Scaffold(
backgroundColor: Color(0xfff3f3f3),
body: Stack(
fit: StackFit.loose,
children: [
Container(
height: bgBoxHeight,
width: bgBoxWidth,
color: Colors.red,
),
Positioned(
left: xAxisStartPosition,
bottom: yAxisStartPosition,
child: LayoutBuilder(builder: (context, constraints) {
return Container(
height: secendBoxHeight,
width: secendBoxWidth,
color: Colors.yellowAccent);
}),
),
Positioned(
left: xAxisStartPosition,
right: rightPading,
// bottom: bottomPading,
bottom: yAxisStartPosition,
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: bgBoxWidth, maxHeight: bgBoxHeight),
child: Text("""Easy to use, stylish placeholders
Just add your desired image size (width & height) after our URL, and you'll get a random image."""),
))
],
));
}
}
I am using stack and positioned with x y coordinates from gesturedetector, ontapdowndetils(details),in with details provide localposition.
But by positioning it with x y , it is not responsive as I tried to do it with stack overflow answered questions and tips. Its changing position from device to device. please refer images and code provided , need to position it on same spots on all devices.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectedImageIndex = 0;
String ourImage = '';
void _changeImageIndex(index) {
setState(() {
selectedImageIndex = index;
});
}
addImageToSpots() {
setState(() {
ourImage =
'https://img.ecelebrityfacts.com/wp-content/uploads/2016/08
/elon-musk-3067-30988-1471258987-2048x2048.jpg';
});
}
List<ImageList> list = [
ImageList(
image:
'https://www.101planners.com/borders/wp-content/uploads/2018/03
/wanted-poster-13.jpg',
x: 1650.0,
y: 1229.0502793296089,
),
ImageList(
image: 'https://img1.etsystatic.com/000/0/6713244/il_570xN.306337089.jpg',
x: 99.35715553977273,
y: 291.88920454545456,
),
];
var newX;
var newY;
void setPoints() async {
if (mounted) {
setState(() {
var newWidth = MediaQuery.of(context).size.width;
var newHeight = MediaQuery.of(context).size.height * .6;
double baseWidth = 395;
double baseHeight = 460;
newX = (newWidth * 102) / baseWidth;
newY = (newHeight * 326) / baseHeight;
});
}
}
#override
Widget build(BuildContext context) {
setPoints();
return Scaffold(
appBar: AppBar(
title: const Text('pip image'),
),
body: Center(
child: Column(
children: [
imageSelector(context),
buildImageList(),
//buildImageList(),
],
)),
);
}
imageSelector(BuildContext context) {
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return Stack(
children: [
SizedBox(
height: height * .6,
width: width,
child: GestureDetector(
onTapDown: ((details) {
print(height);
print(width);
print(details
.localPosition.dx); // for getting the image local position
print(details
.localPosition.dy); // for getting the image local position
}),
child: Image.network(
list[selectedImageIndex].image,
fit: BoxFit.contain,
),
),
),
if (selectedImageIndex == 0)
Positioned(
left: newX,
top: newY,
child: GestureDetector(
onTap: () {
print('addImageToSpots');
addImageToSpots();
},
child: Container(
color: Colors.grey,
height: (height * .6) * .31,
width: width * .36,
child: ourImage.isEmpty
? null
: Image.network(
ourImage,
fit: BoxFit.cover,
),
),
),
)
else
Positioned(
left: newX,
top: newY,
child: GestureDetector(
onTap: () {
print('addImageToSpots');
addImageToSpots();
},
child: CircleAvatar(
radius: (width * .6) * .19,
backgroundImage:
ourImage.isEmpty ? null : NetworkImage(ourImage),
),
),
)
],
);
}
buildImageList() {
return Expanded(
child: Container(
height: 180,
color: Colors.blueGrey,
child: ListView.builder(
itemCount: list.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
return imageList(index);
},
),
),
);
}
imageList(int index) {
return GestureDetector(
onTap: () {
print(index);
_changeImageIndex(index);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.network(
list[index].image,
width: 150,
fit: BoxFit.cover,
),
),
),
);
}
}
I wanted to ask how I can automatically resize the two containers here when it is dragged (see picture). It should work so that I can drag in the middle (where the two meet), and the one then becomes larger or smaller.
I can't find anything helpful on the internet :c
At the moment, both containers are in a row.
My code:
Row(children: [
Container(
color: Colors.red,
width: MediaQuery.of(context).size.width * .5,
height: double.infinity),
Container(
color: Colors.blue,
width: MediaQuery.of(context).size.width * .5,
height: double.infinity)
])
Can someone here maybe help me?
Try this widget
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double? leftPart;
#override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(builder: (context, constraints) {
leftPart ??= constraints.maxWidth / 2; // if you need initially half
return GestureDetector(
onPanUpdate: (details) {
double tapPos = details.globalPosition.dx;
const double softPX = 20; //user flexibility
if (tapPos - 20 < leftPart! && tapPos + 20 > leftPart!) {
leftPart = details.globalPosition.dx;
setState(() {});
}
},
child: Row(
children: [
Expanded(
child: Container(
color: Colors.red,
width: leftPart,
height: double.infinity),
),
Container(
color: Colors.blue,
width: constraints.maxWidth - (leftPart ?? 0.0),
height: double.infinity)
],
),
);
}),
);
}
}
If you like to handle overflow on drag, use
color: Colors.blue,
width: constraints.maxWidth - (leftPart ?? 0.0) >
constraints.maxWidth
? constraints.maxWidth
: constraints.maxWidth - (leftPart ?? 0.0) < 0
? 0
: constraints.maxWidth - (leftPart ?? 0.0),
I want a group of widgets to be placed proportionally above a background Widget (Image widget).
I tried the following.
Created a Widget array and added it to Stack Used LayoutBuilder to build child widgets nad positioned them using Positioned widget.
But it didn't work due to Incorrect use of ParentDataWidget. error.
Widget build(BuildContext context) {
final _widgetStack = <Widget>[];
_widgetStack.add(Container(
child: Image.network(
'https://nikhileshwar96.github.io/Showcase/blue.jpg'),
color: Color.fromARGB(255, 255, 255, 255),
));
for (int _i = 1; _i < 5; _i++) {
_widgetStack.add(LayoutBuilder(
builder: (context, constrain) => Positioned(
top: constrain.maxWidth * 0.5,
left: constrain.maxHeight * 0.5,
width: constrain.maxWidth * 0.25,
height: constrain.maxHeight * 0.25,
child: Container(
child: Text('HiTHERE'),
color: Color.fromARGB(255, 255, 0, 0),
),
)));
}
return Container(
child: Scaffold(
appBar: AppBar(
title: Text("Hi there"),
),
body:
Container(
height: 500,
child: Stack(
children: _widgetStack,
),
)
),
);
}
try:
child: CustomMultiChildLayout(
delegate: FooDelegate([
Rect.fromLTWH(0, 0, 1, 1),
Rect.fromLTWH(0.1, 0.1, 0.2, 0.05),
Rect.fromLTWH(0.5, 0.1, 0.2, 0.05),
]),
children: [
LayoutId(id: 0, child: Container(color: Colors.grey)),
LayoutId(id: 1, child: Material(color: Colors.red, child: InkWell(onTap: () {},))),
LayoutId(id: 2, child: Material(color: Colors.blue, child: InkWell(onTap: () {},))),
],
)
the custom MultiChildLayoutDelegate could look like (however it is completely up to you how you implement the delegate):
class FooDelegate extends MultiChildLayoutDelegate {
final List<Rect> rects;
FooDelegate(this.rects);
#override
void performLayout(Size size) {
var id = 0;
for (var rect in rects) {
var childRect = Rect.fromLTWH(
size.width * rect.left,
size.height * rect.top,
size.width * rect.width,
size.height * rect.height);
print('$childRect $size');
positionChild(id, childRect.topLeft);
layoutChild(id, BoxConstraints.tight(childRect.size));
id++;
}
}
#override
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => true;
}
According to the docs here:
A Positioned widget must be a descendant of a Stack, and the path from
the Positioned widget to its enclosing Stack must contain only
StatelessWidgets or StatefulWidgets (not other kinds of widgets, like
RenderObjectWidgets).
To solve your issue:
You can use the Transform widget's Transform.translate constructor instead of the Positioned widget. So your code looks like this:
for (int _i = 1; _i < 5; _i++) {
_widgetStack.add(LayoutBuilder(
builder: (context, constrain) {
return Transform.translate(
offset: Offset(constrain.maxWidth * 0.5, constrain.maxHeight * 0.5),
child: Container(
width: constrain.maxWidth * 0.25,
height: constrain.maxHeight * 0.25,
child: Text('HiTHERE'),
color: Color.fromARGB(255, 255, 0, 0),
),
);
}));
}
My end goal is to achieve somethinng like this:
As you can see there's the drag handle is required to rotate this image.
I have a following code:
import 'package:flutter/material.dart';
double ballRadius = 7.5;
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double _angle = 0.0;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SafeArea(
child: Stack(
children: [
Positioned(
top: 100,
left: 100,
child: Transform.rotate(
angle: _angle,
child: Column(
children: [
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(30),
),
child: LayoutBuilder(
builder: (context, constraints) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanUpdate: (DragUpdateDetails details) {
Offset centerOfGestureDetector = Offset(
constraints.maxWidth / 2,
constraints.maxHeight / 2,
);
final touchPositionFromCenter =
details.localPosition -
centerOfGestureDetector;
print(touchPositionFromCenter.direction);
setState(() {
_angle = touchPositionFromCenter.direction;
});
},
);
},
),
),
Container(
height: 30,
width: 5,
color: Colors.black,
),
Container(
height: 200,
width: 200,
color: Colors.red,
),
],
),
),
)
],
),
),
),
);
}
}
It is working. But sometimes it's too fast or too slow. Please help me fix this issue.
I made a few modifications to the code, notably
Treating the "real" centerOfGestureDetector as the center of all the items you would like to rotate
Determining and tracking the change in angle with the onPanStart,onPanEnd and onPanUpdate methods
import 'package:flutter/material.dart';
double ballRadius = 7.5;
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double _angle = 0.0;
double _oldAngle = 0.0;
double _angleDelta = 0.0;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SafeArea(
child: Stack(
children: [
Positioned(
top: 100,
left: 100,
child: Transform.rotate(
angle: _angle,
child: Column(
children: [
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(30),
),
child: LayoutBuilder(
builder: (context, constraints) {
// Offset centerOfGestureDetector = Offset(
// constraints.maxWidth / 2, constraints.maxHeight / 2);
/**
* using center of positioned element instead to better fit the
* mental map of the user rotating object.
* (height = container height (30) + container height (30) + container height (200)) / 2
*/
Offset centerOfGestureDetector =
Offset(constraints.maxWidth / 2, 130);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (details) {
final touchPositionFromCenter =
details.localPosition -
centerOfGestureDetector;
_angleDelta = _oldAngle -
touchPositionFromCenter.direction;
},
onPanEnd: (details) {
setState(
() {
_oldAngle = _angle;
},
);
},
onPanUpdate: (details) {
final touchPositionFromCenter =
details.localPosition -
centerOfGestureDetector;
setState(
() {
_angle = touchPositionFromCenter.direction +
_angleDelta;
},
);
},
);
},
),
),
Container(
height: 30,
width: 5,
color: Colors.black,
),
Container(
height: 200,
width: 200,
color: Colors.red,
),
],
),
),
)
],
),
),
),
);
}
}