'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
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:
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"),
),
)))),
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
Solution 9:[9]
If you are comfortable with just using a package I found this one works quite well.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow