flutter call shadow widget of textstyle from class - flutter

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,
);
}

Related

Flutter - how do i make a custom underline for a date in my calendar?

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),

How to use both the text color and foreground color properties together

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

Flutter rounded TabBar border indicator

I spent way too many hours searching for a similar solution but it seems that nobody on earth is implementing such design. I want to indicate selected TabBar Tab with such indicator:
Currently it looks like this:
Code of current TabBar:
const TabBar(
indicator: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.blue, width: 5),
),
),
labelPadding: EdgeInsets.zero,
tabs: [
_Tab(icon: Icons.home, text: 'Home'),
_Tab(icon: Icons.settings, text: 'Settings'),
_Tab(icon: Icons.cleaning_services, text: 'Clean'),
_Tab(icon: Icons.construction, text: 'Service'),
_Tab(icon: Icons.library_books, text: 'Resources'),
],
),
)
Has anybody got an idea of how this should look like?
You must create your own Decoration. Have a look to this guide : https://medium.com/swlh/flutter-custom-tab-indicator-for-tabbar-d72bbc6c9d0c
It creates a custom point under the tab, so you can copy that to create your kind of indicator
Thanks, BabC.
Here's the final result:
class _TabIndicator extends BoxDecoration {
final BoxPainter _painter;
_TabIndicator() : _painter = _TabIndicatorPainter();
#override
BoxPainter createBoxPainter([onChanged]) => _painter;
}
class _TabIndicatorPainter extends BoxPainter {
final Paint _paint;
_TabIndicatorPainter()
: _paint = Paint()
..color = Colors.blue
..isAntiAlias = true;
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration cfg) {
final double _xPos = offset.dx + cfg.size.width / 2;
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromLTRB(_xPos - 20, 0, _xPos + 20, 5),
bottomLeft: const Radius.circular(5.0),
bottomRight: const Radius.circular(5.0),
),
_paint,
);
}
}

How to add drop shadow to TextFormField In Flutter

I have a text form field in flutter I want to add a drop shadow to it. how should I do that?
final password = TextFormField(
obscureText: true,
autofocus: false,
decoration: InputDecoration(
icon: new Icon(Icons.lock, color: Color(0xff224597)),
hintText: 'Password',
fillColor: Colors.white,
filled: true,
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
enabledBorder: OutlineInputBorder(borderRadius:BorderRadius.circular(5.0),
borderSide: BorderSide(color: Colors.white, width: 3.0))
),
);
You can Wrap your TextFormField with a Material widget and edit its properties such as elevation and shadowColor.
Example:
Material(
elevation: 20.0,
shadowColor: Colors.blue,
child: TextFormField(
obscureText: true,
autofocus: false,
decoration: InputDecoration(
icon: new Icon(Icons.lock, color: Color(0xff224597)),
hintText: 'Password',
fillColor: Colors.white,
filled: true,
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
enabledBorder: OutlineInputBorder(borderRadius:BorderRadius.circular(5.0),
borderSide: BorderSide(color: Colors.white, width: 3.0))
),
),
)
You will Get something similar to the image below.
You can Use PhysicalModel to add shadow on each widget like this:
PhysicalModel(
borderRadius: BorderRadius.circular(25),
color: Colors.white,
elevation: 5.0,
shadowColor: Color(0xff44BD32),
child: CustomTextField(...
You can use this class as a wrapper for an element's border. It takes the border of a control and draws a shadow to that border above the control. To create an illusion that the shadow is behind the control, the shadow's area above the control gets cut off.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class DecoratedInputBorder extends InputBorder {
DecoratedInputBorder({
required this.child,
required this.shadow,
}) : super(borderSide: child.borderSide);
final InputBorder child;
final BoxShadow shadow;
#override
bool get isOutline => child.isOutline;
#override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) => child.getInnerPath(rect, textDirection: textDirection);
#override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) => child.getOuterPath(rect, textDirection: textDirection);
#override
EdgeInsetsGeometry get dimensions => child.dimensions;
#override
InputBorder copyWith({BorderSide? borderSide, InputBorder? child, BoxShadow? shadow, bool? isOutline}) {
return DecoratedInputBorder(
child: (child ?? this.child).copyWith(borderSide: borderSide),
shadow: shadow ?? this.shadow,
);
}
#override
ShapeBorder scale(double t) {
final scalledChild = child.scale(t);
return DecoratedInputBorder(
child: scalledChild is InputBorder ? scalledChild : child,
shadow: BoxShadow.lerp(null, shadow, t)!,
);
}
#override
void paint(Canvas canvas, Rect rect, {double? gapStart, double gapExtent = 0.0, double gapPercentage = 0.0, TextDirection? textDirection}) {
final clipPath = Path()
..addRect(const Rect.fromLTWH(-5000, -5000, 10000, 10000))
..addPath(getInnerPath(rect), Offset.zero)
..fillType = PathFillType.evenOdd;
canvas.clipPath(clipPath);
final Paint paint = shadow.toPaint();
final Rect bounds = rect.shift(shadow.offset).inflate(shadow.spreadRadius);
canvas.drawPath(getOuterPath(bounds), paint);
child.paint(canvas, rect, gapStart: gapStart, gapExtent: gapExtent, gapPercentage: gapPercentage, textDirection: textDirection);
}
#override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is DecoratedInputBorder && other.borderSide == borderSide && other.child == child && other.shadow == shadow;
}
#override
int get hashCode => hashValues(borderSide, child, shadow);
#override
String toString() {
return '${objectRuntimeType(this, 'DecoratedInputBorder')}($borderSide, $shadow, $child)';
}
}
MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
inputDecorationTheme: InputDecorationTheme(
border: DecoratedInputBorder(
child: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)),
),
shadow: const BoxShadow(
color: Colors.blue,
blurRadius: 15,
),
),
),
),
It should looks this way:
Interactive example: https://dartpad.dev/?id=35f1249b52d177d47bc91c87d0a8c08c
Alternatively, you can use my package control_style. It implements a deeper realisation of this approach.
Here is a possible solution where the BoxShadow is only displayed behind the TextField, but is not expanding vertically if an error text is displayed.
My solution was to use the Stack widget to create an additional Container behind the actual TextField that is responsible for displaying the shadow.
A TextPainter is used to determine the height of the error text depending on its style:
import 'package:flutter/material.dart';
class TextFieldWithBoxShadow extends StatelessWidget {
final String? errorText;
final String? labelText;
final TextEditingController? controller;
final double height;
const TextFieldWithBoxShadow({
Key? key,
this.errorText,
this.labelText,
this.controller,
this.height = 40,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final errorStyle = const TextStyle(
fontSize: 14,
);
// Wrap everything in LayoutBuilder so that the available maxWidth is taken into account for the height calculation (important if you error text exceeds one line)
return LayoutBuilder(builder: (context, constraints) {
// Use tp to calculate the height of the errorText
final textPainter = TextPainter()
..text = TextSpan(text: errorText, style: errorStyle)
..textDirection = TextDirection.ltr
..layout(maxWidth: constraints.maxWidth);
final heightErrorMessage = textPainter.size.height + 8;
return Stack(
children: [
// Separate container with identical height of text field which is placed behind the actual textfield
Container(
height: height,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Colors.black,
blurRadius: 3,
offset: Offset(3, 3),
),
],
borderRadius: BorderRadius.circular(
10.0,
),
),
),
Container(
// Add height of error message if it is displayed
height: errorText != null ? height + heightErrorMessage : height,
child: TextField(
decoration: InputDecoration(
fillColor: Colors.black,
filled: true,
errorStyle: errorStyle,
errorText: errorText,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
10.0,
),
),
labelText: labelText,
),
controller: controller,
),
),
],
);
});
}
}
The problem when we use a Container, Material or any other Widget to wrap the input text field in order to apply the shadow is if we use the hint text, error text or any other property that change the size of our textbox, the design would be broken.
Intead of wrapping the input in another widget you could use custom painter extending the InputBorder class.
For example:
class MyInputBorder extends OutlineInputBorder {}
Copy the following methods from the OutlineInputBorder implementation (Used for this example) to your new class:
_gapBorderPath
_cornersAreCircular
paint
Then in the paint method you could add the following lines
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, Colors.black, 4, true);
The above lines must be included before the canvas.drawRRect line:
Example:
if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
// paint the shadow for the outlined shape
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, shadowColor!, elevation, true);
canvas.drawRRect(center, paint);
} else {... other code omitted to keep simple}
Then, in your Widget, use the new Input border:
TextField(
decoration: InputDecoration(
border: MyInputBorder()
),
)
The result produced looks like the following without any of the issues of wrapper solutions:
text box with shadow
Here is a complete sample implementation, the post is in spanish but it has the idea explained: Full article for reference
#mrramos answer was almost complete but this code wouldn't give the desired result, I read the article suggested and implemented my own class, my use case is only a shadow for the text field for when its selected therefore the naming of it.
Quick explanation of this as its a lot to read and most of it is not necessary to understand just to implement a simple shadow.
paint() method is copied from OutlineInputBorder as _cornersAreCircular() and _gapBorderPath()
There is an addition of these few lines in the paint method for it to give the shadow.
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, Colors.black, 5, true);
final shadowPaint = Paint();
shadowPaint.strokeWidth = 0;
shadowPaint.color = Colors.white;
shadowPaint.style = PaintingStyle.fill;
canvas.drawRRect(center, shadowPaint);
canvas.drawRRect(center, paint);
Complete file class.
import 'package:flutter/material.dart';
import 'dart:ui' show lerpDouble;
import 'dart:math' as math;
class SelectedInputBorderWithShadow extends OutlineInputBorder {
const SelectedInputBorderWithShadow({
BorderSide borderSide = const BorderSide(),
borderRadius = const BorderRadius.all(Radius.circular(5)),
gapPadding = 4.0,
}) : super(
borderSide: borderSide,
borderRadius: borderRadius,
gapPadding: gapPadding,
);
static bool _cornersAreCircular(BorderRadius borderRadius) {
return borderRadius.topLeft.x == borderRadius.topLeft.y &&
borderRadius.bottomLeft.x == borderRadius.bottomLeft.y &&
borderRadius.topRight.x == borderRadius.topRight.y &&
borderRadius.bottomRight.x == borderRadius.bottomRight.y;
}
Path _gapBorderPath(
Canvas canvas, RRect center, double start, double extent) {
// When the corner radii on any side add up to be greater than the
// given height, each radius has to be scaled to not exceed the
// size of the width/height of the RRect.
final RRect scaledRRect = center.scaleRadii();
final Rect tlCorner = Rect.fromLTWH(
scaledRRect.left,
scaledRRect.top,
scaledRRect.tlRadiusX * 2.0,
scaledRRect.tlRadiusY * 2.0,
);
final Rect trCorner = Rect.fromLTWH(
scaledRRect.right - scaledRRect.trRadiusX * 2.0,
scaledRRect.top,
scaledRRect.trRadiusX * 2.0,
scaledRRect.trRadiusY * 2.0,
);
final Rect brCorner = Rect.fromLTWH(
scaledRRect.right - scaledRRect.brRadiusX * 2.0,
scaledRRect.bottom - scaledRRect.brRadiusY * 2.0,
scaledRRect.brRadiusX * 2.0,
scaledRRect.brRadiusY * 2.0,
);
final Rect blCorner = Rect.fromLTWH(
scaledRRect.left,
scaledRRect.bottom - scaledRRect.blRadiusY * 2.0,
scaledRRect.blRadiusX * 2.0,
scaledRRect.blRadiusX * 2.0,
);
// This assumes that the radius is circular (x and y radius are equal).
// Currently, BorderRadius only supports circular radii.
const double cornerArcSweep = math.pi / 2.0;
final double tlCornerArcSweep = math.acos(
(1 - start / scaledRRect.tlRadiusX).clamp(0.0, 1.0),
);
final Path path = Path()..addArc(tlCorner, math.pi, tlCornerArcSweep);
if (start > scaledRRect.tlRadiusX)
path.lineTo(scaledRRect.left + start, scaledRRect.top);
const double trCornerArcStart = (3 * math.pi) / 2.0;
const double trCornerArcSweep = cornerArcSweep;
if (start + extent < scaledRRect.width - scaledRRect.trRadiusX) {
path.moveTo(scaledRRect.left + start + extent, scaledRRect.top);
path.lineTo(scaledRRect.right - scaledRRect.trRadiusX, scaledRRect.top);
path.addArc(trCorner, trCornerArcStart, trCornerArcSweep);
} else if (start + extent < scaledRRect.width) {
final double dx = scaledRRect.width - (start + extent);
final double sweep = math.asin(
(1 - dx / scaledRRect.trRadiusX).clamp(0.0, 1.0),
);
path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep);
}
return path
..moveTo(scaledRRect.right, scaledRRect.top + scaledRRect.trRadiusY)
..lineTo(scaledRRect.right, scaledRRect.bottom - scaledRRect.brRadiusY)
..addArc(brCorner, 0.0, cornerArcSweep)
..lineTo(scaledRRect.left + scaledRRect.blRadiusX, scaledRRect.bottom)
..addArc(blCorner, math.pi / 2.0, cornerArcSweep)
..lineTo(scaledRRect.left, scaledRRect.top + scaledRRect.tlRadiusY);
}
#override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {
assert(gapExtent != null);
assert(gapPercentage >= 0.0 && gapPercentage <= 1.0);
assert(_cornersAreCircular(borderRadius));
final Paint paint = borderSide.toPaint();
final RRect outer = borderRadius.toRRect(rect);
final RRect center = outer.deflate(borderSide.width / 2.0);
if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, Colors.black, 5, true);
final shadowPaint = Paint();
shadowPaint.strokeWidth = 0;
shadowPaint.color = Colors.white;
shadowPaint.style = PaintingStyle.fill;
canvas.drawRRect(center, shadowPaint);
canvas.drawRRect(center, paint);
} else {
final double extent =
lerpDouble(0.0, gapExtent + gapPadding * 2.0, gapPercentage)!;
switch (textDirection!) {
case TextDirection.rtl:
final Path path = _gapBorderPath(canvas, center,
math.max(0.0, gapStart + gapPadding - extent), extent);
canvas.drawPath(path, paint);
break;
case TextDirection.ltr:
final Path path = _gapBorderPath(
canvas, center, math.max(0.0, gapStart - gapPadding), extent);
canvas.drawPath(path, paint);
break;
}
}
}
}
and my result looks like this.
You can wrap TextFormField into Container add shadow too Container this will add shadow to you TextFormFieldbut it also adds color to TextFormField.
To remove Color from TextFormField use fillColor and filled property on TextFormField.
You Can control the opacity of a color line Colors.black.withOpacity(0.3).
Checkout Code Below:
final Widget password = Container(
decoration: BoxDecoration(
boxShadow: [
const BoxShadow(
blurRadius: 8,
),
],
borderRadius: BorderRadius.circular(5.0),
),
child: TextFormField(
obscureText: true,
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
prefixIcon: const Icon(
Icons.lock,
color: Color(0xff224597),
),
hintText: 'Password',
contentPadding: const EdgeInsets.fromLTRB(
20.0,
10.0,
20.0,
10.0,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
borderSide: const BorderSide(
color: Colors.white,
width: 3.0,
),
),
),
),
);
YOU CAN CHECKOUT THE OUTPUT HERE

How to decorate text stroke in Flutter?

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