41

How to decorate text stroke in Flutter? It's like -webkit-text-stroke - CSS

granoeste
  • 1,531
  • 3
  • 12
  • 15
  • Please check the docs: https://docs.flutter.io/ https://docs.flutter.io/flutter/painting/TextStyle-class.html – Arnold Parge Sep 03 '18 at 12:17
  • 1
    I confirmed the doc, but there was no description about what I wanted to do. Is there a way to do it without using TextStyle? – granoeste Sep 04 '18 at 01:01
  • I dont understand why you dont want to use TextStyle, if that give the desired output? – Arnold Parge Sep 04 '18 at 06:56
  • TextDecoration has lineThrough, overline and underline. But, it does not have text-stroke. Is it right? I want decorate text with border like [text-stroke](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-text-stroke) of css. Can it with TextStyle? – granoeste Sep 05 '18 at 02:25
  • here is the simple solution [solution](https://stackoverflow.com/a/64549730/11914296) – Luqman Tuke Jun 11 '22 at 11:29

7 Answers7

101

Stroke has been possible without workarounds since the addition of foreground paints in TextStyle. An explicit example of stroke under fill bordered text has been added in the TextStyle documentation: https://master-api.flutter.dev/flutter/painting/TextStyle-class.html#painting.TextStyle.6

This example is reproduced here:

enter image description here

Stack(
  children: <Widget>[
    // Stroked text as border.
    Text(
      'Greetings, planet!',
      style: TextStyle(
        fontSize: 40,
        foreground: Paint()
          ..style = PaintingStyle.stroke
          ..strokeWidth = 6
          ..color = Colors.blue[700],
      ),
    ),
    // Solid text as fill.
    Text(
      'Greetings, planet!',
      style: TextStyle(
        fontSize: 40,
        color: Colors.grey[300],
      ),
    ),
  ],
)

Stroke by itself is possible by removing the Stack and just using the first stroke Text widget by itself. The stroke/fill order can also be adjusted by swapping the first and second Text widget.

Gary Qian
  • 2,984
  • 3
  • 10
  • 7
  • Alas, this does not work with smileys. – Stéphane de Luca Sep 26 '21 at 20:32
  • Unfortunately, emojis are rendered as bitmaps and do not use stroke. – Gary Qian Sep 28 '21 at 00:33
  • This will only work in areas that can accept a `Stack()`. If you are working with something which requires a `Text()` widget, then see the below _shadow_ method. For example, if you are labeling a pie chart, and your text flows across multiple colors, and the chart only accepts a text and a `TextStyle()`. – Matthew Rideout Dec 08 '22 at 22:42
  • 4
    This no longer works on iOS since Flutter upgraded to use Impeller rendering engine. The shadows solution is fine. – Apetroaei Andrei Jun 22 '23 at 15:39
53

I was also looking for this, wasn't able to find it. But I did find a workaround using 4 shadows in the TextStyle:

Text("Border test",
    style: TextStyle(
      inherit: true,
      fontSize: 48.0,
      color: Colors.pink,
      shadows: [
        Shadow( // bottomLeft
          offset: Offset(-1.5, -1.5),
          color: Colors.white
        ),
        Shadow( // bottomRight
          offset: Offset(1.5, -1.5),
          color: Colors.white
        ),
        Shadow( // topRight
          offset: Offset(1.5, 1.5),
          color: Colors.white
        ),
        Shadow( // topLeft
          offset: Offset(-1.5, 1.5),
          color: Colors.white
        ),
      ]
    ),
);

I also opened an Issue on GitHub: https://github.com/flutter/flutter/issues/24108

PieterAelse
  • 3,578
  • 2
  • 23
  • 33
  • Stroke is possible to do without this workaround :) I posted how to achieve this in my answer. – Gary Qian Jul 26 '19 at 22:18
  • 1
    As of Flutter 2.5.1 (stable) for web target this solution works in both the HTML and CanvasKit renderer whereas the PaintingStyle.stroke method doesn't work in either. Note that after adding it to a TextStyle you have to restart (not just Hot Reload) to see it. – Pat9RB Oct 18 '21 at 18:21
  • 1
    The font looks messed up, the lines are not clean. – paakjis May 17 '22 at 11:38
6

Inspired by this article, to achieve the effect, I prefer to use a technique that mixes two Text widgets and TextStype.foreground property with custom Paint():

class StrokeText extends StatelessWidget {
  final String text;
  final double fontSize;
  final FontWeight fontWeight;
  final Color color;
  final Color strokeColor;
  final double strokeWidth;

