Let wrap take full width - flutter

How can I make the red box to use full width and put the button to the very right of the screen when wrapping?
Also I want spaceBetween if the button is not wrapping, but it does not work:
This is what I have sofar:
Column(
children: [
Align(
alignment: Alignment.topLeft,
child: Container(
color: Colors.red,
child: Wrap(
alignment: WrapAlignment.end,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Please wrap on small screens",
style: Theme.of(context).textTheme.bodyText1,
),
),
),
TextButton(
onPressed: () {},
child: Text("Bearbeiten"))
],
),
),
),
// ...
]
)

Updated
Finally, I manage to solve your problem. Probably is not a canonic way, but it's working.
You need to compute the total width of your content in order to compare it with the width of the screen (the width is the width of the widgets, plus space you want between widgets, plus padding from both sides).
With that, you are able to know when you need to change the layout, and you can apply the layout you want to each case.
Here I leave the code:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey textKey = GlobalKey();
final GlobalKey buttonKey = GlobalKey();
double widthLimit = double.infinity;
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
final textBox = textKey.currentContext.findRenderObject() as RenderBox;
final buttonBox = buttonKey.currentContext.findRenderObject() as RenderBox;
widthLimit = textBox.size.width + buttonBox.size.width + 8 + 16 * 2;
});
}
Widget _wrap(bool spaceBetween, Widget child) => spaceBetween
? child
: Align(
alignment: Alignment.centerRight,
child: child,
);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(
width: double.infinity,
padding: EdgeInsets.all(16.0),
color: Colors.red,
child: Builder(builder: (context) {
final screenWidth = MediaQuery.of(context).size.width;
final spaceBetween = screenWidth >= widthLimit;
return Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.spaceBetween,
children: [
Text("Please wrap on small screen small screen", key: textKey),
_wrap(
spaceBetween,
TextButton(
key: buttonKey,
onPressed: () {},
child: Text("Bearbeiten"),
)),
],
);
}),
),
),
);
}
}
Old Answer
I am going to take a couple of premises from your screenshots:
The Wrap doesn't have to take the full width by default
You only want to go to two lines when there is not enough space in the device screen.
With that, you can make what you want with this code:
Container(
padding: EdgeInsets.all(16.0),
color: Colors.red,
child: Wrap(
spacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.end,
children: [
Text("Please wrap on small screen"),
TextButton(onPressed: () {}, child: Text("Bearbeiten")),
]),
),
I added the spacing property to the Wrap to provide space as you wanted when both widgets are at the same line. Also, I set the alignment of the Wrap to end to align as you want.
When there is not enough space on the screen, the widget is going to move to a new line and align to the end. As the first widget is longer than the wrapped one and, as I said in the premises, we don't need to take the full width of the screen, Wrap will adapt to keep things aligned properly.

Related

Flutter animated sliver header

I am trying to create a profile header sliver that can animate.
If you consider above image, Section 1 is what we see in the fully expanded sliver, and Section 2 is what we want to see in pinned mode.
Now I would like transition to move the image - purple circle - to the side, shrink it slightly, and also move the name and the links.
I can achieve all of that but one thing: How to center them in the expanded view.
As I have to use transform to move widgets around, I cannot simply use a centring widget like column or center. And I didn't find a way to calculate the exact position to center the widget, as it needs the size of the widget, that I don't have.
Firstly I am using SliverPersistentHeaderDelegate and it provides shrinkOffset that will be used on linear interpolation(lerp method).
Then CompositedTransformTarget widget to follow the center widget.
On this example play with targetAnchor and followerAnchor and use t/shrinkOffset to maintain other animation.
class SFeb223 extends StatelessWidget {
const SFeb223({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
delegate: MySliverPersistentHeaderDelegate(),
pinned: true,
),
SliverToBoxAdapter(
child: SizedBox(
height: 1333,
),
)
],
),
);
}
}
class MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
final LayerLink layerLink = LayerLink();
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
double t = shrinkOffset / maxExtent;
return Material(
color: Colors.cyanAccent.withOpacity(.2),
child: Stack(
children: [
Align(
alignment:
Alignment.lerp(Alignment.center, Alignment.centerLeft, t)!,
child: CompositedTransformTarget(
link: layerLink,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: lerpDouble(100, kToolbarHeight - 10, t),
width: lerpDouble(100, kToolbarHeight - 10, t),
decoration: const ShapeDecoration(
shape: CircleBorder(),
color: Colors.deepPurple,
),
),
),
),
),
CompositedTransformFollower(
link: layerLink,
targetAnchor: Alignment.lerp(
Alignment.bottomCenter, Alignment.centerRight, t)!,
followerAnchor:
Alignment.lerp(Alignment.topCenter, Alignment.centerLeft, t)!,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Column(
children: [Text("Sheikh")],
),
),
),
),
],
),
);
}
#override
double get maxExtent => kToolbarHeight * 6;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
false;
}

How to scale a Text widget that would behave like a scaling of an image

I would like to perform an Hero transition that will change, at the end, the size of a Widget.
This widget contains some text.
By default, the text widget will resize and the text inside move and resize to fit the text widget.
I would like to make to whole widget behave like an image would do : Everything will scale (zoom).
I tried :
auto_size_text package : The text will still move and the result is not perfect
screenshot package : It take too long to generate the image, replace the current widget with the image before performing the hero transition.
I am thinking about RenderRepaintBoundary, but this seems a lot of work for a simple task.
Any idea ?
If I understand what you want to achieve, you may want to use FittedBox.
This is what I used to create the animation below, where the Text widgets have a different size between the beginning and the end of the animation:
Thanks to #Romain, the easy answer was indeed FittedBox.
Making a Hero transition that will change the size of a Text Widget will be smooth when I put a FittedBox on the second page.
But I needed to pass down the original size to the second page to make the Text inside the FittedBox appear on the same number of lines that it was previously displayed.
Here the result :
https://vimeo.com/346745092
Here the code :
import 'package:flutter/material.dart';
const String textThatCouldChangeDependingOnContext = "Hero Text .... ";
main() {
runApp(MaterialApp(home: MyHomePage()));
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
GlobalKey _textKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(64.0),
child: Column(
children: <Widget>[
Hero(
tag: 'tag',
child: Material(
child: Container(
color: Colors.red,
child: Text(textThatCouldChangeDependingOnContext,
key: _textKey),
),
),
),
FlatButton(
child: Text('Fly'),
onPressed: () {
Size originalTextSize = _textKey.currentContext.size;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
MySecondPage(originalTextSize)));
},
)
],
),
),
),
);
}
}
class MySecondPage extends StatefulWidget {
final Size originalTextSize;
MySecondPage(this.originalTextSize);
#override
_MySecondPageState createState() => _MySecondPageState();
}
class _MySecondPageState extends State<MySecondPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
GestureDetector(
onTap: () => Navigator.pop(context),
child: Hero(
tag: 'tag',
transitionOnUserGestures: true,
child: Material(
child: Container(
color: Colors.red,
child: FittedBox(
fit: BoxFit.contain,
child: SizedBox(
height: widget.originalTextSize.height,
width: widget.originalTextSize.width,
child: Text(textThatCouldChangeDependingOnContext),
),
),
),
),
),
)
],
),
),
);
}
}

ListView or SingleChildScrollView of variable size

