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
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
decoration: BoxDecoration(
boxShadow: [
const BoxShadow(
color: your_shadow_color,
),
const BoxShadow(
color: your_bg_color,
spreadRadius: -12.0,
blurRadius: 12.0,
),
],
),
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:
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();
}
}
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,
);
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"),
),
)))),
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:
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],
),
),
)
],
)
If you are comfortable with just using a package I found this one works quite well.
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
with inner shadow