  const StrokeText(
    this.text, {
    Key key,
    this.fontSize,
    this.fontWeight,
    this.color,
    this.strokeColor,
    this.strokeWidth,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Text(
          text,
          style: TextStyle(
            fontSize: fontSize,
            fontWeight: fontWeight,
            foreground: Paint()..color = color,
          ),
        ),
        Text(
          text,
          style: TextStyle(
            fontSize: fontSize,
            fontWeight: fontWeight,
            foreground: Paint()
              ..strokeWidth = strokeWidth
              ..color = strokeColor
              ..style = PaintingStyle.stroke,
          ),
        ),
      ],
    );
  }
}
Aleh
  • 386
  • 4
  • 8
  • [check this out](https://tutorsvilla.blogspot.com/2019/06/how-to-add-text-border-or-text-stroke-flutter.html) courtesy of your code. – tewshi Jun 13 '19 at 15:45
6

If you prefer the shadows method, you can configure the stroke width using :

/// Outlines a text using shadows.
static List<Shadow> outlinedText({double strokeWidth = 2, Color strokeColor = Colors.black, int precision = 5}) {
  Set<Shadow> result = HashSet();
  for (int x = 1; x < strokeWidth + precision; x++) {
    for(int y = 1; y < strokeWidth + precision; y++) {
      double offsetX = x.toDouble();
      double offsetY = y.toDouble();
      result.add(Shadow(offset: Offset(-strokeWidth / offsetX, -strokeWidth / offsetY), color: strokeColor));
      result.add(Shadow(offset: Offset(-strokeWidth / offsetX, strokeWidth / offsetY), color: strokeColor));
      result.add(Shadow(offset: Offset(strokeWidth / offsetX, -strokeWidth / offsetY), color: strokeColor));
      result.add(Shadow(offset: Offset(strokeWidth / offsetX, strokeWidth / offsetY), color: strokeColor));
    }
  }
  return result.toList();
}

Use it like this :

Text(
  'My text',
  style: TextStyle(shadows: outlinedText(strokeColor: Colors.blue)),
);
Skyost
  • 1,429
  • 1
  • 16
  • 31
2

Inspired by @Gary Qian's answer

Widget textWithStroke({String text, String fontFamily, double fontSize: 12, double strokeWidth: 1, Color textColor: Colors.white, Color strokeColor: Colors.black}) {
        return Stack(
          children: <Widget>[
            Text(
              text,
              style: TextStyle(
                fontSize: fontSize,
                fontFamily: fontFamily,
                foreground: Paint()
                  ..style = PaintingStyle.stroke
                  ..strokeWidth = strokeWidth
                  ..color = strokeColor,
              ),
            ),
            Text(text, style: TextStyle(fontFamily: fontFamily, fontSize: fontSize, color: textColor)),
          ],
        );
      }
New Dev
  • 318
  • 4
  • 10
1

This is @Aleh's answer migrated to null-safety and with some more flexibility. Simply paste this inside a new file, and use freely.

import 'package:flutter/widgets.dart';

/// Places a stroke around text to make it appear outlined
///
/// Adapted from https://stackoverflow.com/a/55559435/11846040
class OutlinedText extends StatelessWidget {
  /// Text to display
  final String text;

  /// Original text style (if you weren't outlining)
  ///
  /// Do not specify `color` inside this: use [textColor] instead.
  final TextStyle style;

  /// Text color
  final Color textColor;

  /// Outline stroke color
  final Color strokeColor;

  /// Outline stroke width
  final double strokeWidth;

  /// Places a stroke around text to make it appear outlined
  ///
  /// Adapted from https://stackoverflow.com/a/55559435/11846040
  const OutlinedText(
    this.text, {
    Key? key,
    this.style = const TextStyle(),
    required this.textColor,
    required this.strokeColor,
    required this.strokeWidth,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Text(
          text,
          style: style.copyWith(foreground: Paint()..color = textColor),
        ),
        Text(
          text,
          style: style.copyWith(
            foreground: Paint()
              ..strokeWidth = strokeWidth
              ..color = strokeColor
              ..style = PaintingStyle.stroke,
          ),
        ),
      ],
    );
  }
}
JaffaKetchup
  • 1,151
  • 10
  • 26
0

I created a package using the same logic shared here.

I also make it possible to add multiple strokes at once.

package: https://pub.dev/packages/outlined_text

DEMO

enter image description here