I want to have a widget of variable height that contains a ListView or a SingleChildScrollView or anything that scrolls.
I tried making it like this:
Container(
color: Colors.pink,
child: Column(
children: [
Container(color: Colors.orange, child: Text("Header")),
SingleChildScrollView(
child: Container(
height: 10000,
color: Colors.green,
child: Text("the height of this content could be anything")),
),
Container(color: Colors.blue, child: Text("Footer")),
],
),
)
This causes an overflow because the SingleChildScrollView expands to height of 10000 pixels. If I enclose it in an Expanded then it works fine but then if its child's height is for example 200 instead of 10000, it will still expand the parent widget to the entire height of the screen.
Is it possible to have the height of the scroll/list adjust itself to its content and only expand to the entire screen if it needs to?
You can do it if you know the size of the footer and header widget and using LayoutBuilder widget to get the constraints.
#override
Widget build(BuildContext newcontext) {
return Center(
child: Scaffold(
body: Container(
color: Colors.pink,
child: LayoutBuilder(
builder: (_, constraints) {
final sizeHeader = 150.0;
final sizeFooter = 150.0;
final sizeList = 1000.0;
final available =
constraints.maxHeight - (sizeHeader + sizeFooter);
Widget _buildCenterWidget() {
return Container(
height: sizeList,
color: Colors.green,
child: Text("the height of this content could be anything"),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
height: sizeHeader,
color: Colors.orange,
child: Text("Header")),
available < sizeList
? Expanded(
child: _buildCenterWidget(),
)
: _buildCenterWidget(),
Container(
height: sizeFooter,
color: Colors.blue,
child: Text("Footer")),
],
);
},
)),
),
);
}
You can use ConstrainedBox, to specify minHeight, maxHeight for your widget. Remember that none of your widget should have infinite height/width, that spoils the UI, may also throw error

Specific min and max size for expanded widgets in Column

