How to center a `Positioned` at a specific coordinate? - flutter

I'd like to use Positioned to freely place widgets on a Stack. It looks like by default Positioned only offers to place its child using either left, right, top, or bottom. This has the behavior to align one of the boundaries to the given coordinate. What I'd like to achieve is to place the child at a certain x/y coordinate, but center it on it.
An example:
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Some App",
theme: ThemeData(
primarySwatch: Colors.green,
),
home: Scaffold(
body: Stack(children: [
Positioned(
left: 100,
top: 100,
child: SomeChild(text: "Some child"),
),
Positioned(
left: 100,
top: 100,
child: Container(width: 5, height: 5, color: Colors.red.shade900),
),
Positioned(
left: 100,
top: 150,
child: SomeChild(text: "Some child with longer text"),
),
Positioned(
left: 100,
top: 150,
child: Container(width: 5, height: 5, color: Colors.red.shade900),
),
]),
),
);
}
}
class SomeChild extends StatelessWidget {
final String text;
const SomeChild({Key? key, required this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text(text);
}
}
Results in:
Example on Dartpad
Basically I'd like the children to center on the small debug gizmos.
Note: I'm using SomeChild as a placeholder for an arbitrary child whose size I neither know nor control explicitly.
Naive attempts at solving the problem where using a Center to wrap the child, but that has no effect. I also tried to move the Positioned into the children, doing some manual size determination within the children itself, so that they can shift their top and right coordiate by half their size. But that is not only awkward, and has to be implemented for every children manually, I also had problems making it work in the first place, because Stack complained that its children are no longer either a Positioned/Aligned/Container.
Is there an elegant way to center the child of a Positioned generically?
I think this question is different from that one, because there the goal is to center a child w.r.t. the Stack itself, not a certain coordinate.

If I understood correctly, you need FractionalTranslation
import "package:flutter/material.dart";
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Some App",
theme: ThemeData(
primarySwatch: Colors.green,
),
home: Scaffold(
body: Stack(children: [
Positioned(
left: 100,
top: 100,
child: FractionalTranslation(
translation: Offset(-0.5, -0.5),
child: SomeChild(text: "Some child"),
),
),
Positioned(
left: 100,
top: 100,
child: Container(width: 5, height: 5, color: Colors.red.shade900),
),
Positioned(
left: 100,
top: 150,
child: FractionalTranslation(
translation: Offset(-0.5, -0.5),
child: SomeChild(text: "Some child with longer text"),
),
),
Positioned(
left: 100,
top: 150,
child: Container(width: 5, height: 5, color: Colors.red.shade900),
),
]),
),
);
}
}
class SomeChild extends StatelessWidget {
final String text;
const SomeChild({Key? key, required this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text(text);
}
}

You can calculate the left or x value and top or y values by subtracting the width and height from the coordinates:
Size widgetSize = Size(200,100); // the size of the widget you want to position
Offset position = Offset(300,400); //the position of the widget on the screen
.
.
.
Positioned(
left: position.dx - widgetSize.width/2,
top: position.dy - widgetSize.height/2,
child: ...
),
For this to work you predefine the child widget's size with the widgetSize.
Then you can put SomeChild inside a Container with that widgetSize and center it:
Positioned(
left: position.dx - widgetSize.width/2,
top: position.dy - widgetSize.height/2,
child: Container(
width: widgetSize.width,
height: widgetSize.height,
child: Center(
child: SomeWidget(),
),
),
),
Note that widgetSize should be greater than the size of someWidget.
You could make widgetSize gigantic in order to fit any someWidget.

May try this out. You can replace Positioned.fill with Positioned to suit your needs. Hope this help you out :D
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return MaterialApp(
title: "Some App",
theme: ThemeData(
primarySwatch: Colors.green,
),
home: Scaffold(
body: Stack(children: [
// try replace Positioned.fill > Positioned
Positioned.fill(
left: 100,
top: 100,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(width: 5, height: 5, color: Colors.blue),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: SomeChild(text: "THIS IS A VERY LONG LONG TEXT, NARUHODO!!!! KJASNDKJASNDAJKNSDKJANSJKNJKANDJS"),
),
],
),
],
),
),
Positioned(
left: 100,
top: 150,
child: SomeChild(text: "Some child with longer text"),
),
Positioned(
left: 100,
top: 150,
child: Container(width: 5, height: 5, color: Colors.red.shade900),
),
]),
),
);
}
}

