4

I have created a stateless widget, and I want to define a baseline for it, so that I can use it with the Baseline widget (https://docs.flutter.io/flutter/widgets/Baseline-class.html).

How can I do that?

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133

1 Answers1

7

If you want to define a baseline you can't do it directly in the stateless widget. You need to mess around its corresponding RenderBox that needs to implement the computeDistanceToActualBaseline() method.

Give a look to the ListTile implementation here. You will see that the _RenderListTile RenderBox implements the above method returning the baseline of the title widget.

  @override
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    assert(title != null);
    final BoxParentData parentData = title.parentData;
    return parentData.offset.dy + title.getDistanceToActualBaseline(baseline);
  }

In this case, the baseline of the title is the bottom of the Text widget.

All this is needed because the Baseline widget tries to get the baseline of the child widget. If you don't provide a explicit baseline with the above method, it just uses its bottom position.

You can find below an example of a BaselineBox where you can set an arbitrary baseline from top.

import 'package:flutter/widgets.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';

class BaselineBox extends SingleChildRenderObjectWidget {
  const BaselineBox({Key key, @required this.baseline, Widget child})
      : assert(baseline != null),
        super(key: key, child: child);

  final double baseline;

  @override
  RenderBaselineBox createRenderObject(BuildContext context) =>
      new RenderBaselineBox(baseline: baseline);

  @override
  void updateRenderObject(
      BuildContext context, RenderBaselineBox renderObject) {
    renderObject.baseline = baseline;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DoubleProperty('baseline', baseline));
  }
}

class RenderBaselineBox extends RenderProxyBox {
  RenderBaselineBox({
    RenderBox child,
    @required double baseline,
  })  : assert(baseline != null),
        assert(baseline >= 0.0),
        assert(baseline.isFinite),
        _baseline = baseline,
        super(child);

  double get baseline => _baseline;
  double _baseline;

  set baseline(double value) {
    assert(value != null);
    assert(value >= 0.0);
    assert(value.isFinite);
    if (_baseline == value) return;
    _baseline = value;
    markNeedsLayout();
  }

  @override
  double computeDistanceToActualBaseline(TextBaseline baselineType) {
    return _baseline;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DoubleProperty('baseline', baseline));
  }
}
chemamolins
  • 19,400
  • 5
  • 54
  • 47
  • I see that `ListTile` returns a `InkWell` containing a `_ListTile`, which is a `RenderObjectWidget`, which creates a `_ListTileElement` and a `_RenderListTile`. And the `_RenderListTile` extends `RenderBox`. Does that mean MyWidget should be a `RenderObjectWidget`, which creates a `_MyWidgetElement` and a `_RenderMyWidget` that extends `RenderBox`? It there an easier way? – Marcelo Glasberg Jul 01 '18 at 19:57
  • Ideally, I could have a `AddBaseline` widget that I could use like this: `widget = AddBaseline(baseline:15.0, child: wiget);` – Marcelo Glasberg Jul 01 '18 at 20:04
  • I have edited the answer and added an implementation of a `BaselineBox`. – chemamolins Jul 01 '18 at 22:57
  • Wow, great. But I'm getting this weird error: `The following assertion was thrown during performLayout():` `Failed assertion: boolean expression must not be null` `(package:flutter/src/rendering/box.dart:1636:50)` Which is here: `_cachedBaselines.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline));` (in box.dart) – Marcelo Glasberg Jul 02 '18 at 00:16
  • @MarcG initialize this `final bool fromBottom;` – Blasanka Jul 02 '18 at 04:17
  • I will have a look when a I get home. For the moment I have replaced 100 with `child.size.height` inside `computeDistanceToActualBaseline()`. I had forgotten it after doing some tests. – chemamolins Jul 02 '18 at 05:54
  • Finally this should work. I have removed the `fromBottom` flag because it requires dealing with the child height and that can't be simply done by overriding the `computeDistanceToActualBaseline()` method. – chemamolins Jul 02 '18 at 14:30
  • Works perfectly. Thanks for the great answer! – Marcelo Glasberg Jul 02 '18 at 21:16
  • Nice work @chemamolins, I haven't tried it yet. But looks fine to me, what about negative baseline, that should be possible. Is there a particular reason you limit it to >= 0.0? – Simon Jul 03 '18 at 12:12
  • No special reason. Just submitted it here as an example. I'm sure it could be enhanced with more options as well. – chemamolins Jul 03 '18 at 13:41