Home > Enterprise >  Make CircleBorder encapsulate the whole child widget
Make CircleBorder encapsulate the whole child widget

Time:03-15

I'm trying to make a circular ElevatedButton, but stuck on a formatting problem: the size of the border seems to only take into account the height of the child widget, not the width. To illustrate, this:

return ElevatedButton(
  style: ElevatedButton.styleFrom(
    shape: CircleBorder(),
  ),
  onPressed: () {},
  child: Text(
    "I want the circle border\nto encapsulate all the text"
  ),
);

Produces this result:

enter image description here

I'm trying to figure out how to make the circular border go around the whole text, without hard coding any fixed sizes or hacking it by using padding, because I want it to be responsive to changes in text content and size. How do I do this?

CodePudding user response:

Thanks to the comment by @pskink the solution was to tweak CircleBorder to use the longest side insead of shortest side when drawing the border path. Here it is:

enter image description here

class EncapsulatingCircularBorder extends OutlinedBorder {
  /// Create a circle border.
  ///
  /// The [side] argument must not be null.
  const EncapsulatingCircularBorder({ BorderSide side = BorderSide.none }) : assert(side != null), super(side: side);

  @override
  EdgeInsetsGeometry get dimensions {
    return EdgeInsets.all(side.width);
  }

  @override
  ShapeBorder scale(double t) => EncapsulatingCircularBorder(side: side.scale(t));

  @override
  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
    if (a is EncapsulatingCircularBorder)
      return EncapsulatingCircularBorder(side: BorderSide.lerp(a.side, side, t));
    return super.lerpFrom(a, t);
  }

  @override
  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
    if (b is EncapsulatingCircularBorder)
      return EncapsulatingCircularBorder(side: BorderSide.lerp(side, b.side, t));
    return super.lerpTo(b, t);
  }

  @override
  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {

    return Path()
      ..addOval(Rect.fromCircle(
        center: rect.center,
        // Changed this from rect.shortestSide to longestSide
        radius: math.max(0.0, rect.longestSide / 2.0 - side.width),
      ));
  }

  @override
  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {

    return Path()
      ..addOval(Rect.fromCircle(
        center: rect.center,
        // Changed this from rect.shortestSide to longestSide
        radius: rect.longestSide / 2.0,
      ));
  }

  @override
  EncapsulatingCircularBorder copyWith({ BorderSide? side }) {
    return EncapsulatingCircularBorder(side: side ?? this.side);
  }

  @override
  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
    switch (side.style) {
      case BorderStyle.none:
        break;
      case BorderStyle.solid:
        canvas.drawCircle(rect.center, (rect.shortestSide - side.width) / 2.0, side.toPaint());
    }
  }

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
      return false;
    return other is EncapsulatingCircularBorder
        && other.side == side;
  }

  @override
  int get hashCode => side.hashCode;

  @override
  String toString() {
    return '${objectRuntimeType(this, 'EncapsulatingCircularBorder')}($side)';
  }
}

CodePudding user response:

Adding StadiumBorder() will solve your issue.

ElevatedButton(
      style: ElevatedButton.styleFrom(
        shape: StadiumBorder(),
      ),
      onPressed: () {},
      child: Text(
        "I want the circle border\nto encapsulate all the text"
      ),
    );

How the button looks

  • Related