How to decorate text stroke in Flutter?
It's like -webkit-text-stroke - CSS
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:
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.
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
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,
),
),
],
);
}
}
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)),
);
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)),
],
);
}
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,
),
),
],
);
}
}
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
Related
I want to make this design with an underline for a holiday in my calendar widget.
This is what I have so far. I don't know how to add spacing and round off the sides of the underline.
I am using SfDateRangePicker, here is the code so far:
monthCellStyle: DateRangePickerMonthCellStyle(
specialDatesTextStyle: TextStyle(
fontStyle: FontStyle.normal,
fontSize: 13,
fontWeight: FontWeight.w500,
color: Colors.black,
decoration: TextDecoration.underline,
decorationThickness: 4,
decorationColor: Colors.red[800]),
Unfortunately syncfusion is not open source so can't really test this but they seem to have an example here
I don't know if it will be enough for what you want to accomplish, but you can get around this problem with this style. If you decrease the value "-2" the spacing will increase.
OBS: [credits]: Is it possible to underline text with some height between text and underline line?
Text(
"Your Text here",
style: TextStyle(
shadows: [
Shadow(color: Colors.black,
offset: Offset(0, -2))
],
color: Colors.transparent,
decoration: TextDecoration.underline,
decorationColor: Colors.blue,
decorationThickness: 4,
decorationStyle: TextDecorationStyle.solid,
),
),
I ended up following Mathiew's answer to create the design i needed. This is how i got it to look:
For anyone interested here is the code i ended up using:
class _MonthCellDecoration extends Decoration {
const _MonthCellDecoration(this.showIndicator,
{this.borderColor, this.backgroundColor, this.indicatorColor});
final Color borderColor;
final Color backgroundColor;
final bool showIndicator;
final Color indicatorColor;
#override
BoxPainter createBoxPainter([VoidCallback onChanged]) {
return _MonthCellDecorationPainter(showIndicator,
borderColor: borderColor,
backgroundColor: backgroundColor,
indicatorColor: indicatorColor);
}
}
class _MonthCellDecorationPainter extends BoxPainter {
_MonthCellDecorationPainter(this.showIndicator,
{this.borderColor, this.backgroundColor, this.indicatorColor});
final Color borderColor;
final Color backgroundColor;
final bool showIndicator;
final Color indicatorColor;
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
final Rect bounds = offset & configuration.size;
_drawDecoration(canvas, bounds);
}
void _drawDecoration(Canvas canvas, Rect bounds) {
final Paint paint = Paint()..color = backgroundColor;
canvas.drawRRect(
RRect.fromRectAndRadius(bounds, const Radius.circular(5)), paint);
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 3;
paint.strokeCap = StrokeCap.round;
if (borderColor != null) {
paint.color = borderColor;
canvas.drawRRect(
RRect.fromRectAndRadius(bounds, const Radius.circular(5)), paint);
}
if (showIndicator) {
paint.color = indicatorColor;
paint.style = PaintingStyle.fill;
canvas.drawLine(Offset(bounds.left + 18, bounds.bottom - 7),
Offset(bounds.right - 18, bounds.bottom - 7), paint);
}
}
}
And to use it just add this code to the monthCellStyle property
specialDatesDecoration: _MonthCellDecoration(true,
backgroundColor: Colors.transparent,
indicatorColor: Colors.red),
I would like to know if there's any way to first set the text color of a text and then apply a stroke color to it using the foreground property. I'm looking to make something like this:
Here's the code that I have written. Please note that this is a date range picker calendar(3rd party plugin called syncfusion_flutter_datepicker) and I would like to apply the above style to all the weekend days.
SfDateRangePicker(
monthCellStyle: DateRangePickerMonthCellStyle(
weekendTextStyle: TextStyle(
foreground: Paint()
..style = PaintingStyle.stroke
..color = PrimaryColor
..strokeWidth = 2,
shadows: [
Shadow(
color: PrimaryColor,
blurRadius: 5,
offset: Offset(0, 0))
],
// color: Colors.white, //I would like to set the text color to white
fontSize: 25,
fontWeight: FontWeight.bold),
))
If I try using both the properties, this is the error I get
Assertion failed:
..\…\painting\text_style.dart:510
color == null || foreground == null
"Cannot provide both a color and a foreground\nThe color argument is just a shorthand for \"foreground: new Paint()..color = color\"."
Is there any workaround to this?
The only way to add a "border" to your text would be to put your Text widget inside a Stack and render 2 Text widget, one for the border and another one to render the text in the color you want and render the shadows.
Code Sample
class OutlinedText extends StatelessWidget {
final String text;
final Color primaryColor;
const OutlinedText({
required this.text,
required this.primaryColor,
});
#override
Widget build(BuildContext context) {
final textStyle = TextStyle(
shadows: [
Shadow(
color: primaryColor,
blurRadius: 5,
offset: const Offset(0, 0),
)
],
color: Colors.white,
fontSize: 25,
fontWeight: FontWeight.bold,
);
return Stack(
alignment: Alignment.center,
children: [
Text(
text,
style: textStyle.copyWith(
foreground: Paint()
..style = PaintingStyle.stroke
..color = primaryColor
..strokeWidth = 2,
color: null,
),
),
Text(text, style: textStyle),
],
);
}
}
OutlinedText(
text: "Hello, World!",
primaryColor: Colors.red,
);
Output
Try the full example on DartPad
Im very new to flutter, I want to make the code as much as less, so instead of using shadow widget every time, i want to call it but I don't know how to make the shadows widget as fixed variable
class ShadowTextStyle extends TextStyle {
final Color color;
final FontWeight fontWeight;
final double size;
final List<Shadow>? shadows;
const ShadowTextStyle({
required this.color,
required this.fontWeight,
this.size = 14,
this.shadows =//how to do it here,
,
}): super(
color: color,
fontWeight: fontWeight,
fontSize: size,
shadows: shadows,
);
}
this is the shadow parameters, I want to make it fix
shadows: <Shadow>[
Shadow(
offset: Offset(1.0, 0.0),
blurRadius: 5.0,
color: Color.fromARGB(255, 0, 0, 0),
),
],
I'm not sure if I get the question right, but you can assign your customized shadow to a variable, I always have a style.dart file where I save my customized styles.
Shadow myShadow = Shadow(
offset: Offset(1.0, 0.0),
blurRadius: 5.0,
color: Color.fromARGB(255, 0, 0, 0),
);
I found it. This is what I meant:
const ShadowFile = <Shadow>[
Shadow(
offset: Offset(1.0, 0.0),
blurRadius: 5.0,
color: Color.fromARGB(255, 0, 0, 0),
)
];
class ShadowTextStyle extends TextStyle {
final Color color;
final FontWeight fontWeight;
final double size;
const ShadowTextStyle({
required this.color,
required this.fontWeight,
this.size = 14,
shadows: ShadowFile,
}) : super(
color: color,
fontWeight: fontWeight,
fontSize: size,
shadows: ShadowFile,
);
}
I would like the TextStyle / fontSize to simplify my app.
for this it would be necessary to make the line "
fontSize: Get.width * .030,
" of the attached code available, similar to the color "
{Color color = Colors.white}
This is the code I want to customize ...
TextStyle _textStyle({Color color = Colors.white}) {
return GoogleFonts.getFont(
'Unica One',
fontSize: Get.width * .030,
color: color,
letterSpacing: 0.5,
);
}
And it should look something like that, but unfortunately I don't know exactly how to put it together correctly
TextStyle _textStyle({Color color = Colors.white}{FontSize fontSize = Get.width * .030}) {
return GoogleFonts.getFont(
'Unica One',
fontSize: fontSize,
color: color,
letterSpacing: 0.5,
);
}
Text class with style,
class StoryText extends Text {
StoryText(String title, {Color mColor, double mFontSize})
: super(title,
style: GoogleFonts.getFont(
'Unica One',
fontSize: mFontSize,
color: mColor,
letterSpacing: 0.5,
));
}
// Use of above class
StoryText('title', mColor: Colors.red, mFontSize: Get.width * .030,),
Need to change FontSize type to double.
TextStyle _textStyle({Color color = Colors.white ,double fontSize = Get.width * .030}) {
return GoogleFonts.getFont(
'Unica One',
fontSize: fontSize,
color: color,
letterSpacing: 0.5,
);
}```
Can you maybe tell me how I use this in the "hintText:" of a "TextField ("?
when I do it like this
hintStyle: StyledText (
color: Colors.black,
),
then I get the error
"The argument type 'StyledText' can't be assigned to the parameter
type 'TextStyle?'. (Documentation)"
I have adapted your code so that it works so far, thank you again for that 😊 .. would only like to use it with the "hintStyle"
import 'package: flutter / cupertino.dart';
import 'package: flutter / material.dart';
import 'package: google_fonts / google_fonts.dart';
class StyledText extends Text {
StyledText (
String title,
{
double fontSize = 10.5,
color = Colors.white,
fontWeight = FontWeight.w100,
letterSpacing = 0.5,
textAlign: TextAlign.center,
})
: super (title,
style: GoogleFonts.getFont (
'Unica One',
fontSize: fontSize,
color: color,
fontWeight: fontWeight,
letterSpacing: 0.5,
),
textAlign: textAlign,
);
}
I want to make a custom widget that basically adds a stroke to a text by taking a Text, wrapping it in a Stack with two texts, with one of them rendered with a stroke.
class BorderedText extends StatelessWidget {
final Text displayText;
final Color strokeColor;
final double strokeWidth;
BorderedText(this.displayText,
{Key key, this.strokeColor = Colors.black, this.strokeWidth = 1.0})
: assert(displayText != null),
super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: <Widget>[
Text(
displayText.data,
style: displayText.style
..foreground = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = strokeColor,
),
displayText,
],
),
);
}
}
Intended way of usage:
BorderedText(
Text(
"Hello App",
style: TextStyle(
color: Colors.white,
fontSize: 34.0,
fontFamily: "LexendMega",
),
),
strokeWidth: 6.0,
),
Sadly this code doesn't work because foreground is final. How can I address that?
Can I make a complete copy of displayText parameter and be able to change its foreground?
Can I make a copy of its TextStyle, only changing the foreground?
You can use TextStyle.copyWith for this. This will copy the parameters from your other text style and only changes the ones you supply. In your case it would looks like this:
Text(
displayText.data,
style: displayText.style.copyWith(
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = strokeColor
),
)
By the way: this method exists for many classes in the Flutter framework (where it makes sense) and it is very useful as you would need to manually type all the parameters otherwise.