Flutter: shrink images to fit into row while keeping aspect ratio - flutter

I would like to display two images side by side with a bit of text above and below. The layout has to work in portrait and landscape mode. The images are loaded from the network and I don't know their dimensions, although I do know the aspect ratio (3:4) and orientation (portrait).
The current solution works well in portrait mode but totally fails in landscape. Here the middle section with the two images is scaled down to fit the width which ends up with images that are too high for the space available.
[...]
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text('Text 1', style: Theme.of(context).textTheme.headline),
IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TrimmedImage(left.src, () => _advanceRight()),
),
Expanded(
child: TrimmedImage(right.src, () => _advanceLeft()),
),
],
),
),
Text('Text 2, possibly lines and lines and lines and lines and lines anlines and lines of stuff.',
style: Theme.of(context).textTheme.title,
textAlign: TextAlign.center,
),
],
),
);
}
[...]
class TrimmedImage extends StatelessWidget {
final String src;
final onTap;
TrimmedImage(src, this.onTap);
#override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 3.0 / 4.0,
child: ConstrainedBox(
constraints: BoxConstraints.expand(),
child: Container(
decoration: new BoxDecoration(
border: new Border.all(
color: Colors.grey,
width: 2.0,
),
borderRadius:
new BorderRadius.circular(10.0),
),
child: GestureDetector(
onTap: onTap,
child: ClipRRect(
borderRadius: new BorderRadius.circular(8.0),
child: CachedNetworkImage(
placeholder: CircularProgressIndicator(),
errorWidget: Icon(Icons.broken_image),
imageUrl: src ?? '',
fit: BoxFit.cover,
),
),
),
),
),
);
}
In Landscape mode I'd like the images to be scaled down (while keeping the aspect ratio) to fit the height of the available space, rather than the width.
Portrait mode currently looks good on a big screen but I suspect it'd also fail if the screen were too small to fit the height of all 3 components.

Create your own layout with a CustomMultiChildLayout and, specifically, a MultiChildLayoutDelegate. The Flutter Shrine demo is a good example: https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart. See the _Heading and _HeadingLayout classes.

Related

SingleChildScroll adding a large inset on-top of the Keyboard

I'm trying to create a scrollable register form for a Flutter app. I got SingleChildScroll to work, but theres a large padding or inset or something on top of the keyboard that can block content. You can see if pretty well If I scroll all they way to the bottom.
Here's my code.
Widget _buildContainer() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipRRect(
// This Clips the border Radius of the Container
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
child: Container(
// This sets the attributes of the Container
height: MediaQuery.of(context).size.height * 0.69,
width: MediaQuery.of(context).size.width * 0.9,
decoration: const BoxDecoration(
color: Colors.white,
),
child: Scaffold(
body: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildFirstNameRow(),
_buildLastNameRow(),
_buildEmailRow(),
_buildPasswordRow(),
_buildConfirmPasswordRow(),
_buildRegisterButton(),
],
),
),
),
),
),
),
],
);
}
I've tried setting resizeToAvoidBottomInset: false, in the Scaffold, but that prevents any scrolling from happening.
I've also tried adding to Scaffold as well, but that only changes the padding around that mystery inset.
body: Padding(
padding: EdgeInsets.only(
bottom: 8.0),
Thanks for your Help!

Which widgets should be used for responsive UI of the following wireframe?

What widgets should be used to display 3 cartoons at the bottom of this picture and the text at the center responsively for different screen sizes?
Row with spaceEvenly
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
//your images
],
);
https://flutteragency.com/how-to-set-space-between-elements-in-flutter/
There are many ways to do this. It will also depend on what responsive behavior you are looking for. I hope, I understood your specific requirements.
Here is a solution:
class Page extends StatelessWidget {
const Page({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/bg.png"),
fit: BoxFit.cover,
),
),
child: Stack(children: [
Center(
child: FractionallySizedBox(
widthFactor: .4,
child: FittedBox(
fit: BoxFit.fitWidth,
child: Text(
'Headline text',
style: GoogleFonts.chewy(
textStyle: const TextStyle(
color: Colors.white,
letterSpacing: .5,
),
),
),
),
),
),
Container(
alignment: const Alignment(0, .75),
child: FractionallySizedBox(
widthFactor: .8,
heightFactor: .3,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Image.asset("assets/images/img1.png"),
Image.asset("assets/images/img2.png"),
Image.asset("assets/images/img3.png"),
],
),
),
)
]),
),
),
);
}
}
In this solution,
I use a main Container that will have your image as background.
I then use a Stack to position the headline and the images.
The headline is Centered and defined as a FittedBox inside a FractionallySizedBox. This allows me to have responsivity for the headline too.
Finally, for the list of images, I used a FractionallySizedBox to size the list and an aligned Container to position it within the stack. The images are then spread thanks to the use of MainAxisAlignment.spaceBetween.
So, as you see, I used the following widgets to responsively size and position my Widgets:
Position
Center
Container with the alignment property
Size
FractionallySizedBox

Expanded and flexible not filling entire row

