In recent times, few concepts have rocked the UI/UX design world in a fashion similar to the neumorphic design craze. Dribbble and behance are littered with designs illustrating the use of this design pattern in web or mobile apps. It's a minimal design thus very appealing to the eye due to its softness and uniqueness in a design landscape where popping, shouting colors are all the rave.
Luckily, we can implement these great designs in our flutter apps and make them stand out from the crowd(of apps*). I shall implement both a dark and light neumorphic design. For this demonstration, I shall use a simple fintech app UI sample I created.
Here is the source code
Firstly,we shall begin with the light version. We shall implement a base theme widget that shall hold all global theme properties that shall apply throughout the app. We shall use a scaffold background color of Color.grey[300] to enable use apply neumorphic shadows that pop effectively.
ThemeData lightThemeData(BuildContext context) {
return ThemeData.light().copyWith(
scaffoldBackgroundColor: Colors.grey[300],
}
Next, we shall create a box decoration variable that we shall use throughout the app to create a light box-shadow that shall create the popping effect present in neumorphic designs
final BoxDecoration decorator = BoxDecoration(
[
BoxShadow(
color: Colors.grey.shade500,
offset: Offset(4, 4),
blurRadius: 15,
spreadRadius: 1),
BoxShadow(
spreadRadius: 1,
color: Colors.white,
offset: Offset(-4, -4),
blurRadius: 15,
)
]);
If we want to add the neumorphic shadow to any widget that we use for example, container, we'll just add the decorator variable to the decoration property.
Container(
height: size.height * 0.07,
width: size.width * 0.4,
decoration: decorator.copyWith(
borderRadius: BorderRadius.circular(30.0),
color: Colors.greenAccent),)
In certain instances, we might want to add a depression effect in our neumorphic design language so as to create an illusion of a depression on the screen as illustrated in the image below. This can also be created with flutter
We shall create a decoration and box painter widget and add the colors we shall use to create the concave decoration effect
class SecondDecoration extends Decoration {
final ShapeBorder? shape;
final double? depression;
final List<Color>? colors;
SecondDecoration({
@required this.shape,
@required this.depression,
this.colors,
}) : assert(shape != null),
assert(depression! >= 0),
assert(colors == null || colors.length == 2);
@override
BoxPainter createBoxPainter([void onChanged]) =>
_SecondDecorationPainter(shape!, depression!, colors!);
@override
EdgeInsetsGeometry get padding => shape!.dimensions;
}
class _SecondDecorationPainter extends BoxPainter {
ShapeBorder shape;
double depression;
List<Color> colors;
_SecondDecorationPainter(this.shape, this.depression, this.colors) {
colors = [
// Colors.black,
// Colors.grey.shade800,
Colors.black87,
Colors.white
];
}
@override
void paint(
ui.Canvas canvas, ui.Offset offset, ImageConfiguration configuration) {
final rect = offset & configuration.size!;
final shapePath = shape.getOuterPath(rect);
final delta = 16 / rect.longestSide;
final stops = [0.5 - delta, 0.5 + delta];
final path = Path()
..fillType = PathFillType.evenOdd
..addRect(rect.inflate(depression * 2))
..addPath(shapePath, Offset.zero);
canvas.save();
canvas.clipPath(shapePath);
final paint = Paint()
..maskFilter = MaskFilter.blur(BlurStyle.normal, depression);
final clipSize = rect.size.aspectRatio > 1
? Size(rect.width, rect.height / 2)
: Size(rect.width / 2, rect.height);
for (final alignment in [Alignment.topLeft, Alignment.bottomRight]) {
final shaderRect =
alignment.inscribe(Size.square(rect.longestSide), rect);
paint.shader = ui.Gradient.linear(
shaderRect.topLeft, shaderRect.bottomRight, colors, stops);
canvas.save();
canvas.clipRect(alignment.inscribe(clipSize, rect));
canvas.drawPath(path, paint);
canvas.restore();
}
canvas.restore();
}
}
We shall then pass this Decoration widget as an argument to the decoration property of a container widget.We shall then pass a pair of colors that shall give us a concave depression decoration effect in our container
Container(
height: 60,
decoration: SecondDecoration(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
depression: 10,
colors: [
Color.fromRGBO(216, 213, 208, 1),
Colors.white
]))
And voila. We can now create a light neumorphic shadow and depression effect in our flutter apps.
Next up, we shall replicate the same in dark mode.
Firstly,we shall add a global dark theme with a dark scaffold color that shall enable us to create a dark neumorphic design.
ThemeData darkThemeData(BuildContext context) {
return ThemeData.dark().copyWith(
scaffoldBackgroundColor:Colors.grey[900],
);
}
We shall then create a dark neumorphic shadow that shall create a popping effect. This shall be done by creating a decorator variable that shall hold the BoxDecoration object with the necessary box-shadow
final BoxDecoration decorator = BoxDecoration(
[
BoxShadow(
color: Colors.black,
offset: Offset(5, 5),
blurRadius: 15,
spreadRadius: 5),
BoxShadow(
spreadRadius: 1,
color: Colors.grey.shade800,
offset: Offset(-4, -4),
blurRadius: 15,
)
]);
We can now use this variable within the container widget down the widget tree.
Container(
height: size.height * 0.07,
width: size.width * 0.4,
decoration: decorator.copyWith(
borderRadius: BorderRadius.circular(30.0),
color: Colors.greenAccent),)
We shall then create a dark concave depression neumorphic effect with Decoration and box painter widgets.
class ConcaveDecoration extends Decoration {
final ShapeBorder? shape;
final double? depression;
final List<Color>? colors;
ConcaveDecoration({
@required this.shape,
@required this.depression,
this.colors,
}) : assert(shape != null),
assert(depression! >= 0),
assert(colors == null || colors.length == 2);
@override
BoxPainter createBoxPainter([void onChanged]) =>
_ConcaveDecorationPainter(shape!, depression!, colors!);
@override
EdgeInsetsGeometry get padding => shape!.dimensions;
}
class _ConcaveDecorationPainter extends BoxPainter {
ShapeBorder shape;
double depression;
List<Color> colors;
_ConcaveDecorationPainter(this.shape, this.depression, this.colors) {
colors = [
Colors.black,
Colors.grey.shade800,
// Colors.black87,
// Colors.white
];
}
@override
void paint(
ui.Canvas canvas, ui.Offset offset, ImageConfiguration configuration) {
final rect = offset & configuration.size!;
final shapePath = shape.getOuterPath(rect);
final delta = 16 / rect.longestSide;
final stops = [0.5 - delta, 0.5 + delta];
final path = Path()
..fillType = PathFillType.evenOdd
..addRect(rect.inflate(depression * 2))
..addPath(shapePath, Offset.zero);
canvas.save();
canvas.clipPath(shapePath);
final paint = Paint()
..maskFilter = MaskFilter.blur(BlurStyle.normal, depression);
final clipSize = rect.size.aspectRatio > 1
? Size(rect.width, rect.height / 2)
: Size(rect.width / 2, rect.height);
for (final alignment in [Alignment.topLeft, Alignment.bottomRight]) {
final shaderRect =
alignment.inscribe(Size.square(rect.longestSide), rect);
paint.shader = ui.Gradient.linear(
shaderRect.topLeft, shaderRect.bottomRight, colors, stops);
canvas.save();
canvas.clipRect(alignment.inscribe(clipSize, rect));
canvas.drawPath(path, paint);
canvas.restore();
}
canvas.restore();
}
}
Here is how it appears
We can now use this concave decoration effect in container widgets within our widget tree
Container(
height: 60,
decoration:ConcaveDecoration(
colors: [Colors.black, Colors.grey.shade800],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
depression: 10))
The result is a great looking dark mode neumorphic design for our apps.
Here is a video illustrating the feel and look of the sample app.
In the app, I also implement functionality enabling toggling between light mode and dark mode. I offered an explanation on how to implement that functionality here
Thanks for reading until the end and stay tuned for more flutter tips in future.
Top comments (0)