This time I wish to have a gradient blur effect over the limits of my ScrollingView. So, I have the blur effect at the right position, but I wish the effect grows slowly until the max blur. I don't want the gap between the non-blurred part and one blurred.
This image shows my problem. I don't want that horrible line caused by the effect. It is too evident ...
I've been looking on the internet for some solution, but none of them matches my needs. I put below the code that I'm having right now ...
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
SingleChildScrollView(
//fancy content
),
Positioned(
top: 0,
child: ClipRRect(
child: BackdropFilter(
filter: ui.ImageFilter.blur(
sigmaX: 5,
sigmaY: 5,
),
child: Container(width: width, height: height*0.15, color: Colors.transparent,)
),
),
),
],
),
);
Thanks :)
-
You can use Gradients to achieve this, Linear Gradient with Shader Mask can be used to fix this. Also you can with different different Opacity Values to make blur gradually.Devraj– Devraj2024年10月04日 12:21:01 +00:00Commented Oct 4, 2024 at 12:21
-
Check this question: stackoverflow.com/questions/60311065/…Lynxi– Lynxi2024年10月04日 13:47:20 +00:00Commented Oct 4, 2024 at 13:47
-
@Lynxi this should be ok with an Image ... but I don't have to Blur and image, but a WidgetGeremia Moretti– Geremia Moretti2024年10月11日 08:25:41 +00:00Commented Oct 11, 2024 at 8:25
-
@Devraj do you have an example? The way you wrote is quite vague.Geremia Moretti– Geremia Moretti2024年10月11日 08:27:43 +00:00Commented Oct 11, 2024 at 8:27
1 Answer 1
The issue is that BackdropFilter applies uniform blur, causing a hard edge. To create a gradual gradient blur, layer multiple BackdropFilter widgets with increasing blur values and decreasing opacity. Here's a solution:
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
SingleChildScrollView(
// fancy content
),
// Gradient blur using multiple layers
Positioned(
top: 0,
child: Container(
width: width,
height: height * 0.15,
child: Stack(
children: List.generate(10, (index) {
final blurFactor = (index + 1) * 0.5; // 0.5, 1.0, 1.5, ..., 5.0
final opacity = 1.0 / 10; // Each layer contributes equally
final layerHeight = (height * 0.15) / 10;
return Positioned(
top: index * layerHeight,
left: 0,
right: 0,
child: ClipRect(
child: BackdropFilter(
filter: ui.ImageFilter.blur(
sigmaX: blurFactor,
sigmaY: blurFactor,
),
child: Container(
height: layerHeight * (index + 1),
color: Colors.transparent.withOpacity(opacity),
),
),
),
);
}),
),
),
),
],
),
);
A more efficient approach using a ShaderMask with BackdropFilter:
import 'dart:ui' as ui;
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
SingleChildScrollView(
// fancy content
),
Positioned(
top: 0,
child: ShaderMask(
shaderCallback: (Rect bounds) {
return ui.Gradient.linear(
Offset(0, 0),
Offset(0, bounds.height),
[Colors.transparent, Colors.black],
[0.0, 1.0],
);
},
blendMode: BlendMode.dstIn,
child: ClipRect(
child: BackdropFilter(
filter: ui.ImageFilter.blur(
sigmaX: 5,
sigmaY: 5,
),
child: Container(
width: width,
height: height * 0.15,
color: Colors.transparent,
),
),
),
),
),
],
),
);
Most effective: combine multiple blur layers with a gradient mask:
import 'dart:ui' as ui;
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
SingleChildScrollView(
// fancy content
),
Positioned(
top: 0,
child: ClipRect(
child: Stack(
children: [
// Base blur layer with full strength
BackdropFilter(
filter: ui.ImageFilter.blur(
sigmaX: 5,
sigmaY: 5,
),
child: Container(
width: width,
height: height * 0.15,
color: Colors.transparent,
),
),
// Gradient mask to fade the blur
ShaderMask(
shaderCallback: (Rect bounds) {
return ui.Gradient.linear(
Offset(0, 0),
Offset(0, bounds.height),
[
Colors.transparent,
Colors.white,
],
[0.0, 1.0], // Start transparent at top, fully opaque at bottom
);
},
blendMode: BlendMode.dstIn,
child: Container(
width: width,
height: height * 0.15,
color: Colors.white,
),
),
],
),
),
),
],
),
);
Note: ShaderMask with BlendMode.dstIn masks the blur itself. Since BackdropFilter applies to what's behind it, the mask affects how much of the blurred area is visible.
Best solution: use stacked blur layers with varying intensities:
import 'dart:ui' as ui;
Widget _buildGradientBlur(double width, double height) {
const blurHeight = 0.15;
final totalHeight = height * blurHeight;
const layers = 15; // More layers = smoother gradient
return Positioned(
top: 0,
child: ClipRect(
child: Stack(
children: List.generate(layers, (index) {
// Blur increases from top to bottom
final blurProgress = (index + 1) / layers; // 0 to 1
final blurSigma = 5.0 * blurProgress;
// Each layer covers from top to its position
final layerTop = 0.0;
final layerHeight = totalHeight * blurProgress;
return Positioned(
top: layerTop,
left: 0,
right: 0,
child: BackdropFilter(
filter: ui.ImageFilter.blur(
sigmaX: blurSigma,
sigmaY: blurSigma,
),
child: Container(
height: layerHeight,
color: Colors.transparent,
),
),
);
}),
),
),
);
}
// In your Scaffold:
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
SingleChildScrollView(
// fancy content
),
_buildGradientBlur(width, height),
],
),
);
Most performant: a single BackdropFilter with an opacity gradient:
import 'dart:ui' as ui;
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
SingleChildScrollView(
// fancy content
),
Positioned(
top: 0,
child: ClipRect(
child: BackdropFilter(
filter: ui.ImageFilter.blur(
sigmaX: 5,
sigmaY: 5,
),
child: Container(
width: width,
height: height * 0.15,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.white.withOpacity(0.3),
],
),
),
),
),
),
),
],
),
);
It is recommended to start with the stacked layers approach (most control). Adjust the number of layers (15–20) and blur values to fine-tune the gradient. The key is overlapping layers with increasing blur intensity from top to bottom to avoid the hard line.