How to expand fixed aspect ratio widget in row as much as possible while leaving minimum amount of space for second widget? - flutter

I have two widgets I need to place side by side (will shift to vertical depending on screen size). The first widget has a fixed aspect ratio - this widget should expand as much as possible to fill the space - up until some minimum size constraint for the second widget (pixels preferred, but percentage is workable)
One, will have a fixed aspect ratio of 1 / 1.2 - for the sake of example, we'll keep it simple:
AspectRatio(
aspectRatio: 1 / 1.2,
child: ColoredBox(color: Colors.red),
)
The second widget will be a list of rows of small text items - example implementation with blue for visibility:
ColoredBox(
color: Colors.blue,
child: ListView(
children: [
Row(
children: const [
Expanded(child: Center(child: Text("small"))),
Expanded(child: Center(child: Text("text"))),
]
),
Row(
children: const [
Expanded(child: Center(child: Text("will"))),
Expanded(child: Center(child: Text("be"))),
],
),
Row(
children: const [
Expanded(child: Center(child: Text("here"))),
Expanded(child: Center(child: Text(""))), // avoid unnecessary widget tree structure changes when item is added here?
]
),
]
)
)
So, the first thing I did was just throw them in a Flex widget, wrapped in Flexible:
Flex(
direction: getDirection(),
children: [
Flexible(child: MyFixedAspectRatioWidget()),
Flexible(child: MyStringGridWidget()),
]
)
This works ok for some screen aspect ratios, but:
As the screen width is increased, I get a white bar along the right hand side.
As the width is decreased, I get white bars along the top and bottom
of the fixed aspect ratio widget.
The widgets will always have an equal width.
What I would like to happen:
As the width is increased, the string grid widget expands to fill the remaining space.
As the width is decreased, the string grid decreases in width, while the fixed aspect
ratio widget stays the same size, up until that would mean the string grid
widget would have less than some minimum size, and only then begin
to decrease the size of the aspect ratio widget (white bars on top and bottom).
What I've tried to get this behavior:
Setting a much larger flex for the fixed aspect ratio widget, while using Expanded for the string grid widget.
I was hoping that the large flex would allow it to take up as much size as it wants, while the Expanded, would cause the string grid to fill any remaining space.
At first this seemed promising - the AspectRatio grid had the behavior I wanted, filling all available space while keeping its aspect ratio, and also leaving a minimum amount of space for the string grid! The string grid however, would never expand beyond 1/10th of the width of the fixed aspect ratio widget.
Flex(
direction: getDirection(),
children: [
Flexible(flex: 10, child: MyFixedAspectRatioWidget()),
Expanded(flex: 1, child: MyStringGridWidget()),
]
);
I've tried various other combinations of flex and Expanded, but no luck improving on the initial take. Placing the ListView within Expanded just throws an exception.
Using ConstrainedBox with a very large maxWidth for the aspect ratio widget, and Expanded for the string grid widget seems to work properly when increasing the width; however, it leads to an overflow when decreasing the width, and does not save space for the grid.
I'm sure I could get this working with LayoutBuilder and SizedBox, but I was wondering if there's any built in way to get this kind of behavior.
Full example
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Example App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage()
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}): super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return const Scaffold(
body: BothWidgets()
);
}
}
Axis getDirection() => Axis.horizontal;
class BothWidgets extends StatelessWidget {
const BothWidgets({super.key});
#override
Widget build(BuildContext context) {
return Flex(
direction: getDirection(),
children: [
const Flexible(
flex: 10,
child: AspectRatio(
aspectRatio: 1 / 1.2,
child: ColoredBox(color: Colors.red),
),
),
Expanded(
flex: 1,
child: ColoredBox(
color: Colors.blue,
child: ListView(
children: [
Row(
children: const [
Expanded(child: Center(child: Text("small"))),
Expanded(child: Center(child: Text("text"))),
]
),
Row(
children: const [
Expanded(child: Center(child: Text("will"))),
Expanded(child: Center(child: Text("be"))),
],
),
Row(
children: const [
Expanded(child: Center(child: Text("here"))),
Expanded(child: Center(child: Text(""))), // avoid unnecessary widget tree structure changes when item is added here?
]
),
]
),
)
),
]
);
}
}

