'Inner shadow effect in flutter

According to the github issue there is no inset attribute in ShadowBox yet. Is there any workaround how to emulate inner shadow right now in flutter.

I like to achieve inner shadow effects like you can see on the following images

enter image description here

enter image description here



Solution 1:[1]

decoration: BoxDecoration(
        boxShadow: [
          const BoxShadow(
            color: your_shadow_color,
          ),
          const BoxShadow(
            color: your_bg_color,
            spreadRadius: -12.0,
            blurRadius: 12.0,
          ),
        ],
      ),

Solution 2:[2]

Here is what I do:

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class InnerShadow extends SingleChildRenderObjectWidget {
  const InnerShadow({
    Key key,
    this.blur = 10,
    this.color = Colors.black38,
    this.offset = const Offset(10, 10),
    Widget child,
  }) : super(key: key, child: child);

  final double blur;
  final Color color;
  final Offset offset;

  @override
  RenderObject createRenderObject(BuildContext context) {
    final _RenderInnerShadow renderObject = _RenderInnerShadow();
    updateRenderObject(context, renderObject);
    return renderObject;
  }

  @override
  void updateRenderObject(
      BuildContext context, _RenderInnerShadow renderObject) {
    renderObject
      ..color = color
      ..blur = blur
      ..dx = offset.dx
      ..dy = offset.dy;
  }
}

class _RenderInnerShadow extends RenderProxyBox {
  double blur;
  Color color;
  double dx;
  double dy;

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child == null) return;

    final Rect rectOuter = offset & size;
    final Rect rectInner = Rect.fromLTWH(
      offset.dx,
      offset.dy,
      size.width - dx,
      size.height - dy,
    );
    final Canvas canvas = context.canvas..saveLayer(rectOuter, Paint());
    context.paintChild(child, offset);
    final Paint shadowPaint = Paint()
      ..blendMode = BlendMode.srcATop
      ..imageFilter = ImageFilter.blur(sigmaX: blur, sigmaY: blur)
      ..colorFilter = ColorFilter.mode(color, BlendMode.srcOut);

    canvas
      ..saveLayer(rectOuter, shadowPaint)
      ..saveLayer(rectInner, Paint())
      ..translate(dx, dy);
    context.paintChild(child, offset);
    context.canvas..restore()..restore()..restore();
  }
}

then just use it somewhere:

InnerShadow(
  blur: 5,
  color: const Color(0xFF477C70),
  offset: const Offset(5, 5),
  child: Container(
    decoration: const BoxDecoration(
      borderRadius: BorderRadius.all(Radius.circular(8)),
      color: Color(0xFFE9EFEC),
    ),
    height: 100,
  ),
)

The result: result

Solution 3:[3]

I've taken the answer by @AlexandrPriezzhev and improved it to use the standard Shadow class (including the semantics of its offset field), added support for multiple shadows, and shaved off a saveLayer() call which should make it a bit more efficient:

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class InnerShadow extends SingleChildRenderObjectWidget {
  const InnerShadow({
    Key? key,
    this.shadows = const <Shadow>[],
    Widget? child,
  }) : super(key: key, child: child);

  final List<Shadow> shadows;

  @override
  RenderObject createRenderObject(BuildContext context) {
    final renderObject = _RenderInnerShadow();
    updateRenderObject(context, renderObject);
    return renderObject;
  }

  @override
  void updateRenderObject(
      BuildContext context, _RenderInnerShadow renderObject) {
    renderObject.shadows = shadows;
  }
}

class _RenderInnerShadow extends RenderProxyBox {
  late List<Shadow> shadows;

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child == null) return;
    final bounds = offset & size;

    context.canvas.saveLayer(bounds, Paint());
    context.paintChild(child!, offset);

    for (final shadow in shadows) {
      final shadowRect = bounds.inflate(shadow.blurSigma);
      final shadowPaint = Paint()
        ..blendMode = BlendMode.srcATop
        ..colorFilter = ColorFilter.mode(shadow.color, BlendMode.srcOut)
        ..imageFilter = ImageFilter.blur(
            sigmaX: shadow.blurSigma, sigmaY: shadow.blurSigma);
      context.canvas
        ..saveLayer(shadowRect, shadowPaint)
        ..translate(shadow.offset.dx, shadow.offset.dy);
      context.paintChild(child!, offset);
      context.canvas.restore();
    }

    context.canvas.restore();
  }
}

Solution 4:[4]

James's answer creates a shadow outside which warps the shape. Mohammed's answer is a gradient and not proper shadow but Alexandr's solution works perfectly for me with a small fix! rectInner has to be updated.

final Rect rectInner = Rect.fromLTWH(
    offset.dx,
    offset.dy,
    size.width,
    size.height,
);

Solution 5:[5]

You can use do it using multiple BoxShadow, where the first one (below) is darker, and a lighter one on top, and top it off with the a border with greyish color to make the container pops.

Code:

