How to place Row elements' centers evenly (not using Expanded-s) - flutter

I tried my best to summarize what I want in the title, but here is a more detailed explanation:
Widgets' centers should be placed evenly throughout the row.
A larger widget should be able to extend into the "personal space" of a smaller one.
When it would overflow, overflow should start on the bigger items, cutting into the smaller ones later.
Here's an illustration:
Behavior with Expandeds:
Everything fits
Longest item is overflowing - The problem with this is that the longest text would easily fit if it could extend into the boundary of the shorter texts next to it.
How it should work:
Please notice: The boxes are not Expanded, but the centers of them are evenly placed (not by themselves, but as if they were the centers of Expandeds). As opposed to just laying them out with usual MainAxisAlignment options; neither of those ensures that the centers are evenly placed, a longer text can push the shorter ones to one side. (Illustraion of what I don't want)
Everything fits
Longest text extends into the area of the shorter ones
When two boxes would touch, overflow the longer one
Extreme case - default to even sizes
This may be asking a lot, but I think there should be a way of achieving these, what's more, I think this should be the default way rows with texts work.
Any help on any sub-request is much appreciated.
Edit: I understand that everything I described is pretty complicated when put together. I would be happy to know a way to just simply place center lines evenly, with different width boxes (and no, spaceEvenly doesn't do this). Overflowing behavior can be a different question.

Have you tried mainAxisAlignment: MainAxisAlignment.spaceEvenly? Then you can play with the padding. It seems that you would also like to make your UI responsive, try the LayoutBuilder class. The documentation can be found here https://flutter.dev/docs/development/ui/layout/responsive .

First of all use 3 Expanded() for taking spaces for 3 Text() Widgets after that you can align your text at center using a container. Hope this solution will meet your requirement.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Container(
alignment: Alignment.center,
child: Text('Lorem', style: TextStyle(fontSize: 24),),
),
),
Expanded(
child: Container(
alignment: Alignment.center,
child: Text('Lorem ispum', style: TextStyle(fontSize: 24),),
),
),
Expanded(
child: Container(
alignment: Alignment.center,
child: Text('Lorem', style: TextStyle(fontSize: 24),),
),
),
]
);
}
}

Related

How can Flutter Widgets be sized relatively to each other?

I'm a couple days into learning Flutter, and I keep running into a situation where I want to scale a collection of widgets (aka a Row, Column, Stack, etc) down when the screen shrinks, akin to what Flexible() does. Except unlike Flexible(), I want all children to scale down when the container shrinks, not just the largest elements. Consider this example of a Column() (green) with two children: a red child matching its parent's width, and a blue child with half the parent's width:
In the above case, if the green Column() were to be constrained such that its width was less, you would end up with something like such:
However, what I am wanting is for each of the child elements (red/blue) to scale their width/height relative to each other, like this:
How can I accomplish this?
In the specific Column() case I illustrated above, my workaround was to add a Row() containing the blue widget as well as a Padding(), both with equal flex properties, so that when the Column() shrunk the blue element scaled correctly. I feel like this is a hack though, and wouldn't be as feasible if I had many elements with differing widths/alignments. It would be a nightmare to add one Padding() for left/right aligned elements and two for centered ones. Is there a better way?
I'd also like to know of a similar solution for using Stack()s. Using the Positional() element seems to set the width/height in a way that the Stack() crops any overflow. It'd be nice to have a Stack() that scales all its children the same, just like if it was a single image, and similar to my Column() example above.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatelessWidget(),
),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({super.key});
#override
Widget build(BuildContext context) {
return SizedBox.expand(
child: FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 0.5,
alignment: FractionalOffset.center,
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue,
width: 4,
),
),
),
),
);
}
}
FractionallySizedBox Widget is a widget that sizes its child to a fraction of the total available space. To make it easy to understand how FractionallySizedBox works
You can have a look at FractionallySizedBox class, you can specify the widthFactor and heightFactor e.g. 0.5 so that it is always half the size of the parent container.
Example:
Flexible(
child: FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 0.5,
alignment: FractionalOffset.centerLeft,
child: Container(
color: Colors.blue),
),
),

Auto-scaling image in Row to match other Widgets