I have a Column with a set of Expanded widgets.
Is there a way to control the range in which they expand? I want one widget to expand only to a certain size and make the rest available to other widgets.
EDIT:
Because I got two probably misleading answers, I’d like to clarify. I want something like this:
Expanded(flex: 1, minSize: 50, maxSize: 200, child: ...)
That means that this expanded widget takes a flex of 1, but should never be smaller than 50 and bigger than 200.
When using ConstrainedBox in Rows my minWidth is ignored and the maxWidth is used as a fixed size.
You are looking for ConstrainedBox.
You can create a List of Widgets with both ConstrainedBox and Expanded, as following:
Row(
children: [
ConstrainedBox(
child: Container(color: Colors.red),
constraints: BoxConstraints(
minWidth: 50,
maxWidth: 100,
),
),
Expanded(
child: Container(color: Colors.green),
),
Expanded(
child: Container(color: Colors.blue),
),
],
),
As far as I know, there's no elegant pre-built way in Flutter to do this.
The answer by #HugoPassos is only partially complete. A ConstrainedBox will not change its size unless its content changes size. I believe what you're looking for is for the box to be say 1 / 4 of the width of row if 1/4 of the row is greater than the min and higher than the max.
Here's a working main.dart that get's the job done with width in a row, though you could just as easily use height in a column:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({required this.title});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: LayoutBuilder(builder: (context, constraints) {
return Center(
child: Row(
children: [
ConstrainedWidthFlexible(
minWidth: 50,
maxWidth: 200,
flex: 1,
flexSum: 4,
outerConstraints: constraints,
child: SizeLogger(
child: Container(
color: Colors.red,
width: Size.infinite.width,
height: Size.infinite.height,
child: Text('click me to log my width')),
),
),
Flexible(
flex: 1,
fit: FlexFit.tight,
child: Container(color: Colors.green),
),
Flexible(
flex: 2,
fit: FlexFit.tight,
child: Container(color: Colors.blue),
),
],
));
}));
}
}
class SizeLogger extends StatelessWidget {
final Widget child;
SizeLogger({required this.child});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => {print('context.size!.width ${context.size!.width}')},
child: child);
}
}
class ConstrainedWidthFlexible extends StatelessWidget {
final double minWidth;
final double maxWidth;
final int flex;
final int flexSum;
final Widget child;
final BoxConstraints outerConstraints;
ConstrainedWidthFlexible(
{required this.minWidth,
required this.maxWidth,
required this.flex,
required this.flexSum,
required this.outerConstraints,
required this.child});
#override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(
minWidth: minWidth,
maxWidth: maxWidth,
),
child: Container(
width: _getWidth(outerConstraints.maxWidth),
child: child,
),
);
}
double _getWidth(double outerContainerWidth) {
return outerContainerWidth * flex / flexSum;
}
}
In short: there is no simple answer without calulating the size.
First you need to know: Widget with Size dominate the avialable size in Row/Column, then Flexiable/Expanded share the remaining space.
Column(
children:[
Flexiable(...
Expanded(...
SizedBox(... // <- dominate the avialable size first
]
)
And the parent widget dominate the size of the child widget:
Column(
children:[
Flexiable(flex: 1),
Flexiable(
flex: 1,
child: SizedBox(... // size can't be larger than 1/2
]
)
It is the choise problem if the size exceed or insufficient. I can show some simple examples below:
(BTW: I replace ConstraintedBox with SizedBox because we only use maxWidth/maxHeight. check Understanding constraints)
Flex with max size
In this case is simple and can use only Flexible + SizedBox
Row(
children: [
Flexible(flex: 1, child: _textWidget('Flex:1')),
Flexible(
flex: 1,
child: SizedBox(
width: 300,
child: _textWidget('Flex: 1, max: 300'),
),
),
],
),
Flex with min/max size
For the case need the total size(from LayoutBuilder) and the percentage of the widget size.
LayoutBuilder(
builder: (context, constraint) {
final maxWidth = constraint.maxWidth;
return Row(
children: [
Flexible(flex: 1, child: _textWidget('Flex:1')),
SizedBox(
width: (maxWidth / 3).clamp(200, 300),
child: _textWidget('Flex:1, min: 200, max: 300'),
),
SizedBox(
width: (maxWidth / 3).clamp(200, 300),
child: _textWidget('Flex:1, min: 200, max: 300'),
),
],
);
}
)
Code Example
https://dartpad.dev/?id=f098f9764acda1bcc58017aa0bc0ec09
Yes! There is a way to control maxHeight and maxWidth inside a Row or Column (unbounded Widgets). You could use the Widget LimitedBox in which your maxHeight and maxWidth parameters only works inside unbounded Widgets.
https://api.flutter.dev/flutter/widgets/LimitedBox-class.html
Column(
children: [
LimitedBox(
maxHeight: 200,
maxWidth: 200,
child: Container(),
)
],
),
This worked for me. Please, Check it out.
Expanded(
child: Align(
alignment: Alignment.topCenter,
child: Container(
child: ConstrainedBox(
constraints:
BoxConstraints(maxHeight: 500),
child: Container(
child: DesiredWidget(),
),
),
),
),
)
Instead of directly expanding the desired widget, you should expand and align a container, then set the constrainedbox as a child of the container and then insert the desired widget as a child of the constrainedbox.
This way i managed to render the widget precisely as big as it needs to be, but never exceeding 500 height.
You can use constraint box to use the range of min and max width like below:
Row(
children: <Widget>[
Text("Text 1"),
ConstrainedBox(
constraints: BoxConstraints(maxHeight: 30, maxWidth: 40, minWidth: 30),
),
Text("Text 2")
],
)

Flutter : Vertically center column

How to vertically center a column in Flutter? I have used widget "new Center". I have used widget "new Center", but it does not vertically center my column ? Any ideas would be helpful....
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Thank you"),
),
body: new Center(
child: new Column(
children: <Widget>[
new Padding(
padding: new EdgeInsets.all(25.0),
child: new AnimatedBuilder(
animation: animationController,
child: new Container(
height: 175.0,
width: 175.0,
child: new Image.asset('assets/angry_face.png'),
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * 6.3,
child: _widget,
);
},
),
),
new Text('We are glad we could serve you...', style: new TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w600,
color: Colors.black87),),
new Padding(padding: new EdgeInsets.symmetric(vertical: 5.0, horizontal: 0.0)),
new Text('We appreciate your feedback ! !', style: new TextStyle(
fontSize: 13.0,
fontWeight: FontWeight.w200,
color: Colors.black87),),
],
),
),
);
}
Solution as proposed by Aziz would be:
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
//your widgets here...
],
)
It would not be in the exact center because of padding:
padding: EdgeInsets.all(25.0),
To make exactly center Column - at least in this case - you would need to remove padding.
Try:
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children:children...)
Try this one. It centers vertically and horizontally.
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
)
With Column, use:
mainAxisAlignment: MainAxisAlignment.center
It align its children(s) to center of its parent Space vertically
You control how a row or column aligns its children using the mainAxisAlignment and crossAxisAlignment properties. For a row, the main axis runs horizontally and the cross axis runs vertically. For a column, the main axis runs vertically and the cross axis runs horizontally.
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
While using Column, use this inside the column widget :
mainAxisAlignment: MainAxisAlignment.center
It align its children(s) to the center of its parent Space is its main axis i.e. vertically
or,
wrap the column with a Center widget:
Center(
child: Column(
children: <ListOfWidgets>,
),
)
if it doesn't resolve the issue wrap the parent container with a Expanded widget..
Expanded(
child:Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
),
)
Another Solution!
If you want to set widgets in center vertical form, you can use ListView for it.
for eg: I used three buttons and add them inside ListView which followed by
shrinkWrap: true -> With this ListView only occupies the space which needed.
import 'package:flutter/material.dart';
class List extends StatelessWidget {
#override
Widget build(BuildContext context) {
final button1 =
new RaisedButton(child: new Text("Button1"), onPressed: () {});
final button2 =
new RaisedButton(child: new Text("Button2"), onPressed: () {});
final button3 =
new RaisedButton(child: new Text("Button3"), onPressed: () {});
final body = new Center(
child: ListView(
shrinkWrap: true,
children: <Widget>[button1, button2, button3],
),
);
return new Scaffold(
appBar: new AppBar(
title: Text("Sample"),
),
body: body);
}
}
void main() {
runApp(new MaterialApp(
home: List(),
));
}
Output:
CrossAlignment.center is using the Width of the 'Child Widget' to center itself and hence gets rendered at the start of the page.
When the Column is centered within the page body's 'Center Container' , the CrossAlignment.center uses page body's 'Center' as reference and renders the widget at the center of the page
Code
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
title:"DynamicWidgetApp",
home:DynamicWidgetApp(),
));
class DynamicWidgetApp extends StatefulWidget{
#override
DynamicWidgetAppState createState() => DynamicWidgetAppState();
}
class DynamicWidgetAppState extends State<DynamicWidgetApp>{
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
//Removing body:Center will change the reference
// and render the widget at the start of the page
child: Column(
mainAxisAlignment : MainAxisAlignment.center,
crossAxisAlignment : CrossAxisAlignment.center,
children: [
Text("My Centered Widget"),
]
),
),
floatingActionButton: FloatingActionButton(
// onPressed: ,
child : Icon(Icons.add),
),
);
}
}
For me the problem was there was was Expanded inside the column which I had to remove and it worked.
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded( // remove this
flex: 2,
child: Text("content here"),
),
],
)
You could use.
mainAxisAlignment:MainAxisAlignment.center
This will the material through the center in the column wise.
`crossAxisAlignment: CrossAxisAlignment.center'
This will align the items in the center in the row wise.
Container( alignment:Alignment.center, Child: Column () )
Simply use.
Center ( Child: Column () )
or rap with Padding widget . And adjust the Padding such the the column children are in the center.
You can also wrap the Column widget by Align.
Align(
alignment: Alignment.center,
child: Column(
children: [
Container(
width: 300,
margin: const EdgeInsets.fromLTRB(0, 70, 0, 0),
child: TextFormField(decoration: const InputDecoration(hintText: "First Name"))
),
...
]
)
)
Checkout this website for different ways of centering a widget: Link
In Addition, If you used
mainAxisAlignment: MainAxisAlignment.start
for centering all children but you still one of the children to be centered , Simply use Center() widget on the children.