'Wrapping text inside a circle (Flutter)

is it possible to wrap text inside a circle in Flutter? Here is a failed example. Ideally the text would fit inside the circle and overflow at the end only.

ClipOval(
  child: SizedBox.expand(
    child: Text(
      "Round and round the rugged rocks, the rascally rascals ran. Round and round the rugged rocks, the rascally rascals ran. Round and round the rugged rocks, the rascally rascals ran. Round and round the rugged rocks, the rascally rascals ran. ",
      softWrap: true,
      overflow: TextOverflow.fade,
    ),
  ),
),

enter image description here



Solution 1:[1]

As of now (July 2019), Flutter does not directly support laying out text in non-rectangular shapes.

Using existing API, it should be possible to achieve a similar custom effect by implementing something that performs the following steps:

  • Create a column.
  • Layout single line text with width of circle at line 1
  • Get the character index of the last laid out character (using getBoxesForRange and getPositionForOffset)
  • Truncate the front of the text you want to layout at the index to obtain remaining text.
  • Layout the remaining text as a single line text with width of circle at line 2. The y position at line 2 can be obtained by adding the height of the single line 1.
  • Repeat until no more text or circle is filled. Place all text within the column centered.

That should be able to get you something close. The implementation will have to handle all of the calculations of the widths of the circle at each y-position as well as manually position each line of text.

Solution 2:[2]

Something like this.

import 'dart:math' as math;
import 'package:flutter/material.dart';

class CircleTextWrapper extends StatelessWidget {
  const CircleTextWrapper({
    Key? key,
    this.radius = 110,
    this.text =
        "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s.",
    this.textStyle = const TextStyle(fontSize: 20),
    this.startAngle = 0,
  }) : super(key: key);
  final double radius;
  final String text;
  final double startAngle;
  final TextStyle textStyle;
  @override
  Widget build(BuildContext context) => CustomPaint(
        painter: _Painter(
          radius,
          text,
          textStyle,
        ),
      );
}

class _Painter extends CustomPainter {
  _Painter(this.radius, this.text, this.textStyle, {this.padding = 12});
  final double radius;
  final String text;
  final double padding;

  final TextStyle textStyle;
  final _textPainter = TextPainter(textDirection: TextDirection.ltr);
  final Paint _paint = Paint()
    ..blendMode
    ..color = Colors.white
    ..strokeWidth = 2
    ..style = PaintingStyle.stroke;
  @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(size.width / 2, size.height / 2);
    canvas.drawCircle(Offset.zero, radius + padding, _paint);

    String lineText = "";
    _textPainter.text = TextSpan(text: lineText, style: textStyle);
    _textPainter.layout(
      minWidth: 0,
      maxWidth: double.maxFinite,
    );

    double y = -radius + _textPainter.height * .6;
    double x = math.sqrt(radius * radius - y * y);
    for (int i = 0; i < text.length; i++) {
      lineText += text[i];
      _textPainter.text = TextSpan(text: lineText, style: textStyle);
      _textPainter.layout(
        minWidth: 0,
        maxWidth: double.maxFinite,
      );

      if (_textPainter.width >=
          (Offset(-x, y) - Offset(x, y)).distance - textStyle.fontSize! * .5) {
        _textPainter.paint(canvas, Offset(-x, y - _textPainter.height * .6));
        // canvas.drawLine(Offset(-x, y), Offset(x, y), _paint);
        y += _textPainter.height;
        x = math.sqrt(radius * radius - y * y);
        lineText = "";
      }

      if (i == text.length - 1) {
        _textPainter.paint(canvas, Offset(-x, y - _textPainter.height * .6));
        //  canvas.drawLine(Offset(-x, y), Offset(x, y), _paint);
      }
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

Result:

screenshot of desired result, text contained within a circle

Solution 3:[3]

Solution for small text

I propose this solution, which works very well for small text:

ClipOval(
    child: Container(
        height: 30.0,
        margin: const EdgeInsets.all(20),
        width:  price.length * 10.0,
        decoration: BoxDecoration(color: Colors.white70,
        border: Border.all(color: Color(0x00ffffff), width: 0.0),
            borderRadius: BorderRadius.all(Radius.elliptical(price.length * 10.0, 30))), // this line makes the coffee.
        child: Center(child:Text(price, style: const TextStyle(color: Color(0xff2200ff))))
)),

NB: 0xff2200ff is the blue color, Colors.white70 is the background color.

If you want the shape to be very round like a circle just replace 30.0 by text.length * 10.0 for the height of container and border radius.

For those who want a solution working with long text you can take my solution and add your own function that will create a round shape of text (by adding \n) based on the length of the text.

enter image description here

I hope that it will help.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gary Qian
Solution 2 Jake
Solution 3 Antonin GAVREL