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.
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
This is my code:
Text ButtonText = Text(
_buttonText, style: TextStyle(
color: Colors.white,
fontFamily: 'San francisco',
//fontSize: 21.0.ssp,
letterSpacing: 2.0,
wordSpacing: 2.0
),
);
when I use this Text in my button widget, I want to set font size explicitly. How can I do that?
you can create a class for your situation we can call it customtext
here is an example code :
import 'package:flutter/material.dart';
class CustomText extends StatelessWidget {
final String text;
final double size;
final Color color;
final FontWeight weight;
// name constructor that has a positional parameters with the text required
// and the other parameters optional
CustomText({#required this.text, this.size,this.color,this.weight});
#override
Widget build(BuildContext context) {
return Text(
text,style: TextStyle(fontSize: size ?? 16, color: color ?? Colors.black, fontWeight: weight ?? FontWeight.normal),
);
}
}
Any idea how to make only the fontsize of emojis larger in the Text() widget?
The issue is that the Text() widget parses the emojis automatically. Sure, I can increase the overall text size, but I want the text to stay at the same fontsize.
If you want to auto increase size of emojis in chat message you can use this method:
static final RegExp REGEX_EMOJI = RegExp(
r'(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])');
Widget _buildContent(String content) {
final Iterable<Match> matches = REGEX_EMOJI.allMatches(content);
if (matches.isEmpty)
return Text(
'${content}',
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Colors.black,
),
);
return RichText(
text: TextSpan(children: [
for (var t in content.characters)
TextSpan(
text: t,
style: TextStyle(
fontSize: REGEX_EMOJI.allMatches(t).isNotEmpty ? 20.0 : 12.0,
color: Colors.black,
)),
]));
}
You can use the widget RichText
RichText(
text: TextSpan(
text: 'hello',
children: <TextSpan>[
TextSpan(text: '🙂', style: TextStyle(fontSize: 30))
]
),
),
I was facing the same issue and find beginning of solution here
I've modified the code a little to allow showing text and emoji with different font size.
import 'package:flutter/material.dart';
/// Widget to render emoji and text with different font size
class EmojisText extends StatelessWidget {
const EmojisText({
Key? key,
required this.text,
required this.color,
required this.emojiSize,
required this.textSize,
}) : super(key: key);
///The text which emoji and alpha-numeric characters
///emoji can be absent
final String text;
/// THe font size to set to emoji
final double emojiSize;
/// The font size to set to text
final double textSize;
/// the color of the text
final Color color;
#override
Widget build(BuildContext context) {
return RichText(
text: _buildText(),
);
}
TextSpan _buildText() {
final children = <TextSpan>[];
final runes = text.runes;
for (int i = 0; i < runes.length; /* empty */) {
int current = runes.elementAt(i);
// we assume that everything that is not
// in Extended-ASCII set is an emoji...
final isEmoji = current > 255;
final shouldBreak = isEmoji ? (x) => x <= 255 : (x) => x > 255;
final chunk = <int>[];
while (!shouldBreak(current)) {
chunk.add(current);
if (++i >= runes.length) break;
current = runes.elementAt(i);
}
children.add(
TextSpan(
text: String.fromCharCodes(chunk),
style: TextStyle(
fontSize: isEmoji ? emojiSize : textSize,
color: color,
),
),
);
}
return TextSpan(children: children);
}
}
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