Hello I have the below code
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.max,
children: [
// FlexibleWidget(),
ExpandedWidget(),
FlexibleWidget(),
],
),
Row(
children: [
ExpandedWidget(),
ExpandedWidget(),
],
),
Row(mainAxisSize: MainAxisSize.min,
children: [
FlexibleWidget(),
FlexibleWidget(),
],
),
Row(mainAxisSize: MainAxisSize.min,
children: [
FlexibleWidget(),
ExpandedWidget(),
],
),
],
),
);
}
}
class ExpandedWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.green,
border: Border.all(color: Colors.white),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Expanded',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
);
}
}
class FlexibleWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Flexible(
child: Container(
decoration: BoxDecoration(
color: Colors.red,
border: Border.all(color: Colors.white),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Flexible',
style: TextStyle(color: Colors.black, fontSize: 24),
),
),
),
);
}
}
And the result is this:
Shoudln't the first container fill the space left after the flexible widget is set? Since it also belongs to a column with a child of 2 expanded widgets, the column width size should be the entire screen. That means the row should be the entire screen too. I would have expected the expanded green widget in the first row to fill the row until there is no white left like the image below:
Instead it only fills half the page. Can anyone explain this behaviour?
Let me start with FlexFit before coming to the point. FlexFit.tight allows the children to fill rest of the space but FlexFit.loose allows children to size themselves as they want.
Now, as you might know Expanded is nothing but Flexible with tight-fitting i.e. fit: FlexFit.tight.
Expanded(
child: child,
)
is equivalent to
Flexible(
fit: FlexFit.tight,
child: child,
)
Now, Let's take your example.
For the first block, there are two widgets with Flex 1 (by default), which will divide the screen to two half; each will be given 50%. This does not mean that they have to take 50% of the screen. It means that they can expand to 50% of the screen. So, the Expanded widget does that. It takes 50% of the screen as it has tight-fitting.
Coming to the Flexible widget, by default, it follows loose-fitting. So it says to its children that they can expand till 50% but there is no boundation that they have to take all space. They can take whatever space they want but the upper bound is set to 50%. In your case, it only needs the space to fit "Flexible" text with some padding. So it takes that.
Pushpendra explained how flex works and why they take at most half the screen when using two flex items. However, there is no answer showing how to make one item fill the rest of the space including space that is not taken by the other item.
The trick is to only use a single flex item that takes all space (Expanded) and constraining the other item to maxWidth = half of the available width. This can be achived using LayoutBuilder to get the available width and ConstraintedBox to constrain one widget without using flex - which allows the Expanded to take more than half of the space (all that is not taken by the ConstrainedBox):
LayoutBuilder(builder: (context, constraints) {
return Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(maxWidth: constraints.maxWidth * 0.5),
child: Text(
text1,
overflow: TextOverflow.fade,
softWrap: false,
),
),
Expanded(
child: Text(
text2,
overflow: TextOverflow.fade,
softWrap: false,
),
),
],
);
});

Flutter Row widget solve RenderFlex overflow by cutting off remaining area

There are a lot of questions here already about Renderflex overflow, but I believe my use case might be a bit different.
Just the usual problem - having a Widget that is too big in a Row widget, and I get the A RenderFlex overflowed by X pixels ... error.
I want to create a Row that cuts off it's overflowing child Widget if they would be rendered outside it's area without getting an error.
First off, wrapping the last element in the Row widget with Expanded or Flexible does not work in my case, as recommended here and here and many other places. Please see code and image:
class PlayArea extends StatelessWidget {
#override
Widget build(BuildContext context) {
final dummyChild = Container(
color: Colors.black12,
width: 100,
child: Text('important text'),
);
final fadeContainer = Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Colors.black26,
Colors.black87,
],
),
),
width: 600,
);
return Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
color: Colors.redAccent,
child: Column(children: [
Expanded(
child: Row(
children: <Widget>[
dummyChild,
fadeContainer,
],
),
),
Expanded(
child: Row(
children: <Widget>[
dummyChild,
Expanded(
child: fadeContainer,
),
],
),
),
Expanded(
child: Row(
children: <Widget>[
Container(
color: Colors.black12,
width: 1100,
child: Text('important text'),
),
Expanded(
child: fadeContainer,
),
],
),
),
]),
),
);
}
}
Key points:
Using Expanded changes the width of Container, which changes the gradient's slope. I want to keep the gradient as it is
Even with Expanded widget, the Row is not prepared for the case when important text's area is too wide and does not fit the screen horizontally - it will get an overflow error for that Widget
it is technically working in the first case, because no red color is drawn on the right side on the green field, it 'just' has an error
How do I cut off the remaining space dynamically without any error - regardless of any screen size and content?
One solution I found is that
Row(
children: <Widget>[
dummyChild,
fadeContainer,
],
)
can be converted to
ListView(
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
children: <Widget>[
dummyChild,
fadeContainer,
],
)
Creating a horizontal list and preventing scroll on that.
edit.: just found out it that you'll get unbounded vertical height, so it's not the same.

Creating a Card with a center icon and bottom row

I'm new to Flutter and I'm attempting to create a Card that has a bottom row and a center Icon or Text. I'm sure this is pretty easy, but I'm struggling . I'm not sure of the best way to do this. Should I create a column with two nested columns?
import 'package:flutter/material.dart';
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Card(
elevation: 5,
margin: EdgeInsets.fromLTRB(16, 16, 16, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(
Icons.ac_unit,
color: Colors.blue,
),
Row(
children: <Widget>[
Expanded(
child: Container(
height: 50,
color: Colors.blue,
),
)
],
)
],
),
),
);
}
}
Try out this code.
Column(
children: <Widget>[
Expanded(
child: Center(
child: Text('\$250'),
),
),
Text('\$ Total'),
],
)
I'm using Expanded to use entire space of container (Column) which will push text to the bottom. And using Center to put a widget (Text in this code) in the middle of the container.