DEV Community

Erick Murai
Erick Murai

Posted on

Implementing Neumorphic design in flutter without a package

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],
}
Enter fullscreen mode Exit fullscreen mode

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,
              )
            ]);
Enter fullscreen mode Exit fullscreen mode

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),)
Enter fullscreen mode Exit fullscreen mode

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 Image description
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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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
                          ]))
Enter fullscreen mode Exit fullscreen mode

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],
      );
}
Enter fullscreen mode Exit fullscreen mode

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,
              )
            ]);
Enter fullscreen mode Exit fullscreen mode

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),)
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

Here is how it appears Image description
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))
Enter fullscreen mode Exit fullscreen mode

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)