I'm using CompositedTransformTarget and CompositedTransformFollower to display an OverlayEntry. How can I position the CompositedTransformFollower relative to the CompositedTransformTarget, i.e. how can I align its bottom to the top-center of the target in order to display it horizontally centered above the target, while maintaining interactivity (i.e. hit tests on the child should work)?
I tried to calculate the offset to give to CompositedTransformFollower, but I cannot do the correct calculation, because at that time I don't have the size of the child.
Sample Code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(brightness: Brightness.light),
home: Scaffold(
appBar: AppBar(title: Text('test')),
body: Center(child: OverlayButton()),
),
);
}
}
class OverlayButton extends StatefulWidget {
@override
_OverlayButtonState createState() => _OverlayButtonState();
}
class _OverlayButtonState extends State<OverlayButton> {
OverlayEntry _overlayEntry;
final LayerLink _layerLink = LayerLink();
bool _overlayIsShown = false;
@override
void dispose() {
super.dispose();
if (_overlayIsShown) {
_hideOverlay();
}
}
void _showOverlay() {
if (_overlayIsShown) return;
_overlayEntry = _createOverlayEntry();
Overlay.of(context).insert(_overlayEntry);
_overlayIsShown = true;
}
void _hideOverlay() {
_overlayIsShown = false;
_overlayEntry.remove();
}
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _layerLink,
child: RaisedButton(child: Text('Open Overlay'), onPressed: _showOverlay),
);
}
OverlayEntry _createOverlayEntry() {
RenderBox renderBox = context.findRenderObject();
var anchorSize = renderBox.size;
return OverlayEntry(builder: (context) {
// TODO: dynamically use the correct child width / height for
// positioning us correctly on top + centered on the anchor
var childWidth = 200.0;
var childHeight = 40.0;
var childOffset =
Offset(-(childWidth - anchorSize.width) / 2, -(childHeight));
return Row(
children: <Widget>[
CompositedTransformFollower(
link: _layerLink,
offset: childOffset,
child: RaisedButton(
child: Text('close'),
onPressed: _hideOverlay,
),
),
],
);
});
}
}
General answer:
It looks like we want to know the exact size of a child, before deciding where to place the child. There's a widget made for this purpose, called CustomSingleChildLayout. Also, if you need to layout multiple children, you should check out the slightly more complex version, CustomMultiChildLayout.
For this case:
First you need to insert a CustomSingleChildLayout in the OverlayEntry you are building, for example, I added 2 lines here:
OverlayEntry _createOverlayEntry() {
RenderBox renderBox = context.findRenderObject();
var anchorSize = renderBox.size;
return OverlayEntry(builder: (context) {
return CompositedTransformFollower(
link: _layerLink,
child: CustomSingleChildLayout( // # Added Line 1 #
delegate: MyDelegate(anchorSize), // # Added Line 2 #
child: RaisedButton(
child: Text('close'),
onPressed: _hideOverlay,
),
),
);
});
}
Then let's create a delegate that handles your business logic. Notice I've also created a parameter to take in your "anchorSize":
class MyDelegate extends SingleChildLayoutDelegate {
final Size anchorSize;
MyDelegate(this.anchorSize);
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
// we allow our child to be smaller than parent's constraint:
return constraints.loosen();
}
@override
Offset getPositionForChild(Size size, Size childSize) {
print("my size: $size");
print("childSize: $childSize");
print("anchor size being passed in: $anchorSize}");
// todo: where to position the child? perform calculation here:
return Offset(anchorSize.width, childSize.height / 2);
}
@override
bool shouldRelayout(_) => true;
}
As you can see, in the getPositionForChild method, we are able to gather all the information needed for calculating an offset. After calculating, we can just return that offset and CustomSingleChildLayout will take care of the placement.
To expand on this idea, you probably don't even need CompositedTransformFollower anymore, you can just do a full-screen overlay and calculate the offset that way.
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