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"),
)
)
Related
So for this application (Windows, Web) I have 2 requirements:
User can drag around widgets on the screen (drag and drop) to any location.
The app must scale to screen/window size
For (1) I used this answer.
For (2) I used this solution.
As mentioned in the code comment below I can't have both:
If I set logicWidth and logicHeight dynamically depending on the window size, the dragging works fine but the draggable widgets won't scale but instead stay the same size regardless of the window size.
If I set logicWidth and logicHeight to a constant value (the value of the current cleanHeight ) the dragging will be messed up for other screen sizes but then the draggable widgets will scale correctly with the window size.
In other words: for the dragging to work nicely these values need to be matching the window size at any time. But by changing these values I ruin the scaling I need.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
//containing widgets to drag around
const List<Widget> draggableWidgets = [
DraggableWidget(
draggableWidget: CircleAvatar(
backgroundColor: Colors.green,
radius: 32,
)),
DraggableWidget(
draggableWidget: CircleAvatar(
backgroundColor: Colors.red,
radius: 24,
)),
];
class FrontPageWidget extends ConsumerWidget {
const FrontPageWidget({Key? key}) : super(key: key);
static const routeName = '/frontPage';
#override
Widget build(BuildContext context, WidgetRef ref) {
//screen height and padding
final height = MediaQuery.of(context).size.height;
final padding = MediaQuery.of(context).viewPadding;
// Height (without status and toolbar)
final cleanHeight = height - padding.top - kToolbarHeight;
//either make those values dynamic (cleanHeight updates depending on screen size / window size) OR constant (961px is the cleanHeight on full screen)
//if values are dynamic => the draggable widgets not scaling to screen size BUT dragging works fine
//if values are constant => the draggable widgets do scale to screen size BUT dragging is messed
final logicWidth = cleanHeight; //961
final logicHeight = cleanHeight; //961
return Scaffold(
appBar: AppBar(
title: const Text('Main Page'),
),
body: SizedBox.expand(
child: FittedBox(
fit: BoxFit.contain,
alignment: Alignment.center,
child: Container(
color: Colors.grey,
width: logicWidth,
height: logicHeight,
child: Stack(
children: draggableWidgets,
),
))),
);
}
}
class DraggableWidget extends StatelessWidget {
final Widget draggableWidget;
const DraggableWidget({Key? key, required this.draggableWidget})
: super(key: key);
#override
Widget build(BuildContext context) {
final ValueNotifier<Matrix4> notifier = ValueNotifier(Matrix4.identity());
return Center(
child: MatrixGestureDetector(
onMatrixUpdate: (m, tm, sm, rm) {
notifier.value = m;
},
child: AnimatedBuilder(
animation: notifier,
builder: (ctx, child) {
return Transform(
transform: notifier.value,
child: Center(
child: Stack(
children: [draggableWidget],
),
),
);
},
),
),
);
}
}
One way of doing it is wrapping the draggableWidget in a Transform widget and set the scale factor in relation to the dimensions:
child: AnimatedBuilder(
animation: notifier,
builder: (ctx, child) {
final height = MediaQuery.of(context).size.height;
return Transform(
transform: notifier.value,
child: Center(
child: Stack(
children: [
Transform.scale(
scale: height / 1000,
child: draggableWidget)
],
),
),
);
},
),
I had a similar issue, instead of getting the height from the MediaQuery get it from the LayoutBuilder, I noticed it is working much better when resizing the window.
body: LayoutBuilder(
builder: (context, constraints) {
return SizedBox.expand(
child: FittedBox(
fit: BoxFit.contain,
alignment: Alignment.center,
child: Container(
color: Colors.grey,
width: constraints.maxWidth,
height: constraints.maxHeight,
child: Stack(
children: draggableWidgets,
),
)
)
);
}
);
Another way of achieving this:
To drag around widgets on the screen (drag and drop) to any location.
Draggable Widget
Check Flutter Draggable class
And to scale screen/window size.
Relative Scale
FlutterScreenUtil
I have a Text widget that sometimes can be fully displayed, sometimes not, depending on the widgets around.
If there is not enough space to fully display the widget, I want the widget to not show at all, I don't want it to show partially like with the overflow attribute.
If you know a way to do this, thanks.
LayoutBuilder to the rescue for you!
Builds a widget tree that can depend on the parent widget's size.
Reference
Try this! Play around with the allowedTextHeightInPixels value to see how it works.
/// Breakpoint or condition to WHEN should we display the Text widget
const allowedTextHeightInPixels = 150.0;
/// Test height for the [Text] widget.
const givenTextHeightByScreenPercentage = 0.3;
class ResponsiveTextWidget extends StatelessWidget {
const ResponsiveTextWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
print('Text height in pixels: ${constraints.maxHeight * givenTextHeightByScreenPercentage}');
return Column(
children: [
Container(
color: Colors.red,
height: constraints.maxHeight * 0.5,
),
if (constraints.maxHeight * givenTextHeightByScreenPercentage > allowedTextHeightInPixels)
const SizedBox(
child: Text(
'Responsive Me',
style: TextStyle(fontSize: 15.0),
),
),
Container(
color: Colors.blue,
height: constraints.maxHeight * 0.2,
),
],
);
},
),
),
);
}
}
I don't know why you need to do this but i thing overflow is good enough for most case, you can also use Fittedbox to scale the text with the box with no redundant space.
In case you still want do it, you need to find the RenderBox of that specific widget, which will contain its global position and rendered size from BuildContext. But BuildContext can be not exist if the widget is not rendered yet.
If by "fully displayed" you mean that, for example, you have a SingleChildScrollView and only half of your Text widget is visible, you can try out this library :
https://pub.dev/packages/visibility_detector.
You can retrieve the visible percentage of your widget with the method visibilityInfo.visibleFraction.
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.
I have a widget that needs to draw on the remaining height where that widget needs to be drawn in between the top and bottom widget(Bottom widget drawn with bottom center attribute).
Ex:
Container() -- The First container almost take half screen
Container() -- The Second container draw on the bottom of the screen eg: Consider widget as Button
Container() -- The Third Container needs to be drawn in between two containers.
The Expanded widget would work for this scenario where we need to wrap middle widget with it.
Eg:
Expanded(
child: Container(
...
),
),
I know a better method using MediaQuery.of(), where you just put the percentage of the space available that a widget will cover:
final mediaQuery = MediaQuery.of(context);
const SizedBox(height: mediaQuery.size.heigh * [percentage of the screen]
But this way includes AppBar space too. So if you want to exclude it, you should do this:
Create appBar variable:
final PreferredSizeWidget appBar = AppBar(
title: const Text('Personal expensense'),
);
Subtract mediaQuery.size with appBar variable size:
final availableHeight = mediaQuery.size.height -
appBar.preferredSize.height -
mediaQuery.padding.top;
A full example:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final PreferredSizeWidget appBar = AppBar(
title: const Text('Personal expensense'),
);
final mediaQuery = MediaQuery.of(context);
final availableHeight = mediaQuery.size.height -
appBar.preferredSize.height -
mediaQuery.padding.top;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
height: availableHeight * 0.4,
child: Container(),
),
SizedBox(
height: availableHeight * 0.3,
child: Container(),
),
SizedBox(
height: availableHeight * 0.3,
child: Container(),
),
],
);
}
}
It happens to be really useful!
If you have something like a Column, you probably want to use the Expanded widget.
Try this code
Column(
children: [
Container(color: Colors.red, height: 100),
Expanded(
child: Container(color: Colors.yellow, height: 100),
),
Container(color: Colors.red, height: 100),
],
)
From the Expanded-class documentation:
A widget that expands a child of a Row, Column, or Flex so that the
child fills the available space.
Is there a simple (non-LayoutBuilder) way to size an element relative to screen size (width/height)? For example: how do I set the width of a CardView to be 65% of the screen width.
It can't be done inside the build method (obviously) so it would have to be deferred until post build. Is there a preferred place to put logic like this?
This is a supplemental answer showing the implementation of a couple of the solutions mentioned.
FractionallySizedBox
If you have a single widget you can use a FractionallySizedBox widget to specify a percentage of the available space to fill. Here the green Container is set to fill 70% of the available width and 30% of the available height.
Widget myWidget() {
return FractionallySizedBox(
widthFactor: 0.7,
heightFactor: 0.3,
child: Container(
color: Colors.green,
),
);
}
Expanded
The Expanded widget allows a widget to fill the available space, horizontally if it is in a row, or vertically if it is in a column. You can use the flex property with multiple widgets to give them weights. Here the green Container takes 70% of the width and the yellow Container takes 30% of the width.
If you want to do it vertically, then just replace Row with Column.
Widget myWidget() {
return Row(
children: <Widget>[
Expanded(
flex: 7,
child: Container(
color: Colors.green,
),
),
Expanded(
flex: 3,
child: Container(
color: Colors.yellow,
),
),
],
);
}
Supplemental code
Here is the main.dart code for your reference.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("FractionallySizedBox"),
),
body: myWidget(),
),
);
}
}
// replace with example code above
Widget myWidget() {
return ...
}
FractionallySizedBox may also be useful.
You can also read the screen width directly out of MediaQuery.of(context).size and create a sized box based on that
MediaQuery.of(context).size.width * 0.65
if you really want to size as a fraction of the screen regardless of what the layout is.
This might be a little more clear:
double width = MediaQuery.of(context).size.width;
double yourWidth = width * 0.65;
Hope this solved your problem.
There are several possibilities:
1- The first one is the use of the MediaQuery :
Code :
MediaQuery.of(context).size.width //to get the width of screen
MediaQuery.of(context).size.height //to get height of screen
Example of use :
Container(
color: Colors.yellow,
height: MediaQuery.of(context).size.height * 0.65,
width: MediaQuery.of(context).size.width,
)
Output :
2- The use of FractionallySizedBox
Creates a widget that sizes its child to a fraction of the total available space.
Example :
FractionallySizedBox(
widthFactor: 0.65, // between 0 and 1
heightFactor: 1.0,
child:Container(color: Colors.red
,),
)
Output :
3- The use of other widgets such as Expanded , Flexible and AspectRatio and more .
You could build a Column/Row with Flexible or Expanded children that have flex values that add up to the percentages you want.
You may also find the AspectRatio widget useful.
There is many way to do this.
1. Using MediaQuery : Its return fullscreen of your device including appbar,toolbar
Container(
width: MediaQuery.of(context).size.width * 0.50,
height: MediaQuery.of(context).size.height*0.50,
color: Colors.blueAccent[400],
)
2. Using Expanded : You can set width/height in ratio
Container(
height: MediaQuery.of(context).size.height * 0.50,
child: Row(
children: <Widget>[
Expanded(
flex: 70,
child: Container(
color: Colors.lightBlue[400],
),
),
Expanded(
flex: 30,
child: Container(
color: Colors.deepPurple[800],
),
)
],
),
)
3. Others Like Flexible and AspectRatio and FractionallySizedBox
First get the size of screen.
Size size = MediaQuery.of(context).size;
After this you can get width and multiply it with 0.5 to get 50% of screen width.
double width50 = size.width * 0.5;
But problem generally comes in height, by default when we use
double screenHeight = size.height;
The height we get is global height which includes StatusBar + notch + AppBar height. So, in order to get the left height of the device, we need to subtract padding height (StatusBar + notch) and AppBar height from total height. Here is how we do it.
double abovePadding = MediaQuery.of(context).padding.top;
double appBarHeight = appBar.preferredSize.height;
double leftHeight = screenHeight - abovePadding - appBarHeight;
Now we can use following to get 50% of our screen in height.
double height50 = leftHeight * 0.5
MediaQuery.of(context).size.width
you can use MediaQuery with the current context of your widget and get width or height like this
double width = MediaQuery.of(context).size.width
double height = MediaQuery.of(context).size.height
after that, you can multiply it with the percentage you want
Use the LayoutBuilder Widget that will give you constraints that you can use to obtain the height that excludes the AppBar and the padding. Then use a SizedBox and provide the width and height using the constraints from the LayoutBuilder
return LayoutBuilder(builder: (context2, constraints) {
return Column(
children: <Widget>[
SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
...
if you are using GridView you can use something like Ian Hickson's solution.
crossAxisCount: MediaQuery.of(context).size.width <= 400.0 ? 3 : MediaQuery.of(context).size.width >= 1000.0 ? 5 : 4
Code :
MediaQuery.of(context).size.width //to get the width of screen
MediaQuery.of(context).size.height //to get height of screen
You can use the Align widget. The heightFactor and widthFactor parameters are multiplied by the size of the child widget. Here is an example that will make a widget with a fixed height in% ratio
Align(
alignment: Alignment.topCenter,
heightFactor: 0.63,
widthFactor: ,
child: Container(
width: double.infinity,
),
Use scaler to define the layout width and height in percentage
dependencies:
scaler: ^1.1.0+1
After setting this in pubspec.yaml you can use this by following the code -
import 'package:scaler/scaler.dart';
Example After import use this -
import 'package:scaler/scaler.dart';
#override
Widget build(BuildContext context) {
/**
* Container with 25% width of screen
* and 25% height of screen
*/
return Container(
height: Scaler.height(0.25, context),
width: Scaler.width(0.25, context),
child: Container()
);
}
To more detail about this
https://pub.dev/packages/scaler
For width
double width = MediaQuery.of(context).size.width;
double yourWidth = width * 0.75;
For Height
double height = MediaQuery.of(context).size.height;
double yourHeight = height * 0.75;
If you don't want static height and width just use Expanded widget\
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: MyStatelessWidget(),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Expanded Row Sample'),
),
body: Center(
child: Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
color: Colors.amber,
height: 100,
),
),
Container(
color: Colors.blue,
height: 100,
width: 50,
),
Expanded(
child: Container(
color: Colors.amber,
height: 100,
),
),
],
),
),
);
}
}
I am surprised that no one has yet suggested LayoutBuilder in 2023 which gives you access to the parent's width BoxConstraints.constraints.maxWidth, which is the most versatile method.
Expanded can only set the percentage of the available spacing, but what if you really want to set a percentage based on the actual parent's widget only, not the entire screen width, what if have a fixed width widget in a row, even a more complex, what if you also want an Expanded to expand the remaining Row.
MediaQuery.of(context).size.width is relative to the entire screen, not to the actual parent.
FractionallySizedBox works similarly but you can't put it in Row
Also, this method the perfect emulation of CSS % unit of measurement.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, BoxConstraints constraints) {
return SizedBox(
width: 470,
child: Row(
children: [
SizedBox(
width: 200,
child: Icon(
Icons.link,
color: Colors.white,
),
),
SizedBox(
width: constraints.maxWidth*0.6,
child: Icon(
Icons.message,
color: Colors.white,
),
),
Expanded(
child: Icon(
Icons.phone,
color: Colors.white,
),
),
SizedBox(
width: constraints.maxWidth*0.3,
child: Icon(
Icons.account_balance,
color: Colors.white,
),
),
],
),
);
}
);
}
}
In this example, we are setting a parent widget of 470 width with Row inside. In the Row, one element has a 200 fixed width, another with a 60% of the parent with 470, another with 30% of that same parent, and another expanding any remaining space.