Bringing CSS "float" to Flutter text rendering

·

3 min read

When displaying text in your Flutter app, you may notice that Flutter's text rendering capabilities are lacking some features commonly used in web pages, such as CSS float, which is used to place an image or other element on the left or right side of the text, allowing the text to wrap around it.

For example, let's say we're starting with some rich text, and we want to add a drop-cap for the first letter, and float a couple images in the text.

float_column.png

To accomplish this, we could resort to using a package such as webview_flutter to display the text in a platform native web view using HTML and CSS, but that comes with its own complications and limitations — and after all, we are writing a Flutter app, not a web app — it would be better if the text and floated items could be rendered with widgets!

FloatColumn to the rescue

We can accomplish our goal with the help of the float_column package, whose only dependency is Flutter, so it works seamlessly on all Flutter platforms: Android, iOS, Linux, macOS, Web, and Windows.

This is the code we're starting with — basically, just some rich text:

Text.rich(
  TextSpan(
    children: [
      TextSpan(text: '“This is what you shall do...'),
      ...
    ],
  ),
),

First, let's add the drop cap

  1. Import the float_column package.

  2. Wrap the Text.rich in a FloatColumn widget.

  3. Add a WidgetSpan and Floatable for the drop cap widget.

    The float: FCFloat.start causes the child DropCap widget to float at the start of the text so the text can wrap around it.

  4. Remove the '“T' from the TextSpan.

// 1. Import the `float_column` package.
import 'package:float_column/float_column.dart';

// 2. Wrap the `Text.rich` in a `FloatColumn` widget.
FloatColumn(
  children: [
    Text.rich(
      TextSpan(
        children: [

          // 3. Add a `WidgetSpan` and `Floatable` for the drop cap widget.
          WidgetSpan(
            child: Floatable(
              float: FCFloat.start,
              child: DropCap('“T', height: 3),
          ),

          // 4. Remove the '“T' from the TextSpan.
          TextSpan(text: 'his is what you shall do...'),
        ],
      ),
    ),
  ],
)

Which gives us this:

Simulator Screen Shot - iPhone SE (3rd generation) - 2022-07-09 at 11.02.37.png

Next, let's add the floating images

This involves adding a WidgetSpan and Floatable for each image widget.

For the first image, we're setting float: FCFloat.end. Because the current text direction is left-to-right (LTR), the image is floated to the right side of the text. If the current text direction was RTL it would float to the left side. The possible FCFloat values are none, left, right, start, and end.

We're also setting clear: FCClear.both for both images. Like the CSS clear property, this makes sure the floating widget is placed below floating widgets on both sides. The possible FCClear values are none, left, right, start, end, and both.

The clearMinSpacing property provides a way to add or subtract additional spacing in relation to the cleared position. A positive value moves the widget down, and a negative value moves it up.

import 'package:float_column/float_column.dart';

FloatColumn(
  children: [
    Text.rich(
      TextSpan(
        children: [
          WidgetSpan(
            child: Floatable(
              float: FCFloat.start,
              child: DropCap('“T', height: 3),
          ),

          // Add a `WidgetSpan` and `Floatable` for each floating image:

          WidgetSpan(
            child: Floatable(
              float: FCFloat.end,
              clear: FCClear.both,
              clearMinSpacing: 16,
              maxWidthPercentage: 0.33,
              padding: EdgeInsetsDirectional.only(start: 8),
              child: Img(name: 'walt.jpg', title: 'Walt Whitman'),
            ),
          ),

          WidgetSpan(
            child: Floatable(
              float: FCFloat.start,
              clear: FCClear.both,
              clearMinSpacing: 60,
              maxWidthPercentage: 0.33,
              padding: EdgeInsetsDirectional.only(end: 12),
              child: Img(name: 'jeremy.jpg', title: 'Photo by...'),
            ),
          ),

          TextSpan(text: 'his is what you shall do...'),
        ],
      ),
    ),
  ],
)

Which accomplishes our goal:

Simulator Screen Shot - iPhone SE (3rd generation) - 2022-07-09 at 10.14.54 copy.png

Try it out with Flutter Web:

ronjb.github.io/float_column

For the complete source code for this example (with some tweaks so it looks good on screens of any size), see:

github.com/ronjb/float_column/example/lib/pages/basic_ltr.dart

And for info about the float_column package see: