Flutter optionally include / exclude parent widget in widget tree - flutter

I have this widget tree
return Container(
color: Colors.blue,
child: Container(
child: Text("Child"),
),
);
Is there a way to remove a parent widget from the tree or conditionally include it?
For example if a state variable such as includeBlueContainer was false, I would like to not render the blue container (but show everything else).

I couldn't achieve an optionally include reusable widget but I've been using this pattern which does achieve what I wanted to achieve. I haven't given this a great amount of thought but I still feel there is a better solution somewhere.
class MyContainer extends StatelessWidget {
final Widget child;
final bool isIncluded;
MyContainer({this.child, this.isIncluded = true});
Widget build(BuildContext context) {
if (!isIncluded) return child;
return Container(
color: Colors.blue,
child: child,
);
}
}

Edit: I made a package out of this: https://pub.dev/packages/conditional_parent_widget
Which you can use like:
import 'package:flutter/widgets.dart';
import 'package:conditional_parent_widget/conditional_parent_widget.dart';
// ...
return ConditionalParentWidget(
condition: includeBlueContainer,
child: Text("Child"),
parentBuilder: (Widget child) => Container(
color: Colors.blue,
child: child,
),
);
Internally it is just:
import 'package:flutter/widgets.dart';
class ConditionalParentWidget extends StatelessWidget {
const ConditionalParentWidget({
Key? key,
required this.condition,
required this.child,
required this.parentBuilder,
}) : super(key: key);
final Widget child;
final bool condition;
final Widget Function(Widget child) parentBuilder;
#override
Widget build(BuildContext context) {
return condition ? this.parentBuilder(this.child) : this.child;
}
}

Related

How to access parent widget inside child widget?

I have a widget:
class MyWidget extends StatelessWidget {
final Color? color;
final EdgeInsetsGeometry? padding;
final Widget child;
// ... many more properties
const MyWidget({
super.key,
this.color = Colors.blue,
this.padding = const EdgeInsets.all(4),
required this.child,
});
#override
Widget build(BuildContext context) {
return Container(
color: color,
padding: padding,
child: child,
);
}
}
I also have another widget which wraps the above widget in SliverToBoxAdapter like this:
class MySliverWidget extends MyWidget {
MySliverWidget({
super.color,
super.padding,
required super.child,
// ... many more properties
});
#override
Widget build(BuildContext context) {
// Unable to use "child: super".
return SliverToBoxAdapter(child: super);
}
}
But the problem is I am unable to use the parent widget (MyWidget) instance using super inside the child widget (MySliverWidget).
So, the question is how do I access MyWidget instance inside MySliverWidget?
Note:
In MySliverWidget.build() method, I don't want to use child: MyWidget(...) and pass all the parameters to it (which is redundant).
I also don't to simply use SliverToBoxAdapter(child: MyWidget(...)) instead of having a MySliverWidget in the first place.
Maybe like this :
return SliverToBoxAdapter(child: super.build(context));

Substitute a child of a widget by another widget / optionally wrapping a widget

Is it possible to "merge" two widgets together? I want to build an OptionalWrapper component, that wraps a widget by another widget only if a condition is matched.
The usage should look something like this, I provide a child and if the showWrapper condition is true, this child is put as the child of the wrapper widget.
OptionalWrapper(
showWrapper: !isSmallScreen(context),
wrapper: Padding(
padding: const EdgeInsets.only(top: 16.0),
child: // <-- Here should the child be rendered
),
child: Text("Hello"),
),
of course I could do something like this:
!isSmallScreen(context) ? Padding(
padding: const EdgeInsets.all(16.0),
child: Text("Hello"),
) : Text("Hello"),
But here I declare my Text widget twice which I want to avoid.
The implementation would look like this:
import 'package:flutter/material.dart';
class OptionalWrapper extends StatelessWidget {
final bool showWrapper;
final Widget wrapper;
final Widget child;
const OptionalWrapper({
Key? key,
required this.showWrapper,
required this.wrapper,
required this.child,
}) : super(key: key);
#override
Widget build(BuildContext context) {
if (showWrapper) {
// return the wrapper widget but subject child as child of the wrapper widget
} else {
return child;
}
}
}
You can create builder method like
import 'package:flutter/material.dart';
class OptionalWrapper extends StatelessWidget {
final bool showWrapper;
final Widget Function(Widget) wrapper;
final Widget child;
const OptionalWrapper({
Key? key,
required this.showWrapper,
required this.wrapper,
required this.child,
}) : super(key: key);
#override
Widget build(BuildContext context) {
if (showWrapper) {
return wrapper(child);
} else {
return child;
}
}
}
You can make the padding as EdgeInsects.zero if the condition fails
Padding(
padding: !isSmallScreen(context)?const EdgeInsets.all(16.0): EdgeInsects.zero,
child: Text("Hello"),

CheckBox ui is not updated though the item value of checkbox is updated, Getx flutter

Get the working code sample here
I have an RxList of addOnProducts which contains product and selected attributes.
I am trying to implement the simple multiSelectable grid View, but on clicking the checkBox the selected attribute changes but it is not reflected back to the ui,
If i refresh it will be updated.
I tried Obx()=> (); widget , It is still not updating
My ProductController
class ProductsController extends GetxController {
late Worker worker;
static ProductsController instance = Get.find();
RxList<ProductModel> products = RxList<ProductModel>([]);
RxList<CheckProduct> addOnProducts = <CheckProduct>[].obs;
String collection = "products";
#override
void onReady() {
super.onReady();
products.bindStream(getAllProducts());
worker = once(products, (List<ProductModel> value) {
fillAddOnProducts(value);
}, condition: () => products.isNotEmpty);
}
Stream<List<ProductModel>> getAllProducts() => FirebaseFirestore.instance
.collection(collection)
.snapshots()
.map((query) => query.docs
.map((item) => ProductModel.fromMap(item.data(), item.id))
.toList());
void fillAddOnProducts(List<ProductModel> products) => {
products.forEach((element) {
addOnProducts.add(CheckProduct(product: element, selected: false));
})
};
}
class CheckProduct {
ProductModel product;
bool selected;
CheckProduct(
{required ProductModel this.product, required bool this.selected});
}
My Grid View
class AddOns extends StatelessWidget {
const AddOns({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [],
title: Text("Select Addons"),
),
body: Obx(() => GridView.count(
crossAxisCount: 2,
children: productsController.addOnProducts
.map((element) => ProductWidget(product: element))
.toList(),
)));
}
}
class ProductWidget extends StatelessWidget {
final CheckProduct product;
const ProductWidget({Key? key, required this.product}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
margin: EdgeInsets.all(10),
child: Stack(
alignment: Alignment.center,
children: [
Positioned(
top: 4,
left: 4,
child: Checkbox(
value: product.selected,
onChanged: (value) {
print("value of the value is : $value");
print("value of product selected before is: " +
product.selected.toString());
product.selected = value!;
print("value of product selected after is: " +
product.selected.toString());
},
),
),
],
));
}
}
Therefore in the console it is :
I/flutter (20067): value of the value is : true
I/flutter (20067): value of product selected before is: false
I/flutter (20067): value of product selected after is: true
But the checkBox is not updating, it updates only when i refresh, How to overCome this? Adding Obx() to the parent isn't helping..
Find the github link to code below here which has just the question and and the problem faced..
After going through your code. I've implemented the following that will change state without hot reload:
In your main dart you do not need to put your product controller here as you are not using it
main.dart
import 'package:flutter/material.dart';
import 'grid.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: GridSelect(),
);
}
}
Next, I have changed your grid class to generate a list of product widget as the size of the addProduct list length. In my opinion this is a better way to write GridView counts children. Remove obx from your gridview and change your stateful widget to stateless as you are using Getx. It will manage your state even in a stateless widget. Add your product controller here as you will access addProduct list from the controller class.
grid.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:test_project/controllers/productController.dart';
import 'package:test_project/productWidget.dart';
class GridSelect extends StatelessWidget {
final _controller = Get.put(ProductController());
GridSelect({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.count(
crossAxisCount: 2,
children: List.generate(_controller.addOnProducts.length, (index) => ProductWidget(index: index))
),
);
}
}
In your product controller class, remove the instance as it is not important. That is the only change here:
ProductController.dart
import 'package:get/get.dart';
import 'package:test_project/models/productModel.dart';
class ProductController extends GetxController {
RxList<CheckProduct> addOnProducts = <CheckProduct>[].obs;
#override
void onReady() {
super.onReady();
addOnProducts.add(CheckProduct(product: ProductModel('productOne', 20)));
addOnProducts.add(CheckProduct(product: ProductModel('productTwo', 25)));
addOnProducts.add(CheckProduct(product: ProductModel('productThree', 30)));
addOnProducts.add(CheckProduct(product: ProductModel('productFour', 40)));
}
}
class CheckProduct {
ProductModel product;
RxBool selected = false.obs;
CheckProduct({
required this.product,
});
}
Lastly, your productWidget class needs a required value index. So, the widget knows which index in gridview the user is clicking and use Obx() here in checkbox as you have an observable value selected here. Remember to always use Obx() when you have an obs value. This will update the widget whenever an obs value changes. Here, if you notice we are using Get.find() instead of Put as Get.put is already inside the scope so all you need to do is find the controller that you will use. You can find or put multiple controllers and update values as much as you want.
productWidget.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:test_project/controllers/productController.dart';
class ProductWidget extends StatelessWidget {
final ProductController _controller = Get.find();
final int index;
ProductWidget({Key? key, required this.index}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
margin: EdgeInsets.all(20),
child: Stack(
alignment: Alignment.center,
children: [
Positioned(
top: 4,
left: 4,
child: Obx(()=>Checkbox(
value: _controller.addOnProducts[index].selected.value,
onChanged: (value) {
print("value of the value is : $value");
print("value of product selected before is: " +
_controller.addOnProducts[index].selected.toString());
_controller.addOnProducts[index].selected.value = value!;
print("value of product selected after is: " +
_controller.addOnProducts[index].selected.toString());
},
)),
)
],
),
);
}
}
Go through GetX documentation for proper use of GetX. Even though I have 2 apps in Playstore with GetX, I still go through documentation from time to time. They have a clear documentation on how to manage state.
In ProductWidget adding an additional Obx() solved my problem
class ProductWidget extends StatelessWidget {
final CheckProduct product;
const ProductWidget({Key? key, required this.product}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
margin: EdgeInsets.all(10),
child: Stack(
alignment: Alignment.center,
children: [
Positioned(
top: 4,
left: 4,
// Even the child needs Obx() ; The parent's Obx() is not reflected here
child: Obx(()=>(Checkbox(
value: product.selected,
onChanged: (value) {
print("value of the value is : $value");
print("value of product selected before is: " +
product.selected.toString());
product.selected = value!;
print("value of product selected after is: " +
product.selected.toString());
},
),))
),
],
));
}

Creating custom widgets with multiple constructors

So I am trying to create a custom widget in flutter that has multiple constructors.
Essentially I am creating an arrow button but want to be able to create the button with arrow icons facing in different directions.
Currently my code looks like this:
class RotateButton extends StatefulWidget {
RotateButton.left() {
_RotateButtonState createState() => _RotateButtonState.left();
}
RotateButton.right() {
_RotateButtonState createState() => _RotateButtonState.right();
}
#override
_RotateButtonState createState() => _RotateButtonState();
}
class _RotateButtonState extends State<RotateButton> {
IconData icon;
_RotateButtonState();
_RotateButtonState.left() {
icon = Icons.arrow_back;
}
_RotateButtonState.right() {
icon = Icons.arrow_forward;
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: kPrimaryColor,
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Icon(
icon,
size: 70,
),
),
);
}
}
Every time I use my widget it just defaults to the default constructor and shows no Icon child.
Is there a way to build a class without making a default constructor.
Also is there a way I can build this widget without using a stateful widget as it kind of just overcomplicates it.
I am getting a message that says:
The declaration 'createState' isn't referenced
This message is coming up next to the named constructors in the rotatebutton class.
Any help is very much appreciated.
Use something like this:
class SomeWidget extends StatelessWidget {
final bool isRight;
const SomeWidget({Key key, this.isRight}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Icon(isRight?Icons.arrow_forward:Icons.arrow_back),
);
}
}

How to get height of a Widget?

I don't understand how LayoutBuilder is used to get the height of a Widget.
I need to display the list of Widgets and get their height so I can compute some special scroll effects. I am developing a package and other developers provide widget (I don't control them). I read that LayoutBuilder can be used to get height.
In very simple case, I tried to wrap Widget in LayoutBuilder.builder and put it in the Stack, but I always get minHeight 0.0, and maxHeight INFINITY. Am I misusing the LayoutBuilder?
EDIT: It seems that LayoutBuilder is a no go. I found the CustomSingleChildLayout which is almost a solution.
I extended that delegate, and I was able to get the height of widget in getPositionForChild(Size size, Size childSize) method. BUT, the first method that is called is Size getSize(BoxConstraints constraints) and as constraints, I get 0 to INFINITY because I'm laying these CustomSingleChildLayouts in a ListView.
My problem is that SingleChildLayoutDelegate getSize operates like it needs to return the height of a view. I don't know the height of a child at that moment. I can only return constraints.smallest (which is 0, the height is 0), or constraints.biggest which is infinity and crashes the app.
In the docs it even says:
...but the size of the parent cannot depend on the size of the child.
And that's a weird limitation.
To get the size/position of a widget on screen, you can use GlobalKey to get its BuildContext to then find the RenderBox of that specific widget, which will contain its global position and rendered size.
Just one thing to be careful of: That context may not exist if the widget is not rendered. Which can cause a problem with ListView as widgets are rendered only if they are potentially visible.
Another problem is that you can't get a widget's RenderBox during build call as the widget hasn't been rendered yet.
But what if I need to get the size during the build! What can I do?
There's one cool widget that can help: Overlay and its OverlayEntry.
They are used to display widgets on top of everything else (similar to stack).
But the coolest thing is that they are on a different build flow; they are built after regular widgets.
That have one super cool implication: OverlayEntry can have a size that depends on widgets of the actual widget tree.
Okay. But don't OverlayEntry requires to be rebuilt manually?
Yes, they do. But there's another thing to be aware of: ScrollController, passed to a Scrollable, is a listenable similar to AnimationController.
Which means you could combine an AnimatedBuilder with a ScrollController, it would have the lovely effect to rebuild your widget automatically on a scroll. Perfect for this situation, right?
Combining everything into an example:
In the following example, you'll see an overlay that follows a widget inside ListView and shares the same height.
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final controller = ScrollController();
OverlayEntry sticky;
GlobalKey stickyKey = GlobalKey();
#override
void initState() {
if (sticky != null) {
sticky.remove();
}
sticky = OverlayEntry(
builder: (context) => stickyBuilder(context),
);
SchedulerBinding.instance.addPostFrameCallback((_) {
Overlay.of(context).insert(sticky);
});
super.initState();
}
#override
void dispose() {
sticky.remove();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
if (index == 6) {
return Container(
key: stickyKey,
height: 100.0,
color: Colors.green,
child: const Text("I'm fat"),
);
}
return ListTile(
title: Text(
'Hello $index',
style: const TextStyle(color: Colors.white),
),
);
},
),
);
}
Widget stickyBuilder(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (_,Widget child) {
final keyContext = stickyKey.currentContext;
if (keyContext != null) {
// widget is visible
final box = keyContext.findRenderObject() as RenderBox;
final pos = box.localToGlobal(Offset.zero);
return Positioned(
top: pos.dy + box.size.height,
left: 50.0,
right: 50.0,
height: box.size.height,
child: Material(
child: Container(
alignment: Alignment.center,
color: Colors.purple,
child: const Text("^ Nah I think you're okay"),
),
),
);
}
return Container();
},
);
}
}
Note:
When navigating to a different screen, call following otherwise sticky would stay visible.
sticky.remove();
This is (I think) the most straightforward way to do this.
Copy-paste the following into your project.
UPDATE: using RenderProxyBox results in a slightly more correct implementation, because it's called on every rebuild of the child and its descendants, which is not always the case for the top-level build() method.
NOTE: This is not exactly an efficient way to do this, as pointed by Hixie here. But it is the easiest.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
typedef void OnWidgetSizeChange(Size size);
class MeasureSizeRenderObject extends RenderProxyBox {
Size? oldSize;
OnWidgetSizeChange onChange;
MeasureSizeRenderObject(this.onChange);
#override
void performLayout() {
super.performLayout();
Size newSize = child!.size;
if (oldSize == newSize) return;
oldSize = newSize;
WidgetsBinding.instance!.addPostFrameCallback((_) {
onChange(newSize);
});
}
}
class MeasureSize extends SingleChildRenderObjectWidget {
final OnWidgetSizeChange onChange;
const MeasureSize({
Key? key,
required this.onChange,
required Widget child,
}) : super(key: key, child: child);
#override
RenderObject createRenderObject(BuildContext context) {
return MeasureSizeRenderObject(onChange);
}
#override
void updateRenderObject(
BuildContext context, covariant MeasureSizeRenderObject renderObject) {
renderObject.onChange = onChange;
}
}
Then, simply wrap the widget whose size you would like to measure with MeasureSize.
var myChildSize = Size.zero;
Widget build(BuildContext context) {
return ...(
child: MeasureSize(
onChange: (size) {
setState(() {
myChildSize = size;
});
},
child: ...
),
);
}
So yes, the size of the parent cannot can depend on the size of the child if you try hard enough.
Personal anecdote - This is handy for restricting the size of widgets like Align, which likes to take up an absurd amount of space.
Here's a sample on how you can use LayoutBuilder to determine the widget's size.
Since LayoutBuilder widget is able to determine its parent widget's constraints, one of its use case is to be able to have its child widgets adapt to their parent's dimensions.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var dimension = 40.0;
increaseWidgetSize() {
setState(() {
dimension += 20;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(children: <Widget>[
Text('Dimension: $dimension'),
Container(
color: Colors.teal,
alignment: Alignment.center,
height: dimension,
width: dimension,
// LayoutBuilder inherits its parent widget's dimension. In this case, the Container in teal
child: LayoutBuilder(builder: (context, constraints) {
debugPrint('Max height: ${constraints.maxHeight}, max width: ${constraints.maxWidth}');
return Container(); // create function here to adapt to the parent widget's constraints
}),
),
]),
),
floatingActionButton: FloatingActionButton(
onPressed: increaseWidgetSize,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Demo
Logs
I/flutter (26712): Max height: 40.0, max width: 40.0
I/flutter (26712): Max height: 60.0, max width: 60.0
I/flutter (26712): Max height: 80.0, max width: 80.0
I/flutter (26712): Max height: 100.0, max width: 100.0
Update: You can also use MediaQuery to achieve similar function.
#override
Widget build(BuildContext context) {
var screenSize = MediaQuery.of(context).size ;
if (screenSize.width > layoutSize){
return Widget();
} else {
return Widget(); /// Widget if doesn't match the size
}
}
Let me give you a widget for that
class SizeProviderWidget extends StatefulWidget {
final Widget child;
final Function(Size) onChildSize;
const SizeProviderWidget(
{Key? key, required this.onChildSize, required this.child})
: super(key: key);
#override
_SizeProviderWidgetState createState() => _SizeProviderWidgetState();
}
class _SizeProviderWidgetState extends State<SizeProviderWidget> {
#override
void initState() {
///add size listener for first build
_onResize();
super.initState();
}
void _onResize() {
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
if (context.size is Size) {
widget.onChildSize(context.size!);
}
});
}
#override
Widget build(BuildContext context) {
///add size listener for every build uncomment the fallowing
///_onResize();
return widget.child;
}
}
EDIT
Just wrap the SizeProviderWidget with OrientationBuilder to make it respect the orientation of the device
I made this widget as a simple stateless solution:
class ChildSizeNotifier extends StatelessWidget {
final ValueNotifier<Size> notifier = ValueNotifier(const Size(0, 0));
final Widget Function(BuildContext context, Size size, Widget child) builder;
final Widget child;
ChildSizeNotifier({
Key key,
#required this.builder,
this.child,
}) : super(key: key) {}
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
notifier.value = (context.findRenderObject() as RenderBox).size;
},
);
return ValueListenableBuilder(
valueListenable: notifier,
builder: builder,
child: child,
);
}
}
Use it like this
ChildSizeNotifier(
builder: (context, size, child) {
// size is the size of the text
return Text(size.height > 50 ? 'big' : 'small');
},
)
If I understand correctly, you want to measure the dimension of some arbitrary widgets, and you can wrap those widgets with another widget. In that case, the method in the this answer should work for you.
Basically the solution is to bind a callback in the widget lifecycle, which will be called after the first frame is rendered, from there you can access context.size. The catch is that you have to wrap the widget you want to measure within a stateful widget. And, if you absolutely need the size within build() then you can only access it in the second render (it's only available after the first render).
findRenderObject() returns the RenderBox which is used to give the size of the drawn widget and it should be called after the widget tree is built, so it must be used with some callback mechanism or addPostFrameCallback() callbacks.
class SizeWidget extends StatefulWidget {
#override
_SizeWidgetState createState() => _SizeWidgetState();
}
class _SizeWidgetState extends State<SizeWidget> {
final GlobalKey _textKey = GlobalKey();
Size textSize;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => getSizeAndPosition());
}
getSizeAndPosition() {
RenderBox _cardBox = _textKey.currentContext.findRenderObject();
textSize = _cardBox.size;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Size Position"),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
"Currern Size of Text",
key: _textKey,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
SizedBox(
height: 20,
),
Text(
"Size - $textSize",
textAlign: TextAlign.center,
),
],
),
);
}
}
Output:
There is no direct way to calculate the size of the widget, so to find that we have to take the help of the context of the widget.
Calling context.size returns us the Size object, which contains the height and width of the widget. context.size calculates the render box of a widget and returns the size.
Checkout https://medium.com/flutterworld/flutter-how-to-get-the-height-of-the-widget-be4892abb1a2
In cases where you don't want to wait for a frame to get the size, but want to know it before including it in your tree:
The simplest way is to follow the example of the BuildOwner documentation.
With the following you can just do
final size = MeasureUtil.measureWidget(MyWidgetTree());
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
/// Small utility to measure a widget before actually putting it on screen.
///
/// This can be helpful e.g. for positioning context menus based on the size they will take up.
///
/// NOTE: Use sparingly, since this takes a complete layout and sizing pass for the subtree you
/// want to measure.
///
/// Compare https://api.flutter.dev/flutter/widgets/BuildOwner-class.html
class MeasureUtil {
static Size measureWidget(Widget widget, [BoxConstraints constraints = const BoxConstraints()]) {
final PipelineOwner pipelineOwner = PipelineOwner();
final _MeasurementView rootView = pipelineOwner.rootNode = _MeasurementView(constraints);
final BuildOwner buildOwner = BuildOwner(focusManager: FocusManager());
final RenderObjectToWidgetElement<RenderBox> element = RenderObjectToWidgetAdapter<RenderBox>(
container: rootView,
debugShortDescription: '[root]',
child: widget,
).attachToRenderTree(buildOwner);
try {
rootView.scheduleInitialLayout();
pipelineOwner.flushLayout();
return rootView.size;
} finally {
// Clean up.
element.update(RenderObjectToWidgetAdapter<RenderBox>(container: rootView));
buildOwner.finalizeTree();
}
}
}
class _MeasurementView extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
final BoxConstraints boxConstraints;
_MeasurementView(this.boxConstraints);
#override
void performLayout() {
assert(child != null);
child!.layout(boxConstraints, parentUsesSize: true);
size = child!.size;
}
#override
void debugAssertDoesMeetConstraints() => true;
}
This creates an entirely new render tree separate from the main one, and wont be shown on your screen.
So for example
print(
MeasureUtil.measureWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(Icons.abc),
SizedBox(
width: 100,
),
Text("Moin Meister")
],
),
),
),
);
Would give you Size(210.0, 24.0)
Might be this could help
Tested on Flutter: 2.2.3
Copy Below code this in your project.
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class WidgetSize extends StatefulWidget {
final Widget child;
final Function onChange;
const WidgetSize({
Key? key,
required this.onChange,
required this.child,
}) : super(key: key);
#override
_WidgetSizeState createState() => _WidgetSizeState();
}
class _WidgetSizeState extends State<WidgetSize> {
#override
Widget build(BuildContext context) {
SchedulerBinding.instance!.addPostFrameCallback(postFrameCallback);
return Container(
key: widgetKey,
child: widget.child,
);
}
var widgetKey = GlobalKey();
var oldSize;
void postFrameCallback(_) {
var context = widgetKey.currentContext;
if (context == null) return;
var newSize = context.size;
if (oldSize == newSize) return;
oldSize = newSize;
widget.onChange(newSize);
}
}
declare a variable to store Size
Size mySize = Size.zero;
Add following code to get the size:
child: WidgetSize(
onChange: (Size mapSize) {
setState(() {
mySize = mapSize;
print("mySize:" + mySize.toString());
});
},
child: ()
This is Remi's answer with null safety, since the edit queue is full, I have to post it here.
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
final controller = ScrollController();
OverlayEntry? sticky;
GlobalKey stickyKey = GlobalKey();
#override
void initState() {
sticky?.remove();
sticky = OverlayEntry(
builder: (context) => stickyBuilder(context),
);
SchedulerBinding.instance
.addPostFrameCallback((_) => Overlay.of(context)?.insert(sticky!));
super.initState();
}
#override
void dispose() {
sticky?.remove();
super.dispose();
}
#override
Widget build(BuildContext context) => Scaffold(
body: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
if (index == 6) {
return Container(
key: stickyKey,
height: 100.0,
color: Colors.green,
child: const Text("I'm fat"),
);
}
return ListTile(
title: Text(
'Hello $index',
style: const TextStyle(color: Colors.white),
),
);
},
),
);
Widget stickyBuilder(BuildContext context) => AnimatedBuilder(
animation: controller,
builder: (_, Widget? child) {
final keyContext = stickyKey.currentContext;
if (keyContext != null) {
final box = keyContext.findRenderObject() as RenderBox;
final pos = box.localToGlobal(Offset.zero);
return Positioned(
top: pos.dy + box.size.height,
left: 50.0,
right: 50.0,
height: box.size.height,
child: Material(
child: Container(
alignment: Alignment.center,
color: Colors.purple,
child: const Text("Nah I think you're okay"),
),
),
);
}
return Container();
},
);
}
use the package: z_tools.
The steps:
1. change main file
void main() async {
runZoned(
() => runApp(
CalculateWidgetAppContainer(
child: Center(
child: LocalizedApp(delegate, MyApp()),
),
),
),
onError: (Object obj, StackTrace stack) {
print('global exception: obj = $obj;\nstack = $stack');
},
);
}
2. use in function
_Cell(
title: 'cal: Column-min',
callback: () async {
Widget widget1 = Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 100,
height: 30,
color: Colors.blue,
),
Container(
height: 20.0,
width: 30,
),
Text('111'),
],
);
// size = Size(100.0, 66.0)
print('size = ${await getWidgetSize(widget1)}');
},
),
The easiest way is to use MeasuredSize it's a widget that calculates the size of it's child in runtime.
You can use it like so:
MeasuredSize(
onChange: (Size size) {
setState(() {
print(size);
});
},
child: Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
);
You can find it here: https://pub.dev/packages/measured_size
It's easy and still can be done in StatelessWidget.
class ColumnHeightWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final scrollController = ScrollController();
final columnKey = GlobalKey();
_scrollToCurrentProgress(columnKey, scrollController);
return Scaffold(
body: SingleChildScrollView(
controller: scrollController,
child: Column(
children: [],
),
),
);
}
void _scrollToCurrentProgress(GlobalKey<State<StatefulWidget>> columnKey,
ScrollController scrollController) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final RenderBox renderBoxRed =
columnKey.currentContext.findRenderObject();
final height = renderBoxRed.size.height;
scrollController.animateTo(percentOfHeightYouWantToScroll * height,
duration: Duration(seconds: 1), curve: Curves.decelerate);
});
}
}
in the same manner you can calculate any widget child height and scroll to that position.
**Credit to #Manuputty**
class OrigChildWH extends StatelessWidget {
final Widget Function(BuildContext context, Size size, Widget? child) builder;
final Widget? child;
const XRChildWH({
Key? key,
required this.builder,
this.child,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return OrientationBuilder(builder: (context, orientation) {
return ChildSizeNotifier(builder: builder);
});
}
}
class ChildSizeNotifier extends StatelessWidget {
final ValueNotifier<Size> notifier = ValueNotifier(const Size(0, 0));
final Widget Function(BuildContext context, Size size, Widget? child) builder;
final Widget? child;
ChildSizeNotifier({
Key? key,
required this.builder,
this.child,
}) : super(key: key);
#override
Widget build(BuildContext context) {
WidgetsBinding.instance!.addPostFrameCallback(
(_) {
notifier.value = (context.findRenderObject() as RenderBox).size;
},
);
return ValueListenableBuilder(
valueListenable: notifier,
builder: builder,
child: child,
);
}
}
**Simple to use:**
OrigChildWH(
builder: (context, size, child) {
//Your child here: mine:: Container()
return Container()
})