Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to position a CompositedTransformFollower based on its child's size?

Tags:

flutter

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,
            ),
          ),
        ],
      );
    });
  }
}
like image 996
nioncode Avatar asked Nov 15 '25 13:11

nioncode


1 Answers

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.

like image 176
user1032613 Avatar answered Nov 17 '25 08:11

user1032613



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!