The understanding constraints article is very useful in understanding how layout works. However, I'm having trouble creating a custom layout that works the way it is explained there.
The article says that size flows up, first we obtain the sizes of the children and then we can decide on our own size based on the sizes of the children.
It seems CustomSingleChildLayout
is the way to create a custom layout with one child. In it, based on the article I mentioned above, I would like to get my child's size, and then decide on my own size based on that. However, this seems to be impossible. getSize()
method is called with the constraints, therefore I'm supposed to decide on my widgets size based on the constraints that I receive from above. I am able to obtain the child's size in getPositionForChild()
, but this called after I give my size in getSize()
. It made sense to trigger a relayout at this point, but it seems I'm not allowed to do that from a function called by the layout system.
So, the question is, what's the best way to implement a custom layout, with the layout behavior of constraints flowing down and sizes flowing up, as explained in understanding constraints?
Edit: Here is some code to explain what I really want.
This is the look that I'm going for. Here, I'm manually supplying the width and height of the blue square. I don't want to do that. I want the child's size to determine the size of the square. I want the size to flow up from the text and the blue square should decide on an edge length of max(width, height)
.
https://codepen.io/gazialankus/pen/abdKyVR
import 'package:flutter/material.dart';
class AppRoot extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', // Appears in app switcher
theme: ThemeData(
primaryColor: Colors.grey.shade300,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyPage(),
);
}
}
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.yellow,
child: SizedBox(
width: 180,
height: 180,
child: Container(
color: Colors.blue,
child: AspectRatio(
aspectRatio: 1,
child: Text(
"I want to be in a tight square.",
style: TextStyle(
backgroundColor: Colors.red
),
),
),
),
),
),
),
);
}
}
void main() {
runApp(AppRoot());
}
It was suggested that I could wrap it with Align
. Here I tried, but Align
grows up. Align
's widthFactor
and heightFactor
make use of the child's size, but can't make it a square that way. Removing the blue Container
has no effect:
https://codepen.io/gazialankus/pen/MWKXvpO
import 'package:flutter/material.dart';
class AppRoot extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', // Appears in app switcher
theme: ThemeData(
primaryColor: Colors.grey.shade300,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyPage(),
);
}
}
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.yellow,
child: Align(
alignment: Alignment.center,
child: Container(
color: Colors.blue,
child: Text(
"I want to be in a square.",
style: TextStyle(
backgroundColor: Colors.red
),
),
),
),
),
),
);
}
}
void main() {
runApp(AppRoot());
}
Here I'm trying to use AspectRatio
to make the square, but it does not care about the child's size and just grows:
https://codepen.io/gazialankus/pen/KKVevXv
import 'package:flutter/material.dart';
class AppRoot extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', // Appears in app switcher
theme: ThemeData(
primaryColor: Colors.grey.shade300,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyPage(),
);
}
}
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.yellow,
child: Align(
alignment: Alignment.center,
child: Container(
color: Colors.blue,
child: AspectRatio(
aspectRatio: 1,
child: Text(
"I want to be in a tight square.",
style: TextStyle(
backgroundColor: Colors.red
),
),
),
),
),
),
),
);
}
}
void main() {
runApp(AppRoot());
}
And here I'm trying to do a custom layout, but I don't get to use the child's size to decide on my size. I have to decide on my size before receiving my child's. Trying to force a relayout once I learn my child's size throws an exception, which is at the end of the code. Codepen fails silently.
https://codepen.io/gazialankus/pen/LYGrjxM
import 'package:flutter/material.dart';
class AppRoot extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', // Appears in app switcher
theme: ThemeData(
primaryColor: Colors.grey.shade300,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyPage(),
);
}
}
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.yellow,
child: CustomSingleChildLayout(
delegate: MyDelegate(relayout: ValueNotifier<int>(0)),
child: Text(
"I want to be in a square.",
style: TextStyle(
backgroundColor: Colors.red
),
),
),
),
),
);
}
}
class MyDelegate extends SingleChildLayoutDelegate {
ValueNotifier<int> relayout;
MyDelegate({ this.relayout }) : super(relayout: relayout);
@override
bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {
return desiredWidth == 100;
}
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
print('getConstraintsForChild');
return super.getConstraintsForChild(constraints);
}
double desiredWidth = 300;
@override
Offset getPositionForChild(Size size, Size childSize) {
print('getPositionForChild');
print(size);
print(childSize);
if (size.width > childSize.width) {
desiredWidth = childSize.width;
relayout.value++;
// ^trying to force a relayout
// throws exception, output is given at the bottom in comments
print("RELAYOUT");
}
return super.getPositionForChild(size, childSize);
}
@override
Size getSize(BoxConstraints constraints) {
print('getSize');
return Size(desiredWidth, 100);
}
}
void main() {
runApp(AppRoot());
}
/*
Performing hot reload...
Syncing files to device LG H990...
I/flutter (19850): getSize
I/flutter (19850): getConstraintsForChild
I/flutter (19850): getPositionForChild
I/flutter (19850): Size(300.0, 100.0)
I/flutter (19850): Size(148.0, 16.0)
════════ Exception caught by foundation library ════════════════════════════════════════════════════
The following assertion was thrown while dispatching notifications for ValueNotifier<int>:
'package:flutter/src/rendering/object.dart': Failed assertion: line 1527 pos 12: '_debugCanPerformMutations': is not true.
Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=BUG.md
When the exception was thrown, this was the stack:
#2 RenderObject.markNeedsLayout (package:flutter/src/rendering/object.dart:1527:12)
#3 RenderBox.markNeedsLayout (package:flutter/src/rendering/box.dart:2053:11)
#4 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:207:21)
#5 ValueNotifier.value= (package:flutter/src/foundation/change_notifier.dart:274:5)
#6 MyDelegate.getPositionForChild (package:m_health/ui/app_root.dart:64:16)
...
The ValueNotifier<int> sending notification was: ValueNotifier<int>#ac2fb(1)
════════════════════════════════════════════════════════════════════════════════════════════════════
Reloaded 2 of 522 libraries in 1,390ms.
I/flutter (19850): RELAYOUT
*/
This should resolve your issue. We remove the constraints passed down by using the UnconstrainedBox
. We only need to remove the vertical so that the AspectRatio
uses the width constraint. Then we ask the sub-tree to be sized by its intrinsic size. This is, of course, now easy to overflow because it will never wrap and will always use its intrinsic width. However you can wrap the UnconstrainedBox
in a FittedBox
and it will never overflow and instead scale to fit a parent that might be smaller than it's intrinsic dimension. You might also want to try setting the axis parameter on the UnconstrainedBox
to horizontal. Then the IntrinsicWidth will be constrained to the parent width.
import 'package:flutter/material.dart';
class AppRoot extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', // Appears in app switcher
theme: ThemeData(
primaryColor: Colors.grey.shade300,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyPage(),
);
}
}
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.yellow,
child: UnconstrainedBox(
child: IntrinsicWidth(
child: Container(
color: Colors.blue,
child: AspectRatio(
aspectRatio: 1,
child: Text(
"I want to be in a tight square.",
style: TextStyle(backgroundColor: Colors.red),
),
),
),
),
),
),
),
);
}
}
void main() {
runApp(AppRoot());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With