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),
),
),
Related
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
there is a scenario that i have a circle and inside that circle i want to show some text. Both items are in a scrollable listview. i want to calculate the width of the parent and then give with to the inner children to be placed and shouldn't move backward or forward from its outer parent circle widget.
i have used stack widget to do so which works fine on a single device or screen but what about other screen sizes?
any suggestion according to this?
I asked yesterday the same concept question:
the FractionallySizedBox is super useful on those cases, it gives the width / height of a children based on the width / height of the parent:
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ParentCircle(
height: 250,
width: 250,
color: Colors.red,
child: FractionallySizedBox(
widthFactor: .5,
heightFactor: .5,
alignment: FractionalOffset.center,
child: ChildCircle(),
),
),
),
),
);
}
}
this will give the ChildCircle a 50% width and 50% height of the ParentCircle
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'm trying to recreate the weather app design from this link UI design. However i'm stuck at creating the huge Icon that is overflowing in the background. I tried to use a stack and add an Icon widget with very big size. However, the icon will change its position when the size is changed. So it moves from the center. Any idea on how to get the background result as in the design?
Edit:
I tried the code below. However the icon will move if the screen size of the device changes. Is there away to have this look the same on every screen size?
Stack(
children: [
Positioned(
left: -250,
top: -100,
child: Icon(
Icons.ac_unit,
size: 900,
color: Colors.white.withOpacity(0.5),
),
),
Which gives me the result below.
Use the widget: OverflowBox
The Flutter documentation explains OverflowBox as:
A widget that imposes different constraints on its child than it gets from its parent, possibly allowing the child to overflow the parent.
Example:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Overflow Box',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
backgroundColor: Colors.blue,
body: Stack(children: [
OverflowBox(
// Specify the maxWidth and maxHeight and it works
// regardless of orientation.
maxWidth: 800,
maxHeight: 800,
child: Icon(
Icons.ac_unit,
size: 800,
color: Colors.black.withAlpha(80),
),
),
]),
),
);
}
}
Image of the code in action:
Using Stack is fine. Did you use Positioned or Align to wrap the Icon widget, to control its position?
For example, your widget hierarchy could be something like: Stack > Align > Icon.
In the Align widget, you can specify how it aligns in the Stack. The values are (x, y) ranging from -1 to +1, with 0 being the center. For example, you can use alignment: Alignment(0, -0.5) to make it center horizontally, and 50% towards the top on the vertical axis.
if you will use Positioned widget, you can actually positioned based on screen height and width and then warp in FractionalTranslation to center it from its given size,
lets calculate with simple way :
code :
return Stack(
children: [
Positioned(
left: MediaQuery.of(context).size.width * 0.35,
top: MediaQuery.of(context).size.height * 0.4,
child: FractionalTranslation(
translation: const Offset(-0.5, -0.5),
child: child()),
)
],
);
I like the answer that #Tor-Martin Holen suggested, but I would adjust the maxWidth and size attributes to MediaQuery.of(context).size.width to adjust to different screens.
I am trying to understand the SafeArea widget in Flutter.
SafeArea code added to Flutter Gallery app here in github show top:false and bottom:false everywhere. Why do these need to be set false in these cases?
SafeArea is basically a glorified Padding widget. If you wrap another widget with SafeArea, it adds any necessary padding needed to keep your widget from being blocked by the system status bar, notches, holes, rounded corners, and other "creative" features by manufacturers.
If you are using a Scaffold with an AppBar, the appropriate spacing will be calculated at the top of the screen without needing to wrap the Scaffold in a SafeArea and the status bar background will be affected by the AppBar color (Red in this example).
If you wrap the Scaffold in a SafeArea, then the status bar area will have a black background rather than be influenced by the AppBar.
Here is an example without SafeArea set:
Align(
alignment: Alignment.topLeft, // and bottomLeft
child: Text('My Widget: ...'),
)
And again with the widget wrapped in a SafeArea widget:
Align(
alignment: Alignment.topLeft, // and bottomLeft
child: SafeArea(
child: Text('My Widget: ...'),
),
)
You can set a minimum padding for edges not affected by notches and such:
SafeArea(
minimum: const EdgeInsets.all(16.0),
child: Text('My Widget: ...'),
)
You can also turn off the safe area insets for any side:
SafeArea(
left: false,
top: false,
right: false,
bottom: false,
child: Text('My Widget: ...'),
)
Setting them all to false would be the same as not using SafeArea. The default for all sides is true. Most of the time you will not need to use these settings, but I can imagine a situation where you have a widget that fills the whole screen. You want the top to not be blocked by anything, but you don't care about the bottom. In that case, you would just set bottom: false but leave the other sides to their default true values.
SafeArea(
bottom: false,
child: myWidgetThatFillsTheScreen,
)
Supplemental code
In case you want to play around more with this, here is main.dart:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: BodyWidget(),
),
);
}
}
class BodyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: SafeArea(
left: true,
top: true,
right: true,
bottom: true,
minimum: const EdgeInsets.all(16.0),
child: Text(
'My Widget: This is my widget. It has some content that I don\'t want '
'blocked by certain manufacturers who add notches, holes, and round corners.'),
),
);
}
}
When you wrap a widget A in a safe area, you are asking to the framework "Please, keep my widget A away from the device's UI navigation and notches".
The arguments 'top, bottom, right and left' are used to tell to the framework if you want him to avoid the device's intrusions from that sides specifically.
For example: if you put your widget A inside a safe area in the top of the screen and sets the "top" argument to false, it will be cropped by the iPhone's X and Pixel 3's notches.
SafeArea is a widget that sets its child by enough padding to avoid intrusions by the operating system and improve the user interface.
import 'package:flutter/material.dart';
class SafeArea extends StatefulWidget {
#override
_SafeAreaState createState() => _SafeAreaState();
}
class _SafeAreaState extends State<SafeArea> {
#override
Widget build(BuildContext context) {
MediaQueryData mediaQueryData=MediaQuery.of(context);
double screenWidth = mediaQueryData.size.width;
var bottomPadding=mediaQueryData.padding.bottom;
return Padding(
padding: EdgeInsets.only(bottom: bottomPadding),
child: Scaffold(
body: new Container(
),
),
); }}
Without using SafeArea in iPhone 12 pro max
With using SafeArea
Code snippet using SafeArea
SafeArea(
child: Text('Your Widget'),
)