34

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

Mac
  • 468
  • 1
  • 5
  • 10
  • Well there is always a simpler way to do it. Using **packages**: Here is a good package if you gotta make a neumorphic design --> [flutter_neumorphic](https://pub.dev/packages/flutter_neumorphic) [![enter image description here](https://i.stack.imgur.com/5k8o4.gif)](https://i.stack.imgur.com/5k8o4.gif) [![enter image description here](https://i.stack.imgur.com/fzzV8.gif)](https://i.stack.imgur.com/fzzV8.gif) [![enter image description here](https://i.stack.imgur.com/1dSc8.png)](https://i.stack.imgur.com/1dSc8.png) – Taba Apr 27 '21 at 04:10

9 Answers9

57
decoration: BoxDecoration(
        boxShadow: [
          const BoxShadow(
            color: your_shadow_color,
          ),
          const BoxShadow(
            color: your_bg_color,
            spreadRadius: -12.0,
            blurRadius: 12.0,
          ),
        ],
      ),
Community
  • 1
  • 1
james
  • 618
  • 7
  • 4
26

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

Alexandr Priezzhev
  • 1,383
  • 13
  • 18
  • 2
    This code was released as [sums_inner](https://pub.dev/packages/sums_inner) on Pub, btw. The code in the GitHub repository looked surprisingly familiar! – geisterfurz007 Dec 11 '20 at 13:05
  • I don't unfortunately; first thing I would give a shot is writing to SO because it's a license violation of the [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) from what I understand (because no attribution). Maybe they can give you more information then. As a sidenote: Not sure if calling other people "Scum" is accepted here (regardless of what they have done) :) – geisterfurz007 Dec 11 '20 at 15:25
  • Thank you for the advice. You are right re the used term, my bad. I was not able to edit my comment, so just copying it here: Yeah, he did not even bother to change a single line and put his own copyright. Do you have any idea where to write a claim to? – Alexandr Priezzhev Dec 11 '20 at 16:38
  • @AlexandrPriezzhev I really like the answer provided above. Can you please have a look at this question https://stackoverflow.com/questions/66514745/how-to-create-an-inner-shadow-for-custom-shape-in-flutter. It's of similar type – Sarvesh Dalvi Mar 07 '21 at 09:14
  • @SarveshDalvi, you can use my answer given here. It works well for any custom shape. – Alexandr Priezzhev Mar 08 '21 at 11:17
  • @AlexandrPriezzhev thanks for this, but can you give a hint on how to optimize the performance? I am using 35 widgets wrapped with this inner shadow and I am getting pretty bad fps. – Christian May 11 '21 at 13:14
  • @AlexandrPriezzhev could you please explain how exactly does the paint method work? I'm stuck trying to decipher the saveLayer methods and the order in which they are called together with the draw methods. – Elisey Ozerov Feb 23 '22 at 19:31
  • @AlexandrPriezzhev. Inner shadow is not working if it contains a child like ElevatedButton or ListView. – Ketan Ramani Mar 07 '22 at 07:24
  • 1
    Can I use a transparent container? Here the widget disapear when not define a opaque color – Gilian Sep 24 '22 at 01:35
13

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();
  }
}
Hossein Hadi
  • 1,229
  • 15
  • 20
Thomas
  • 174,939
  • 50
  • 355
  • 478
  • Hi Thomas! Thanks for your version. Works pretty well. I get a bug though (also in Alexanders version) ``` ════════ Exception caught by rendering library ═════════════════════════════════ Object has been disposed. The relevant error-causing widget was InnerShadow ``` This is probably caused by `saveLayer` in conjunction with `paintChild`. I could not find a fix yet. See also https://github.com/luigi-rosso/flutter-inner-shadow/issues/1 Does anyone have a fix for this? – Richard Jul 03 '20 at 09:41
  • Is solved, see https://github.com/luigi-rosso/flutter-inner-shadow/commit/e05b7c512a0deafe53a9c3893b4f116969015f59 – Richard Aug 03 '20 at 08:44
  • @Richard Thanks for noting that! An edit was proposed some days ago that made a change like that to this post, but without stating the reason, so it got rejected. I've made the same changes here now. – Thomas Aug 05 '20 at 13:40
  • I am getting The method 'paintChild' isn't defined for the type 'Canvas'. – user3808307 Jan 11 '21 at 04:44
  • @user3808307 Edited; it's a method on the context not the canvas. – Thomas Jan 11 '21 at 07:40
  • I used this also somewhat succesful but sometiimes smaller children mess with the shadows. Like a TextField child. I don't know why – Rudolf J May 04 '22 at 13:46
6

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,
);
ysalary
  • 61
  • 1
  • 1
  • Changing `rectInner` like this removed the shadow from the bottom and right side. [Alexandr's solution](https://stackoverflow.com/a/60530625/3291390) worked great for me as is. – Stack Underflow Jun 09 '20 at 23:14
4

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.

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

Abdul Rahman Shamair
  • 580
  • 1
  • 11
  • 22
stoiczek
  • 1,333
  • 1
  • 8
  • 8
4

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:

FozanKh
  • 41
  • 3
1

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],
          ),
        ),
      )
    ],
  )
Mohamed Mo Kawsara
  • 4,400
  • 2
  • 27
  • 43
0

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

https://pub.dev/packages/sums_inner

Payne Danger
  • 139
  • 1
  • 2
0

Found this package: https://pub.dev/packages/inner_shadow_widget

Works perfect for me:

InnerShadow(
 blur: 3,
 offset: const Offset(2, 2),
 child: Image.asset('path'),
),

without inner shadow

enter image description here

with inner shadow

enter image description here

MCB
  • 503
  • 1
  • 8
  • 21