Row and Column widgets has axis-alignment parameter.. you can easly center a view by using them

Related

LayoutBuilder and Postioned Widget in Flutter does not enable positioningen in Stack

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."""),
))
],
));
}
}

Widget taking width of grandparent

The CircularProgressIndicator widget is taking width of its grandparent widget, the SizedBox of width 200. I'm expecting the dimension to be 10x10, but the dimensions are 200x10. I get the same behavior if the innermost widget is a box drawn with a Container and BoxDecoration.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Color.fromARGB(255, 18, 32, 47),
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const SizedBox(
width: 200,
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator()));
}
}
This is a simplified version of my app, and I need the outer SizedBox.
There are some limitation on Widget constrains.
If a child wants a different size from its parent and the parent doesn’t have enough information to align it, then the child’s size might be ignored. Be specific when defining alignment
A widget can decide its own size only within the constraints given to it by its parent. This means a widget usually can’t have any size it wants.
A widget can’t know and doesn’t decide its own position in the screen, since it’s the widget’s parent who decides the position of the widget.
readmore here: https://docs.flutter.dev/development/ui/layout/constraints
thats why, the second SizedBox size was ignored, because its doesnt
know the position and aligment. and because of that, the
CircularProgressIndicator() take the grandparent size.
Solution:
set the alignment to the SizedBox.
const SizedBox(
width: 200,
child: Align(alignment:Alignment.center,
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator())));
The short answer and solution is just wrap your inner SizedBox with Center. It will help.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const SizedBox(
width: 200,
child: Center(
child: SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(),
),
),
);
}
}
I recommend you read this article which describes how sizes and constraints work in Flutter. https://docs.flutter.dev/development/ui/layout/constraints
You can use Center widget to centralize the child widget
SizedBox(
width: 200,
child: Center(
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator()),
));
Or you can use Align widget to re-position the child in the available area
SizedBox(
width: 200,
child: Align(
alignment: Alignment.topRight,
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator()),
));
Remove the outer SizedBox which is having the width of 200.
or use like this.
const SizedBox(
width: 200,
child: Center(
child: SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(),
),
),
);

How to remove padding from some children in a Column?

Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
// ... some widgets
Padding(
padding: const EdgeInsets.all(-20.0), // Error: How to do something like this?
child: FooWidget()
),
// ... more widgets
BarWidget(), // Remove padding from here also
// ... and some more widgets
],
),
)
I'm providing a padding of 20 to my Column, but I want to remove this padding from some of its children, like FooWidget, BarWidget, etc. How can I do that?
Note: I'm not looking for workarounds like provide the padding to other widgets instead of the root Column or wrap those widgets in another Column and provide that column a padding, etc.
you can apply transformation to the widgets that you want to remove the padding for, for example:
Container(
transform: Matrix4.translationValues(-20.0, 0, 0.0), //here
child: FooWidget()),
This working solution uses UnconstrainedBox that only takes away the left side and right side of padding. You might do the calculation of overflowWidth first when screenWidth is not feasible to use.
In addition, this comes up with a RenderConstraintsTransformBox overflowed exception that will be gone away in app release version, e.g. flutter build appbundle for android app.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: UnboundedWidget(),
);
}
}
class UnboundedWidget extends StatelessWidget {
const UnboundedWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final double overflowWidth = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(
title: const Text('Unbounded demo'),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
UnconstrainedBox(
child: Container(
color: Colors.red,
width: overflowWidth,
child: const Text('123'),
),
),
],
)),
);
}
}
There is no such thing as negative margins in flutter.
You can try workarounds with transforms on x, y, z axis as transform property on Container widget.
Or try with SizedBox which ignores parent padding.
Here is a similar example that should work:
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Container(
width: double.infinity, height: 20, color: Colors.green),
// This child ignores parent padding.
SizedBox(
width: MediaQuery.of(context).size.width,
height: 20,
child: Expanded(
child: OverflowBox(
maxWidth: MediaQuery.of(context).size.width,
child: Container(
width: double.infinity,
height: 20,
color: Colors.red)),
),
),
Container(
width: double.infinity,
height: 20,
color: Colors.blue),
],
),
),
Use Stack widget with Positioned widget Positioned(left: -20, child: widget)
on the other hand, for padding less.
you can create custom 2 widget name as:
paddingLessWidget(child: your widget)
paddingWithWidget(child: your widget)
then use this into column() widget.
Remove padding from column's parents.
use as:
Column(
children:[
paddingLessWidget(child: your widget),
paddingWithWidget(child: your widget)
]
),

Specific min and max size for expanded widgets in Column

I have a Column with a set of Expanded widgets.
Is there a way to control the range in which they expand? I want one widget to expand only to a certain size and make the rest available to other widgets.
EDIT:
Because I got two probably misleading answers, I’d like to clarify. I want something like this:
Expanded(flex: 1, minSize: 50, maxSize: 200, child: ...)
That means that this expanded widget takes a flex of 1, but should never be smaller than 50 and bigger than 200.
When using ConstrainedBox in Rows my minWidth is ignored and the maxWidth is used as a fixed size.
You are looking for ConstrainedBox.
You can create a List of Widgets with both ConstrainedBox and Expanded, as following:
Row(
children: [
ConstrainedBox(
child: Container(color: Colors.red),
constraints: BoxConstraints(
minWidth: 50,
maxWidth: 100,
),
),
Expanded(
child: Container(color: Colors.green),
),
Expanded(
child: Container(color: Colors.blue),
),
],
),
As far as I know, there's no elegant pre-built way in Flutter to do this.
The answer by #HugoPassos is only partially complete. A ConstrainedBox will not change its size unless its content changes size. I believe what you're looking for is for the box to be say 1 / 4 of the width of row if 1/4 of the row is greater than the min and higher than the max.
Here's a working main.dart that get's the job done with width in a row, though you could just as easily use height in a column:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({required this.title});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: LayoutBuilder(builder: (context, constraints) {
return Center(
child: Row(
children: [
ConstrainedWidthFlexible(
minWidth: 50,
maxWidth: 200,
flex: 1,
flexSum: 4,
outerConstraints: constraints,
child: SizeLogger(
child: Container(
color: Colors.red,
width: Size.infinite.width,
height: Size.infinite.height,
child: Text('click me to log my width')),
),
),
Flexible(
flex: 1,
fit: FlexFit.tight,
child: Container(color: Colors.green),
),
Flexible(
flex: 2,
fit: FlexFit.tight,
child: Container(color: Colors.blue),
),
],
));
}));
}
}
class SizeLogger extends StatelessWidget {
final Widget child;
SizeLogger({required this.child});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => {print('context.size!.width ${context.size!.width}')},
child: child);
}
}
class ConstrainedWidthFlexible extends StatelessWidget {
final double minWidth;
final double maxWidth;
final int flex;
final int flexSum;
final Widget child;
final BoxConstraints outerConstraints;
ConstrainedWidthFlexible(
{required this.minWidth,
required this.maxWidth,
required this.flex,
required this.flexSum,
required this.outerConstraints,
required this.child});
#override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(
minWidth: minWidth,
maxWidth: maxWidth,
),
child: Container(
width: _getWidth(outerConstraints.maxWidth),
child: child,
),
);
}
double _getWidth(double outerContainerWidth) {
return outerContainerWidth * flex / flexSum;
}
}
In short: there is no simple answer without calulating the size.
First you need to know: Widget with Size dominate the avialable size in Row/Column, then Flexiable/Expanded share the remaining space.
Column(
children:[
Flexiable(...
Expanded(...
SizedBox(... // <- dominate the avialable size first
]
)
And the parent widget dominate the size of the child widget:
Column(
children:[
Flexiable(flex: 1),
Flexiable(
flex: 1,
child: SizedBox(... // size can't be larger than 1/2
]
)
It is the choise problem if the size exceed or insufficient. I can show some simple examples below:
(BTW: I replace ConstraintedBox with SizedBox because we only use maxWidth/maxHeight. check Understanding constraints)
Flex with max size
In this case is simple and can use only Flexible + SizedBox
Row(
children: [
Flexible(flex: 1, child: _textWidget('Flex:1')),
Flexible(
flex: 1,
child: SizedBox(
width: 300,
child: _textWidget('Flex: 1, max: 300'),
),
),
],
),
Flex with min/max size
For the case need the total size(from LayoutBuilder) and the percentage of the widget size.
LayoutBuilder(
builder: (context, constraint) {
final maxWidth = constraint.maxWidth;
return Row(
children: [
Flexible(flex: 1, child: _textWidget('Flex:1')),
SizedBox(
width: (maxWidth / 3).clamp(200, 300),
child: _textWidget('Flex:1, min: 200, max: 300'),
),
SizedBox(
width: (maxWidth / 3).clamp(200, 300),
child: _textWidget('Flex:1, min: 200, max: 300'),
),
],
);
}
)
Code Example
https://dartpad.dev/?id=f098f9764acda1bcc58017aa0bc0ec09
Yes! There is a way to control maxHeight and maxWidth inside a Row or Column (unbounded Widgets). You could use the Widget LimitedBox in which your maxHeight and maxWidth parameters only works inside unbounded Widgets.
https://api.flutter.dev/flutter/widgets/LimitedBox-class.html
Column(
children: [
LimitedBox(
maxHeight: 200,
maxWidth: 200,
child: Container(),
)
],
),
This worked for me. Please, Check it out.
Expanded(
child: Align(
alignment: Alignment.topCenter,
child: Container(
child: ConstrainedBox(
constraints:
BoxConstraints(maxHeight: 500),
child: Container(
child: DesiredWidget(),
),
),
),
),
)
Instead of directly expanding the desired widget, you should expand and align a container, then set the constrainedbox as a child of the container and then insert the desired widget as a child of the constrainedbox.
This way i managed to render the widget precisely as big as it needs to be, but never exceeding 500 height.
You can use constraint box to use the range of min and max width like below:
Row(
children: <Widget>[
Text("Text 1"),
ConstrainedBox(
constraints: BoxConstraints(maxHeight: 30, maxWidth: 40, minWidth: 30),
),
Text("Text 2")
],
)

How can i move a widget left and right until the available space is filled?

I'm a beginner and still learning flutter, please forgive me if am getting something wrong here.
So i have a Stack inside of which i have 2 Widgets. Widget1 and Widget2.
I want to center the Widget B right below Widget A.
NOTE: Widget 2 is larger than Widget1.
class MyWidgets extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Widget1,
Positioned(
top: widget1PositionFromTop + widget1Size,
left: widget1PositionFromLeft,
child: FractionalTranslation(
translation: Offset(-0.5, 0.0),
child: Widget2,
),
)
],
);
}
}
The Problem is, if the Widget1's position from left is 0, or less than half the size of Widget2, the Widget2 is overflowed, and only half or a part of the Widget2 can be seen.
I want to know how can i only translate its position as long as there is space left, i mean if there is space left only for 0.3 of Widget2 then it will move only -0.3 not -0.5..
Thank you.
If you want this using Stack then
class SO extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
Container(width: 200, height: 200, color: Colors.red),
Container(width: 100, height: 100, color: Colors.blue),
],
),
);
}
}
which gives
Maybe you could use Column like
class SO extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(width: 200, height: 200, color: Colors.red),
Container(width: 100, height: 100, color: Colors.blue),
],
),
);
}
}
with the end result as