Writing custom Widgets in Flutter (Part 2.a) — ChildSize (with helpers)

Intro

  • it changes how Widget’s Element is destroyed
  • you need to add imperative code into declarative Widget structure
  • there is no ability to actually track size of the Widget and it needs to be pulled on demand
return ChildSize(
child: buildChildWidget(),
onChildSizeChanged: (size) => handleNewChildSize(size),
);

Brief theory

  • For each custom Widget we need to write its Element and (sometimes) RenderObject implementations
  • Element will inflate its children Widgets into a separate Elements and update/recreate them when needed
  • RenderObject has few important roles — children management, layout, paint and hit test (mouse pointers, touch events, etc.)

Code

class ChildSize extends SingleChildRenderObjectWidget {
final void Function(Size)? onChildSizeChanged;

const ChildSize({
Key? key,
Widget? child,
this.onChildSizeChanged,
}) : super(key: key, child: child);
}
class ChildSize extends SingleChildRenderObjectWidget {
// ...

@override
RenderObject createRenderObject(BuildContext context) {
return RenderChildSize().._widget = this;
}

@override
void updateRenderObject(BuildContext context, RenderChildSize renderObject) {
renderObject.._widget = this;
}
}
class RenderChildSize extends RenderBox
with RenderObjectWithChildMixin<RenderBox> {
var _widget = const ChildSize();
}
class RenderChildSize ... {
// ...
var _lastSize = Size.zero;

@override
void performLayout() {
final child = this.child;
if (child != null) {
child.layout(constraints, parentUsesSize: true);
size = child.size;
} else {
size = constraints.smallest;
}
if (_lastSize != size) {
_lastSize = size;
_widget.onChildSizeChanged?.call(_lastSize);
}
}
}
  • we must pass parentUsesSize = true to child’s layout function in order to get its size afterwards. Otherwise child.size will throw exception. Thanks to this flag Flutter can add additional optimisations.
  • there can be a case that even though we have a child Widget, there will be no child RenderObject for it. Not all Widgets have RenderObjects. In such case Flutter will try to provide us with nearest nested RenderObject if any exists.
class RenderChildSize ... {
// ...

@override
void paint(PaintingContext context, Offset offset) {
final child = this.child;
if (child != null) {
context.paintChild(child, offset);
}
}

@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return child?.hitTest(result, position: position) == true;
}
}

Other articles:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store