How to get inside a widget the resulting size of itself shrinked by FittedBox - flutter

In the code below, a row of two 300x300 boxes (_Box) wrapped with FittedBox shrinks to fit in the screen. As a result, each box becomes smaller than 300x300.
However, if I get the width of a box in the build() method of _Box using RenderBox.size and LayoutBuilder(), the obtained size is 300.0 and Infinity respectively.
How can I get the actual displayed size?
I'd like to get it inside the _Box class, without it getting passed from the parent.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: FittedBox(
child: Row(
children: <Widget>[
_Box(Colors.red),
_Box(Colors.orange),
],
),
),
),
),
);
}
}
class _Box extends StatelessWidget {
const _Box(this.color);
final Color color;
#override
Widget build(BuildContext context) {
RenderBox renderBox = context.findRenderObject();
print(renderBox?.size?.width); // 300.0
return LayoutBuilder(
builder: (_, constraints) {
print(constraints.maxWidth); // Infinity
return Container(
width: 300,
height: 300,
color: color,
);
},
);
}
}

The Flutter constraints object is used to limit how large or small a widget can be rendered, and is usually never really used to get the current size of the widget.
The renderBox.size object is of Size class, and as a result it has both renderBox.size.width and renderBox.size.height as defined getters. Note that these values can only be set once the layout phase of the current view is over: see the findRenderObject() docs page.
This means that you will have to avoid calling findRenderObject() from the build() method. Instead you will have to define a callback function that must execute after the layout process is complete. You can do this using that have widgets that have callback functions like onTap or onSelected. How you implement this and finally get the actual layout size by running the callback function is totally dependent on your use case.
Further recommended reading:
DevelopPaper - Sample code for getting the width and height of screen and widget in Flutter
Flutter on Github - How to get a height of a widget? #16061
RĂ©mi Rousselet's amazing answer explaining his workaround (using an Overlay widget)

I'll answer my own question, although it is not a direct answer.
I couldn't find a way to get the size shrinked by FittedBox, but I realised that I was able to get around it by using Flexible instead.
SafeArea(
child: Row(
children: const [
Flexible(
child: _Box(Colors.red),
),
Flexible(
child: _Box(Colors.orange),
),
],
),
);
#override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 300.0),
child: AspectRatio(
aspectRatio: 1.0,
child: ColoredBox(color: color),
),
);
}
It still seems impossible to get the size via RenderBox, and it is now possible with LayoutBuilder. But either way, I didn't need them.
The constraints of the two boxies are shrinked by Flexible if a smaller space is available, but they expand as big as the space allows, so I limited the maximum size using ConstrainedBox and AspectRatio.
I didn't have to stick to FittedBox. I think I was obsessed with the idea of using it and couldn't think of other solutions when I posted the question two years ago.

Related

How to fix a widget to the bottom of the screen without causing render overflow in Flutter?

I'd like to achieve the effect shown on the screenshots below:
First scenario:
The green widget is fixed to the bottom. Container isn't scrollable, as the content is short enough.
Second scenario:
The green widget is pushed to the bottom. The container is scrollable, as the content is too long to fit in the viewport.
The problem is, that since technically SingleChildScrollView's height is infinite, it's impossible to push the green widget to the end of the viewport.
So, what needs to be done for this effect to be achieved (also, both the blue and the green widgets are of dynamic height)?
Try this:
import 'package:flutter/material.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Container(
height: 300,
color: Colors.blue,
),
)),
Container(
height: 100,
color: Colors.green,
)
],
)),
);
}
}
Mess around with the blue containers height to get scrolling to work. The key Widget here is Expanded as it makes it's child height be the greatest room available inside the column. It will take up the rest of the space that the green container is not using
Id highly recommend reading this article to better understand how widgets are laid out in Flutter.
use bottomNavigationBar parameter in you Scaffold for fixed widget to bottom screen

How to hide an element that cannot be fully displayed in flutter?

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.

How to expand all children of a Stack that includes a CustomPainter in Flutter