Container(
                        width: 100,
                        height: 100,
                        alignment: Alignment.center,
                        decoration: BoxDecoration(
                            border: Border.fromBorderSide(
                              BorderSide(color: Colors.black12),
                            ),
                            shape: BoxShape.circle,
                            boxShadow: [
                              BoxShadow(color: Colors.black, blurRadius: 1, spreadRadius: 0),
                              BoxShadow(color: Colors.white, blurRadius: 10, spreadRadius: 5),
                            ]),
                        child: Icon(Icons.pause, size: 70, color: Colors.black54),
                      ),

result:

Solution 6:[6]

Alternatively, you can solve this problem by using normal box shadow and two containers - the outer one acting as a background and inner one providing the shadow shadow.

Code:

      Container(
          padding: EdgeInsets.only(top: 30, left: 10, right: 10),
          child: Container(
              padding: EdgeInsets.all(10),
              child: Container(
                  decoration: BoxDecoration(
                      color: Colors.grey,
                      borderRadius: BorderRadius.all(Radius.circular(12))),
                  padding: EdgeInsets.all(10),
                  child: Container(
                    padding: EdgeInsets.zero,
                    decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.all(Radius.circular(8)),
                        boxShadow: [
                          BoxShadow(
                              color: Colors.white,
                              blurRadius: 10,
                              spreadRadius: 10)
                        ]),
                    width: double.infinity,
                    height: 272,
                    child: Center(
                      child: Text("Content goes here"),
                    ),
                  )))),

Produces: Screen shot

Solution 7:[7]

james's answer did not do the trick for me.

So I simply made it out by placing an inner Gradient layer above my container/image, thus, I had the kind of inner shadow I wanted (only from the bottom in my case).

Stack(children: <Widget>[
      Container(
        decoration: BoxDecoration(
          image: DecorationImage(
            fit: BoxFit.cover,
            image: AssetImage('images/bg.jpg') /*NetworkImage(imageUrl)*/,
          ),
        ),
        height: 350.0,
      ),
      Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: FractionalOffset.topCenter,
            end: FractionalOffset.bottomCenter,
            colors: [
              Colors.grey.withOpacity(0.0),
              Colors.black54,
            ],
            stops: [0.95, 1.0],
          ),
        ),
      )
    ],
  )

Solution 8:[8]

Here is the code of inner shadow

First Crate a this class

class InnerShadow extends SingleChildRenderObjectWidget {
  const InnerShadow({
    this.blur = 10,
    this.color = Colors.black38,
    this.offset = const Offset(10, 10),
    required Widget child,
  }) : super(child: child);

  final double blur;
  final Color color;
  final Offset offset;

  @override
  RenderObject createRenderObject(BuildContext context) {
    final _RenderInnerShadow renderObject = _RenderInnerShadow();
    updateRenderObject(context, renderObject);
    return renderObject;
  }

  @override
  void updateRenderObject(
      BuildContext context, _RenderInnerShadow renderObject) {
    renderObject
      ..color = color
      ..blur = blur
      ..dx = offset.dx
      ..dy = offset.dy;
  }
}

class _RenderInnerShadow extends RenderProxyBox {
  late double blur;
  late Color color;
  late double dx;
  late double dy;

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child == null) return;

    final Rect rectOuter = offset & size;
    final Rect rectInner = Rect.fromLTWH(
      offset.dx,
      offset.dy,
      size.width - dx,
      size.height - dy,
    );
    final Canvas canvas = context.canvas..saveLayer(rectOuter, Paint());
    context.paintChild(child!, offset);
    final Paint shadowPaint = Paint()
      ..blendMode = BlendMode.srcATop
      ..imageFilter = ImageFilter.blur(sigmaX: blur, sigmaY: blur)
      ..colorFilter = ColorFilter.mode(color, BlendMode.srcOut);

    canvas
      ..saveLayer(rectOuter, shadowPaint)
      ..saveLayer(rectInner, Paint())
      ..translate(dx, dy);
    context.paintChild(child!, offset);
    context.canvas
      ..restore()
      ..restore()
      ..restore();
  }
}

After using InnerShadow class like this

InnerShadow(
      blur: 2,
      offset: const Offset(-3, -8),
      color: Colors.black12.withOpacity(0.2),
      child: Container(
        child: ClipRRect(
          borderRadius: BorderRadius.circular(30.0),
          child: Image.asset(
            'assets/icons/abc.png',
            fit: BoxFit.fill,
          ),
        ),
        width: 30.h,
        height: 30.h,
        decoration: BoxDecoration(
          color: Colors.white.withOpacity(0.01),
          borderRadius: const BorderRadius.all(Radius.circular(30)),
        ),
      ),
    )

Here is my output

enter image description here

Solution 9:[9]

If you are comfortable with just using a package I found this one works quite well.

https://pub.dev/packages/sums_inner

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 Alexandr Priezzhev
Solution 3 Hossein Hadi
Solution 4 ysalary
Solution 5
Solution 6 stoiczek
Solution 7
Solution 8 Paresh Mangukiya
Solution 9 Payne Danger