Related

flutter image placeholder (FadeInImage) without setting fixed size?

How can I use something like FadeInImage to setup and hold the layout of a page before images have downloaded? As expected, just using Image.network causes the page to jump around once the images load and become visible. I don't have set image sizes (i allow them to resize based on screen/etc) and want to avoid setting a fixed height. The images load and show fine using FadeInImage however the screen still jumps a lot.
#override
Widget build(BuildContext context) {
return
Scaffold(
appBar: AppBar(
title: Text('Welcome!'),
),
drawer: sideDrawer(),
body: new SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
SizedBox(height: 28),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(width: 64),
Flexible( // tried Expanded too
child:
FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: 'https://www.xyzserver.com/images/dummyimage.png',
fit: BoxFit.scaleDown,
),
),
SizedBox(width: 64),
],
),
SizedBox(height: 28),
Text("stuff below the image"),
],
),
)
);
}
When using "Expanded" the image row/area is very tall vertically (the text "stuff below the image" is at the bottom of the page so the page jumps up when the image loads. When using "Flexible" the image row/area is somewhat smaller and the page jumps down when the image loads.
In the image I'm playing around with now, it's a horizontal image that is larger than the available screen space, so it will get scaled down. I guess I was thinking that since flutter can calculate the max width of what's available to the expanded/flexible, it should be able to calculate the height, but as I write this I'm thinking that's impossible since it doesn't know the height/width ratio so it can't predict the height.
How can I set this up so that images can be resized and show correctly and the page doesn't jump around? I can't imagine using fixed height/width settings is the way to go. Maybe my approach to images is all wrong and I should always use a set height/width although that can be rather difficult when people are allowed to upload their own images/etc.
Thanks!
Check this one
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Uint8List? imageData;
Future<Uint8List> dosometinhdd() async {
return (await rootBundle.load('assets/images/a.png')).buffer.asUint8List();
}
#override
void initState() {
dosometinhdd().then((value) {
setState(() {
imageData = value;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Welcome!'),
),
body: new SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height * 0.8,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
imageData != null
? Expanded(
child: FadeInImage.memoryNetwork(
placeholder: imageData!,
image:
'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/1200px-Image_created_with_a_mobile_phone.png',
fit: BoxFit.scaleDown,
),
)
: Container(),
Text("stuff below the image"),
],
),
),
));
}

Alignment properties doesn't work so I use empty expanded widget

I want make text to the right, what am I doing so far
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _judul = 'Private Chat';
static var _warnaTema = Colors.pink[100];
Widget _dummyExpanded() {
return Expanded(
child: Container(), //contain empty data,
); //just for fill remaining space
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _judul,
theme: ThemeData(
primaryColor: _warnaTema,
),
home: Column(
children: [
Container(
child: Row(
children: [
this._dummyExpanded(),
Text('this is real data 1'),
],
),
),
Container(
child: Row(
children: [
this._dummyExpanded(),
Text('this is real data 2'),
],
),
)
],
),
);
}
}
The layout output is what I expected (the text is in right), however there's unneeded code.
As you see I use unnecessary method _dummyExpanded to fill
available space which it's just expanded with empty container. Of course it will be hard to read since there are many nested row and column, how I remove that unneeded method without loss layout output what I expect?
I believe I should have use alignment but I don't know how to
Method 1.
Container(
child: Row(
mainAxisAlignment:MainAxisAlignment.end,
children: [
Text('this is real data 2'),
],
),
)
Method 2.
I prefer this method, You can wrap text with this method.
You can also use Flexible widget instead of Expanded
Container(
child: Row(
children: [
Expanded(child: Text('this is real data 2', textAlign:TextAlign.end )),
],
),
)
Method 3.
If you have only Text widgets as children of Column widget then you can set crossAxisAlignment: CrossAxisAlignment.end, for your parent Column widget.