Let's say in Flutter we have a Stack that contains:
A CustomPainter with size of Size.infinite
Any other widget that is not a Custom Painter (that we also want to fill all available space exactly the same way that the custom painter does)
In this example, I'm using an Icon for item 2:
#override
Widget build(BuildContext context) {
return Expanded(
child: Container(
color: Colors.white,
child: Stack(
children: [
Icon(Icons.star),
CustomPaint(
painter: MyCustomPainter(percentTimeElapsed: 0.5),
size: Size.infinite)
],
),
),
);
}
Now, obviously, we don't get the desired result yet -- the custom painter is "expanded" but the icon is not.
But I have tried all the following with no luck:
Wrapping the Icon with Expanded (causes exception)
Wrapping the Stack widget with Expanded (causes exception)
Defining the size of the Icon to be Size.infinite (will not build because size param expects a double)
Using fit: StackFit.expand as a parameter for the Stack, but this just seems to center the Icon and doesn't change it's size.

Since the Listview.builder widget in flutter requires a height, some fixed heights I give to the container wrapping the widget causes to overflow

Code
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('Search Families'),
centerTitle: true,
),
backgroundColor: StaticEntry.backColor,
body: Center(
child: FractionallySizedBox(
widthFactor: 0.8,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SearchInput(onSubmitHandler: onSubmit),
SizedBox(
height: 300,
),
resultList.isNotEmpty
? Container( // <---------------- Container that I am using to wrap the list view widget
height: 400, // <---------------- fixed height I am setting on the container that is wrapped around the ListView widget
child: ListView.builder( // <---------------- ListView widget
itemCount: 20,
itemBuilder: (context, index) {
return Text('Heyyyy!');
},
),
)
: SizedBox()
],
),
),
),
),
);
}
Problem
In the above code, as I have pointed using arrows, I am wrapping a ListView widget in a Container and assigning a fixed height to that Container since ListView widgets has an infinite height by default.
The problem with this approach is, since that height I am providing to the container is a fixed height, the layout breaks on devices with small viewport heights, while it works fine with devices that has a large viewport height.
So what I am trying to figure out is, how can I set a height to that Container that works on all devices without breaking the layout? (I am trying to make that height as maximum as possible without making the app break on smaller devices.)
(While researching about this, I came across this stack overflow link and according to that link, I tried wrapping the ListView widget with a Flexible widget and set the shrinkWrap property of the ListView widget to true. This did not work and it caused my ListView widget and the other widget to gain as much space as possible between them and pushed my ListView widget to the bottom of the screen.)

How to get device font size in flutter

I am facing a problem with the listview when height is bounded, so when I change the phone font size an overflow occurs and I don't want to give extra height to the container.
Container(
height: fixed height goes here,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
some widgets goes here...
],
),
)
you can try to rely on textScaleFactor, by default it's 1.0
if you change font size on Settings page of your device this value will be changed to 1.15, 1.3 and so on (in 0.15 steps).
so you can multiply container height by this value:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: SafeArea(child: Home()),
),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
double h = MediaQuery.of(context).textScaleFactor;
return Center(
child: Text('$h'), // with default settings it shows 1.0
);
}
}
In case your looking for the device font size (set inside the settings activity of the smartphone), you can use textScaleFactor property of the MediaQuery class.
double textScale = MediaQuery.of(context).textScaleFactor;
Alternatively, you can get the same value like this:
double textScale = MediaQuery.textScaleFactorOf(context);
As a heads up, all of the font sizes in Flutter are automatically scaled by this setting so if a user has their font scaled way up, you might hit some overflow errors. With that said, doing the same thing while you're debugging is an awesome way to find where your layout might overflow.
Check out the accessibility section of the Flutter Docs for some more info.
You need to detect screen height and give your Container() height according to tha, and keep it detecting whenever build() method being re-called.
That's how to get a responsive height for your Container()
MediaQuery() could do that, as follow :
Container(
height: MediaQuery.of(context).size.height, // screen's size.height
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
some widgets goes here...
],
),
)
Flutter says about size property :
The size of the media in logical pixels (e.g, the size of the screen).