Within my layout, I have a Row. On the left side of that Row, I have a Column containing two Text objects of differing font sizes. On the right side of the Row, I have an Image. I want the Image to scale itself uniformly to have an equal height to the intrinsic height of the Column on the left side of the Row. I want this to work whether the Image's pixel height is lesser or greater than the intrinsic height of the Column, regardless of the font sizes used in the Text widgets and the text scaling factor used by the operating system. (That is, I don't want to hard-code the Image size.)
How can I make this work? I suspect I may need to cloak the Image in some widget to make its parent think it has an intrinsic height of 0 because Images have intrinsic heights equal to their pixel heights.
Here is some broken code I tried in DartPad:
(Note: the network image is the same image used in the documentation # https://flutter.dev/docs/cookbook/images/network-image . I don't have any rights to it.)
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: IntrinsicHeight(
child: Container(
color: Colors.green,
width: 600,
height: 600,
child: Row(crossAxisAlignment: CrossAxisAlignment.end, children: [
Column(children: [
Text("Larger", style: TextStyle(fontSize: 36)),
Text("Smaller", style: TextStyle(fontSize: 18))
]),
Expanded(
child: Image.network(
"https://picsum.photos/250?image=9",
alignment: Alignment.bottomRight,
fit: BoxFit.scaleDown,
))
]),
),
)));
}
}

Flexible widgets not rendering inside Column

I need to word-wrap some text and have been reading how Flexible and Expanded help make that happen. My problem is the rendering breaks when I put the resulting widgets in a Column. I read that you need to wrap parent columns in Expanded widgets, too, but that gave the same broken result?
Here's a text version of what I'm after
The DartPad code below produces that, but when the commented Column is uncommented, no text is rendered?
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeDat.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return
// Column( children: [ // <- Uncommenting Column breaks rendering
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('[Avatar]'),
SizedBox(width: 10),
Expanded(
child: Column(mainAxisAlignment: MainAxisAlignment.start, children: [
Flexible(
child: Text(
'Really Really Really Really Really Really Really Really '
'Really Really Really Really Really Really Long Heading'),
),
Flexible(
child: Text(
'Really Really Really Really Really Really Really Really '
'Really Really Really Really Really Really Long Text'),
),
]),
),
SizedBox(width: 10),
Text('[Button]'),
// ]),
],
);
}
}
You can solve the issue just by wrapping the Row with an Expanded.
try this
Flexible(fit: FlexFit.loose,child:.....)

Flutter baseline text alignment with Wrap class

Using the Row class, I can make the text baseline aligned with this property:
crossAxisAlignment: CrossAxisAlignment.baseline,
But this property seems not to be available on the Wrap class.
The benefit of using Wrap instead of Row is that it allows having multiline text. While the Row class force the content to stay into one line.
How can I keep the benefit of Wrap, while making the text baseline aligned?
I also had this problem. There is an open issue in the Flutter Repo addressing this. While the built-in solution is not implemented yet, I did come up with a slightly-hacky workaround.
Basically, depending on your font and your needs...
Set your Wrap() to have crossAxisAlignment: WrapCrossAlignment.end,. Then you can make one of the children of your Wrap() a Column() that has a SizedBox() as the lowest child, with the height that you need to make it appear like your texts are using the does-not-exist WrapCrossAlignment.baseLine
Example of the problem:
Example of the solution as outlined above (I did point out that it's kind of hacky):
The Code:
class WrapBaselineHackWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(border: Border.all(color: Colors.red)),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.end,
spacing: 8,
children: [
Text(
'\$50,000.00',
style: TextStyle(fontSize: 24),
),
Column(
children: [
Text('Is a lot of money'),
//this is needed because Wrap does not have WrapCrossAlignment.baseLine
const SizedBox(height: 3),
],
),
],
),
);
}
}
Keep using Row and simply wrap Text widget with Expanded or Flexible widget.

How to make text as big as the width allows in flutter

