I retrieve a piece of text from an API. I want to allot a set amount of space to it (say a max Container with width: 300.0 and height: 100.0). Sometimes, the piece of text fits in this Container with font size 30.0. In other times, it won't fit unless I set the text size to 24.0.
Is there a way to dynamically resize text based on its parent container space?
I've built a Container with a ConstrainedBox, which lets me define the max size of the text space. I've also wrapped my Text with a LayoutBuilder. I was hoping that I could check the height of the space of the text, and based on that, determine how to size the text. Like this:
Container(
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 300.0,
maxWidth: 300.0,
minHeight: 30.0,
maxHeight: 100.0,
),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (/* height is larger than 100.0? height is over the constraints? */) { return textWithSize24(); }
return textWithSize30();
}),
),
),
How can I determine the "height that the text would take up if it were size 30.0"?
Maybe I'm approaching this the wrong way and I'm supposed to use maxLines to determine this instead? But how do we know that we've reached more than maxLines?
The other way to do it is to use the number of characters in my String to determine when to change font sizes. This seems kind of manual.
You can use FittedBox to manage text based on height or width.
For Ex.
Simply just wrap your Text widget to FittedBox widget like, Here I want to resize my AppBar text based on width.
AppBar(
centerTitle: true,
title: FittedBox(
fit: BoxFit.fitWidth,
child: Text('Hey this is my long text appbar title')
),
),
Text will be resized based on width of AppBar.
Using BoxFit.scaleDown and fixing the FontSize you can adjust the maximum size of the font.
If the content is small, it occupies the minimum width with the specified font size. At the same time, if the content is large, it resizes to the smallest font size.
FittedBox(
fit: BoxFit.scaleDown,
child:
Text(
"Text here",
style: TextStyle(fontSize: 18),
),)
If you need the text to fill the entire width, using any font size use BoxFit.cover
FittedBox(
fit: BoxFit.cover,
child:
Text(
"Text here",
//style: TextStyle(fontSize: 18),
),)
You can do it using the auto_size_text package:
Container(
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 300.0,
maxWidth: 300.0,
minHeight: 30.0,
maxHeight: 100.0,
),
child: AutoSizeText(
"yourText",
style: TextStyle(fontSize: 30.0),
),
),
);
You can also set maxLines to constrain the text even further or use presetFontSizes if you only want to allow specific font sizes.
FittedBox worked in my case with multiple lines.
SizedBox(
width: MediaQuery.of(context).size.width,
child: FittedBox(
fit: BoxFit.contain,
child: Text(
widget.model.poem,
textAlign: TextAlign.justify,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontStyle: FontStyle.normal),
),
),
),
Responsive_Flutter, I had the same issues since reading your problem. I found it Works every time using this package to resize your fonts.
This Flutter package is for scaling the size your apps UI and fontSize across different sized devices. (The example shows the top text using the Responsive_Flutter package and the bottom text without plugin.
https://github.com/layounisl/responsive_flutter
You can install in 30 seconds - 3 Steps
Add dependencies to yaml file responsive_flutter: ^0.0.4
import 'package:responsive_flutter/responsive_flutter.dart';
child: Text("Responsive flutter", style: TextStyle(fontSize: ResponsiveFlutter.of(context).fontSize(3)),
Here is a list of things I tried which may work for you depending on your use case: https://github.com/flutter/flutter/issues/18431
Ultimately I went with a function that sets the font size based on the length of the String (i.e. with if-else statements).
I used it this way and it worked perfectly:
Flexible(
child: FittedBox(
fit: BoxFit.contain,
child: AutoSizeText(
'your specific text',
maxLines: 1,
style: TextStyle(),
),
),
),
This gives us flexibility and ensures that our child widget is inside and behaving as expected (containing), in addition to using auto_size_text.
Easiest thing I found was auto_size_text: ^2.1.0. As simple as importing the dependency and using AutoSizeText() instead of Text()
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
width: 200.0,
height: 140.0,
child: AutoSizeText(
'This string will be automatically resized to fit in two lines.',
style: TextStyle(fontSize: 30.0),
maxLines: 2,
),
),
),
),
);
}
}
Just generate the font size according to the text length and the maximum rendering area. 300.0 * 100.0 in your case, without forgetting the areas lost during the rendering of the text.
like this :
class MyWidget extends StatefulWidget {
#override
_StateMyWidget createState() => _StateMyWidget();
}
class _StateMyWidget extends State<MyWidget> {
static const _QUOTES = [
{"quote": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", "author": "test"},
{"quote": "Talk is cheap. Show me the code.", "author": "Linus Torvalds"},
{"quote": "First, solve the problem. Then, write the code.", "author": "John Johnson"},
{"quote": "To iterate is human, to recurse divine.", "author": "L. Peter Deutsch"},
{"quote": "The best thing about a boolean is even if you are wrong, you are only off by a bit.", "author": "Anonymous"},
{"quote": "Software is like sex: It’s better when it’s free.", "author": "Linus Torvalds"},
{"quote": "The first 90% of the code accounts for the first 90% of the development time. The remaining 10% of the code accounts for the other 90% of the development time.", "author": "Tom Cargill"},
{"quote": "I think that it’s extraordinarily important that we in computer science keep fun in computing. When it started out it was an awful lot of fun. Of course the paying customers got shafted every now and then and after a while we began to take their complaints seriously. We began to feel as if we really were responsible for the successful error-free perfect use of these machines. I don’t think we are. I think we’re responsible for stretching them setting them off in new directions and keeping fun in the house. I hope the field of computer science never loses its sense of fun. Above all I hope we don’t become missionaries. Don’t feel as if you’re Bible sales-men. The world has too many of those already. What you know about computing other people will learn. Don’t feel as if the key to successful computing is only in your hands. What’s in your hands I think and hope is intelligence: the ability to see the machine as more than when you were first led up to it that you can make it more.", "author": "Alan J. Perlis"},
{"quote":"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live","author": "John Woods"},
{"quote":"You've baked a really lovely cake, but then you've used dog shit for frosting.","author": "Steve Jobs"},
{"quote": "Most software today is very much like an Egyptian pyramid with millions of bricks piled on top of each other, with no structural integrity, but just done by brute force and thousands of slaves.","author": "Alan Kay" },
{"quote": "Software suppliers are trying to make their software packages more ‘user-friendly’… Their best approach so far has been to take all the old brochures and stamp the words ‘user-friendly’ on the cover.","author": "Bill Gates"},
];
static const AREA_LOST_PERCENT = 5;
final rand = math.Random();
#override
initState() {
super.initState();
Timer.periodic(Duration(seconds: 3), (timeVal) {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
final Map<String, String> mapQuote = _QUOTES[rand.nextInt(_QUOTES.length)];
final authorW =
Text(mapQuote["author"], style: TextStyle(fontStyle: FontStyle.italic));
final quoteW = Text(
mapQuote["quote"],
textAlign: TextAlign.center,
style: TextStyle(
fontSize: autoSize(
quoteLength: mapQuote["quote"].length,
parentArea: (350 - 10 * 2 - 16 * 2) * (450 - 10 * 2),
),
),
);
final containerW0 = Container(
height: 450.0,
padding: EdgeInsets.all(10.0),
color: Colors.grey,
child: Center(child: quoteW));
final containerW1 = Container(
height: 500.0,
width: 350,
padding: EdgeInsets.all(16.0),
color: Colors.purple,
child: Column(children: [authorW, containerW0]));
return containerW1;
}
double autoSize({#required int quoteLength, #required int parentArea}) {
assert(quoteLength != null, "`quoteLength` may not be null");
assert(parentArea != null, "`parentArea` may not be null");
final areaOfLetter = parentArea / quoteLength;
final pixelOfLetter = math.sqrt(areaOfLetter);
final pixelOfLetterP = pixelOfLetter - (pixelOfLetter * AREA_LOST_PERCENT) / 100;
return pixelOfLetterP;
}
}
the escape characters must be taken into account during the allocation of the percentage of lost areas. it is best to remove them if the variation is excessive.
Online view -> dartpad.dev
There are two approaches to do this.
Using FittedBox
Using TextPainter (calculate width with text Painter
and update font )
https://prafullkumar77.medium.com/how-to-dynamically-resize-text-in-flutter-bca80415a4d2
You can also use the textScaleFactor attribute in the MediaQueryData class to limit how big a text get
Related
Having a card (see image below)
Figma Info card
All I need to do is to reach this UI but look what I've got:
My Info card
As you can see the image is not fitted, however I created a SizedBox with needed width and heigth, child FittedBox, which has its own child Image.asset but it's not fitted.
DevTools say that the SizedBox itself has 100x100(which is to be as expected) but the Image is not:(
So could you guys suggest me any possible solution or idea?
Appreciate any answer:))
I've tried to put it in Expanded but the Column with Text begins to overflow, I've also tried to create a Container() with ImageDecoration but this is not working as well.
This is the image I need to fit (in case someone wants to try)
Here is the code of my InfoCard() widget:
class InfoCard extends StatelessWidget {
final String heading;
final String description;
const InfoCard({super.key, required this.heading, required this.description});
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 157.0,
padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: ColorConstants.kBackgroundSecondary,
),
child: Row(
children: [
SizedBox(
width: 100.0,
height: 100.0,
child: FittedBox(
child: Image.asset(
'/images/info_card_icon.png',
),
),
),
const SizedBox(width: 20.0),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
heading,
style: Theme.of(context).textTheme.headlineSmall,`
),
const SizedBox(height: 10.0),
Text(
description,
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: ColorConstants.kText),
),
],
),
)
],
),
);
}
}
After #CCP suggestion to remove FittedBox and set Image.asset fit = BoxFit.cover DevTools show me 100x100 image size.
WIth FittedBox()
After I removed FIttedBox()
But as you can see even it's 100x100, the image itself is not expanded as I want
It should match the whole purple area I think
I don't think the problem is the code, but the image
I'm not an image expert so I speak as a hypothesis: maybe even the images that contain only the object have a transparent box with a dimension behind it, and in your image that box was big and made your image small. I tried copying the image to a smaller box and using that fixed the size issue.
info_card_icon2
In your image.asset give his property
fit: BoxFit.cover
I am learning flutter, and faced this issue:
I want the equal button to stretch to the left and cover the whole green area as shown in the picture.
The equal button is in one column and the other four buttons are in another column. And in turn, both of these columns are in a row. i.e
Column:
Row:
Column 1:
Equal-Button // If I warp it in expended, it goes out of sight.
Column 2:
the other four buttons
What is the proper way to fix it, not just hacks. How would you do it?
Here is the whole code:
Github Gist
You'll need to have it in its own column then use crossAxisAlignment.stretch. Just ensure it occupies a finite space
You can wrap the Elevated Button with Sized Box and give it a width of your choice,
than if you want to put the '=' string to the right instead of center you can just wrap the text with align and set alignemnt to your choice
SizedBox(
width: 300,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.grey.shade600),
),
child: Align(
alignment: Alignment.centerRight,
child: Text(
"=",
style: TextStyle(fontSize: 26),
),
),
onPressed: () {},
),
)
Assuming both the = button and / button are in a Row widget you could wrap the = button in a Expanded widget forcing it to take all available space along it MainAxis.
Here's a quick example:
class Sample extends StatelessWidget {
const Sample({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Container(color: Colors.red, height: 50)),
Container(
height: 50,
width: 100,
color: Colors.green,
margin: const EdgeInsets.symmetric(horizontal: 10),
)
],
);
}
}
Good Evening,
I have been searching for many hours and can not find a solution.
I have set a full screen image and with a Stack I have Positioned several GestureDetectors.
I have succeeded to be able to press on a GestureDetector and call a Function.
The problem is that when the screen size changes, either to a new or older phone then the Image responds and covers the full screen but the Positioned() of course stay at the same place, thus the Image mappings are not correct any more.
Is there a way to make the Positioned be responsive? or Maybe a total different way achieving the desired outcome?
Please help me :)
class Overview extends StatelessWidget {
#override
Widget build(BuildContext context) {
double sh = MediaQuery.of(context).size.height;
double sw = MediaQuery.of(context).size.width;
return Scaffold(
drawer: AppDrawer(),
body: Stack(
children: <Widget>[
Container(
height: sh,
width: sw,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/ang7.png'),
fit: BoxFit.cover)),
),
Positioned(
left: 10,
top: 50,
child: IconButton(
icon: Icon(Icons.menu),
iconSize: 30,
color: Colors.white,
onPressed: () => Scaffold.of(context).openDrawer(),
),
Positioned(
left: left,
top: top,
child: GestureDetector(
// child: Icon(Icons.add_circle),
onTap: () => Navigator.push(context, MaterialPageRoute(
builder: (context) {
return Amenities(
tText[page][0],
tText[page][1],
tText[page][2],
);
},
)),
child: Container(
height: 35,
width: 50,
color: Colors.blue,
),
));
),
Posting my comment as an answer just in case it works and there are no better answers provided. Please up vote and mark as answered if it does. :-)
You could try experimenting with MediaQuery.of to determine the size of the device (height and width) and then use those values to scale the Positioned widgets position parameters up or down from your 'baseline' sizes. In theory that should (may) match how the background image has been scaled. Would probably need to scale the size of the Positioned widgets as well.
Update regarding your comment:
Not really. What I mean is that you would need to run MediaQuery.of on the device that you are happy with the UI layout, and record that device's width and height. You then use this width and height in your app and compare these 'baseline' values with what values you get when you run the app on other devices.
So:
Divide other-device-width by baseline-device-width to get your width scaling factor.
Divide other-device-height by baseline-device-height to get your height scaling factor.
Then use these scaling factors to 'scale' the Positioned widgets width (positioned-widget-width multiplied by width-scaling-factor) and height (positioned-widget-height multiplied by height-scaling-factor) and position on the screen (you would have to play around with the positioning values).
Sounds messy but it is just a bit of simple math.
Like I say, might not work or might not be the most elegant solution. There are so many different devices out there you will never really be able to completely test this. Might need to rethink you UI. Maybe some other people have ideas around that.
I have a Text widget and not sure why it seems to just have padding at the top and bottom even though I didn't set any in the code. This is from the default Flutter app, I just modified the font size.
body: Center(
child: Column(
children: <Widget>[
Text(
'0:00.00',
style: TextStyle(fontSize: 76),
),
],
),
),
This is a screenshot of the highlighted Text widget in Android Studio. There's really nothing else adding any padding so I don't know why it's there.
Sometimes you get this in CSS where there is padding even though none was set but you can remove it simply with padding: 0 but I don't see how to do it here since I can't find a padding option for the Text widget.
EDIT: The amount of padding changes with the size of the font. It seems to always contain a certain amount of padding, like a html H1 tag.
The proper way you can get rid of the unwanted padding is by setting the height property in the TextStyle. With this you set the height for each line.
Text(
"Let's make\nsome pancakes",
style: TextStyle(
height: 1.2, //SETTING THIS CAN SOLVE YOUR PROBLEM
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w300,
),
textAlign: TextAlign.center,
),
In fact, we can confirm from the docs that:
For most fonts, setting height to 1.0 is not the same as omitting or setting height to null because the fontSize sets the height of the EM-square, which is different than the font provided metrics for line height.
For more info: https://api.flutter.dev/flutter/painting/TextStyle/height.html
So give it a try. It worked for me.
There are reasons why the Text widget has this "padding". Take in consideration the next example:
Text(
'123 gyÓ',
style: TextStyle(
fontSize: 40.0,
),
),
As we can see, using another characters like the letters g and y and an uppercase O with an accent marker, shows us that there's no padding on the Text widget really.
Fonts have ascenders and descenders on some characters, and there's also en ascent line for special characters like the accent marker. That's why numbers are centered in the middle. That's not padding on Flutter side, but typography design(?). Maybe you could find a way to sort your issue, by looking for a font without ascenders and descenders.
More info about fonts on Wikipedia
Conclusion: if you want to select the Text widget with the Flutter inspector, and see no space around some characters, that is not possible.
I think I found a good solution/workaround for removing both top and bottom padding.
The trick is to put your Text in a Container and then change the height of your TextStyle inside the Text, as well as the height of your Container. It's a bit of going back and forth for the perfect measurements but it's worth it imo.
Here's an example from my own code.
Container(
height: 24,
child: Text(
'Auto',
style: TextStyle(
height: 0.77,
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
There are 2 potential reasons:
The fontFamily - try commenting it out and see if you still have the same problem. If this is the reason and you absolutely must/want to use this specific font, I'm not sure what the fix is, apart from some horrible logic to stack widgets accordingly so that it looks like there's no padding.
Layout constraints from the parent Widgets - you only included the code of the Text widget, but if for example it's wrapped in an Expanded widget within a Column, that would also explain the extra height (possibly wrong assumption here because the text wouldn't center itself vertically by default anyway, but then there might be a DefaultTextStyle widget up in the hierarchy somewhere so still possible....)
Edit: It seems like there is some default padding around the text.
I found that you can reduce the top padding by setting a TextStyle with a height lower than 1. The value required seems to depend on the font size and the text itself, as different characters have different heights.
You can also reduce the bottom padding by clipping the Text using a fixed size Container. This will also vary based on the font size and the text itself. Below is an example of using both of these to reduce the top and bottom padding to 0 for the text '0:00.00' of font size 72:
ClipRect(
child: Container(
height: 55,
child: Text("0:00.00",
style: TextStyle(
fontSize: 72,
height: 0.80,
),
),
),
),
I know this is a very poor solution, but I don't know of any better one. I tried using a custom StrutStyle on the Text widget, but that didn't help either.
Try playing with line height of the text by setting height in style property. Height of the text is determined based on font size. It will multiply the font size and give you the space that will look like it has some padding. The line height of the text is also linked to the font itself, since every font has it own line height.
child: Text(
'Some text goes here',
style: TextStyle(
fontSize: 25.0,
height: 1,
),
)
I found the solution myself in the end. It can be positioned using Stack and Row widgets. I found Row works better than using Positioned as the text can be centred using the Row widgets.
body: Stack(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'0:00.00',
style: TextStyle(fontSize: 76),
),
],
),
Padding(
padding: EdgeInsets.only(top: 56.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Coke',
style: TextStyle(fontSize: 76),
),
],
),
),
],
)
It kind of works like the negative padding in CSS but actually it's the padding that moves the bottom text down rather than up since otherwise they occupy the same space.
After googling a while, I've written this.
What does it do? It's shifting down the position of the text
(not shrinking)
import 'package:vector_math/vector_math_64.dart';
import 'package:flutter/material.dart';
class BaselineText extends StatefulWidget {
final Widget child;
BaselineText({this.child});
#override
_BaselineTextState createState() => _BaselineTextState();
}
class _BaselineTextState extends State<BaselineText> {
GlobalKey _key = GlobalKey();
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {}));
}
#override
Widget build(BuildContext context) {
final RenderBox renderBox = _key.currentContext?.findRenderObject();
final height = renderBox?.size?.height ?? 0;
return Baseline(
baseline: 0,
baselineType: TextBaseline.ideographic,
key: _key,
child: Transform(
transform: Matrix4.translation(
Vector3(0, height, 0),
),
child: widget.child,
),
);
}
}
How to use:
BaselineText(
child: Text("something"),
)
DONE!!!
I'm getting different results of the same screen when run on different phone sizes:
The second phone screen is my desired outcome. Using expanded initially helped me solve an issue with the first phone screen (where there was an extra space below the green bar). Later I realized that I was getting overflow on the second phone screen. Thus, I solved the overflow by using SingleChildScrollView. However that somehow cause the first phone screen to have the issue of the extra space again.
Code:
final quizBottomContentText = Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.only(left:30.0, right:30.0, top: 30.0, bottom: 30),
child: Text(
questions[questionNum].title,
style: TextStyle(fontSize: 18.0),
)
);
final quizOptions = Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.only(bottom: 20.0),
child: Center(
child: Column(
children: questions[questionNum].options.map<Widget>(
(option) => SimpleRoundButton(
backgroundColor: Color.fromRGBO(58, 66, 86, 1.0),
buttonText: Text(option,
style: TextStyle(
color: Colors.white
),
),
textColor: Colors.white,
onPressed: (){},
),
).toList(),
)
)
);
final countdown = CountdownWidget(
width: MediaQuery.of(context).size.width,
duration: 20,
triviaState: triviaState,
);
final quizBottomContent = Expanded(
child: SingleChildScrollView(
child: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: <Widget>[quizBottomContentText, quizOptions, countdown],
),
)
)
);
Because you're app is not responsive. See, you're passing size in a hard coded way
eg: padding: EdgeInsets.only(bottom: 20.0)
What this means? Mean that in your emulator, you'll get a result, maybe is what you expect, but in another device, maybe in a iPhone Xs Max, the result is diferent, so, what can you do?
You'll have two options here, the first, use the MediaQuery component. I'll show you how and why I use the way I use for a better understanding.
In Flutter, we have the MediaQuery component with a lot of propierty, one of them, is this: MediaQuery.of(context).size.width (Which gets your device's full width size). Everything fine till here, right? If you print this in diferent devices, you'll get diferente results, which means that a Padding with 20 from left/right will be different in both devices.
I made a calculation to kinda ''hack'' this and make it responsive, get this:
MediaQuery.of(context).size.width and divide by 400. Why 400? Me and a friend discovered this and when we divided, we've got a value almost next to 1 in very different devices, so, if you get the result (eg 1.5) and multiplies by 20, you'll make your app kinda of responsive. It's a way, the second way it's to use the LayoutBuilder which need a context and a constraints as parameters.
With constraints, you can manipulate to show differents model designs in differents devices, like, buildConventionalDesig for devices with width less than 400, an example, or buildBiggerDesign for a bigger phone, like iPhone Xs Max.
There's an article that you could be your reference to help you, check it:
Build Response UIs in Flutter. It doesn't cover this trick with MediaQuery, but maybe you can get a new insight.
Hope this have helped.