Flutter - How can I make a square widget take up its maximum possible space in a row?

I have a CustomPaint that needs to be a 1:1 square, and I need to put this in a Row. The horizontal and vertical space available can vary, so I need both the length and width of the square to be the smallest maximum constraint.
How can I achieve this behaviour?
I've tried using LayoutBuilder for this:
Row(
children: [
...,
LayoutBuilder(
builder: (context, constraints) {
final size = min(constraints.maxWidth, constraints.maxHeight);
return SizedBox(
width: size,
height: Size,
child: CustomPaint(...),
),
},
),
]
),
This, however, doesn't work, because Row provides unbounded horizontal constraints (maxWidth == double.infinity). Using the FittedBox widget also fails for the same reason.
Wrapping the LayoutBuilder in an Expanded widget provides it with a bounded maximum width, but I need to have another widget next to it in the Row, so this is not appropriate. Flexible behaves like Expanded in this case, as well.
I think you can get what you want from the AspectRatio widget... if you tell it 1:1, then it tries to make a square unless completely not possible.
Please try the code below, using Align widget restrains the widget to a square :
import 'package:flutter/material.dart';
import 'dart:math';
final Color darkBlue = const 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: MyWidget(),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
flex: 1,
child: Container(),
),
Expanded(
flex: 2,
child: LayoutBuilder(
builder: (context, constraints) {
final size = min(constraints.maxWidth, constraints.maxHeight);
return Align(
alignment: Alignment.centerRight,
child: Container(
height: size,
width: size,
color: Colors.amber,
),
);
},
),
),
// Expanded(
// flex: 1,
// child: Container(),
// ),
],
);
}
}
I ended up working this issue by moving the responsibility of keeping the widget square up the tree. Widgets that were using the square widget knew more about what other things they were showing and were more capable of giving it the right constraints.

how to widget height fill to parent (or get parent size) in Flutter?

I am new to flutter, and I having trouble with my ListView. My ListView contains many items, the item of ListView includes question content and some comments of users.
The item height is automatically decided by its children height, also the first children of the item (deepOrange Container below) height is dependent to parent item height (is't the item of ListView), its height must be fill to parent item.
The items size of ListView dependent to its children items size,
also the first widget (Container below) in the item is dependent to parent item too.
How i can do this?
I'm very very sorry, English is not my primate language, my English is very poor. Here is my code:
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class MainPage extends StatefulWidget {
const MainPage({Key key, this.title}) : super(key: key);
final String title;
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
var items = List.generate(10, (index) => index);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: items.map((item) => Row(
/*I can't compute the real rendered height of the item by it's children content, because it's possible contain many replay message, different font size etc.*/
children: <Widget>[
Container(
width: 5.0,
/**
* I want to fill the height to parent widget of the container,
* (I want to make an timeline widget, it's should max height to its parent.)
* but i can't get parent items size, or double.infinity etc,
*/
height: 80.0, // How to fill the height to parent of the property? or how to get parent item size (or actually rendered height)?
color: Colors.deepOrange,
),
SizedBox(width: 10.0,),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("there is long comment of users, there is long comment of users, there is long comment of users, there is long comment of users, there is long comment of users, $item"),
/*
Also here is a lists of replay message by another user.
* */
Column(children: <Widget>[
Text("replay 1"),
Text("replay 2"),
Text("replay 3"),
Text("replay N"),
],),
Row(
children: <Widget>[
IconButton(icon: Icon(Icons.favorite)),
IconButton(icon: Icon(Icons.delete)),
IconButton(icon: Icon(Icons.message)),
],
)
],
),
),
],
),).toList(),
),
);
}
}
I simply want to fit that purple Container's height to the parent container.
Thank you in advance.
Wrap your list in a container. Then set the height to the screen size. You can get that through the MediaQuery.
var screenSize = MediaQuery.of(context).size;
Container(
height: screenSize.height,
width: screenSize.width,
child: ListView(...));

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"),
)
)