I have a Container where I need to show a barcode and I'd love to have the barcode to be as wide as possible on the screen.
For now I set the font size at a reasonable size that suits all devices, but it's only temporary of course.
How can I solve this? This is the code I am using for building the Widget.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_title),
),
body: Container(
padding: const EdgeInsets.all(12.0),
child: Column(
children: <Widget>[
SizedBox(
width: double.infinity,
child: Text(_barcode, style: TextStyle(fontFamily: 'Code128', fontSize: 90.0))
),
Text(_barcode, style: TextStyle(fontSize: 40.0))
]
),
)
);
}
I believe what you're looking for is FittedBox.
BoxFit applies whichever 'fit' you want to stretch/scale the child to fit in the box. It doesn't perform a pure 'stretch' on the text but rather the space it should take up. You shouldn't specify the text's size at the same time.
That looks like this:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
MyAppState createState() {
return new MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Center(
child: Container(
color: Colors.blue,
width: 300.0,
height: 200.0,
child: FittedBox(
fit: BoxFit.contain,
child: Text("Whee"),
),
),
),
),
),
);
}
}
If you're wanting to actually 'stretch' the text (i.e. make the actual characters wider or taller) you'll have to do something a bit more custom.
If that's the case, look at CustomPaint, CustomPainter, TextPainter, and the Canvas translate & scale options. Basically, you would need to create a class extending CustomPainter in which you created a TextPainter, laid it out at a particular size, painted it onto the canvas, and then scaled it to fit the actual size of the CustomPainter (or do you scale the canvas first - I forget...). Then you'd pass an instance of that class to CustomPaint.
FittedBox is what worked for me but there is a twist. I also had to style my fontSize to a big number for it to work. Hope this helps.
child: FittedBox(
fit: BoxFit.fitHeight,
child: Text(
"Your Expanded Text :)",
style: TextStyle(fontSize: 400.0),
),
),
The code sample in the question has a Text widget as one of the children: of a Column widget. The width of the Text parent is unknown.
So to maximise the width and size of the Text widget in this case, wrap the Text widget in a FittedBox, then an Expanded.
child: Column(children: <Widget>[
Expanded(
child: FittedBox(
fit: BoxFit.contain,
child: Text(
'123',
)),
),
]),
The Text size should also automatically resize correctly even when the device is rotatated, or the screen resized, without overflow issues.
Expanded:
/// A widget that expands a child of a [Row], [Column], or [Flex]
/// so that the child fills the available space.
///
/// Using an [Expanded] widget makes a child of a [Row], [Column], or [Flex]
/// expand to fill the available space along the main axis (e.g., horizontally for
/// a [Row] or vertically for a [Column]). If multiple children are expanded,
/// the available space is divided among them according to the [flex] factor.
from /flutter/packages/flutter/lib/src/widgets/basic.dart
FittedBox:
/// Creates a widget that scales and positions its child within itself according to [fit].
you can use fitted box widget.
FittedBox(child:Text('text sample'));
https://api.flutter.dev/flutter/widgets/FittedBox-class.html
FittedBox would only work if it is provided some constraints, so make sure to provide one, like provide height as shown below:
SizedBox(
height: 400, // 1st set height
child: FittedBox(child: Text("*")), // 2nd wrap in FittedBox
)
Use TextPainter.width and a for loop to find the largest fitting font size (adding +1 is not very efficient, you may want to fine-tune that):
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(
home: MyHomePage(),
theme: ThemeData(platform: TargetPlatform.iOS),
));
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Text autoscale'),
),
body: Padding(
padding: EdgeInsets.all(32.0),
child: Center(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final text = 'Hello World';
final style = TextStyle(fontWeight: FontWeight.bold); // apply your barcode font here
final fontSize = calculateAutoscaleFontSize(text, style, 30.0, constraints.maxWidth);
return Text(
text,
style: style.copyWith(fontSize: fontSize),
maxLines: 1,
);
},
),
),
),
);
}
}
double calculateAutoscaleFontSize(String text, TextStyle style, double startFontSize, double maxWidth) {
final textPainter = TextPainter(textDirection: TextDirection.ltr);
var currentFontSize = startFontSize;
for (var i = 0; i < 100; i++) {
// limit max iterations to 100
final nextFontSize = currentFontSize + 1;
final nextTextStyle = style.copyWith(fontSize: nextFontSize);
textPainter.text = TextSpan(text: text, style: nextTextStyle);
textPainter.layout();
if (textPainter.width >= maxWidth) {
break;
} else {
currentFontSize = nextFontSize;
// continue iteration
}
}
return currentFontSize;
}
Wrap the text within a FittedBox widget, to force the text to be enclosed by a box. The FittedBox's size will depend on it's parent's widget. Within the FittedBox, the Text widget, can simply 'cover' the box, so the text doesn't stretch to fill the available space within the FittedBox. The enum BoxFit.fill, is a way to stretch the text to fit the entire space available within the FittedBox. You can change the dimensions of the box by altering the height and width of the FittedBox's parent, the Container.
Container(
height: _height,
width: _width,
FittedBox(
fit: BoxFit.fill,
child: Text("Whee"),
)
)