Last active
February 26, 2025 14:12
-
-
Save flar/e6258443da95ae0815a593959f5a701b to your computer and use it in GitHub Desktop.
Example of how to fade the edges of a BackdropFilter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:ui'; | |
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
theme: ThemeData( | |
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), | |
useMaterial3: true, | |
), | |
home: const MyHomePage(title: 'Flutter Demo Home Page'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
const MyHomePage({super.key, required this.title}); | |
final String title; | |
@override | |
State<MyHomePage> createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
double horizontalFadeAmount = 0.0; | |
double verticalFadeAmount = 10.0; | |
double fadeBias = 1.0; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
backgroundColor: Theme.of(context).colorScheme.inversePrimary, | |
title: Text(widget.title), | |
), | |
body: Column( | |
children: <Widget>[ | |
Expanded( | |
child: Stack( | |
alignment: Alignment.center, | |
children: [ | |
Text( | |
'xxx' * 1000, | |
), | |
ClipRect( | |
child: SizedBox( | |
width: 300, | |
height: 300, | |
child: BackdropFilter( | |
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), | |
child: Stack( | |
alignment: Alignment.center, | |
children: <Widget>[ | |
if (horizontalFadeAmount > 0.0 || verticalFadeAmount > 0.0) | |
CustomPaint( | |
painter: FadedEdgePainter( | |
horizontalFadeSize: horizontalFadeAmount, | |
verticalFadeSize: verticalFadeAmount, | |
bias: fadeBias, | |
), | |
size: const Size(400, 400), | |
), | |
const Text('BDF content'), | |
], | |
), | |
), | |
) | |
), | |
], | |
), | |
), | |
Column( | |
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
children: <Widget>[ | |
Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
const Text('fade horizontal edges'), | |
Slider( | |
value: horizontalFadeAmount, | |
min: 0.0, | |
max: 20.0, | |
onChanged: (value) { | |
setState(() { | |
horizontalFadeAmount = value; | |
}); | |
},), | |
], | |
), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
const Text('fade vertical edges'), | |
Slider( | |
value: verticalFadeAmount, | |
min: 0.0, | |
max: 20.0, | |
onChanged: (value) { | |
setState(() { | |
verticalFadeAmount = value; | |
}); | |
},), | |
], | |
), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
const Text('fade bias'), | |
Slider( | |
value: fadeBias, | |
min: 0.0, | |
max: 1.0, | |
onChanged: (value) { | |
setState(() { | |
fadeBias = value; | |
}); | |
},), | |
], | |
), | |
], | |
), | |
], | |
), | |
); | |
} | |
} | |
class FadedEdgePainter extends CustomPainter { | |
FadedEdgePainter({ | |
this.horizontalFadeSize = 0.0, | |
this.verticalFadeSize = 0.0, | |
this.bias = 1.0, | |
}); | |
final double horizontalFadeSize; | |
final double verticalFadeSize; | |
// 0.0 means no fade visible, 1.0 means full fade from 100% to 0% visible | |
final double bias; | |
@override | |
void paint(Canvas canvas, Size size) { | |
Paint p = Paint(); | |
p.imageFilter = ImageFilter.blur( | |
sigmaX: horizontalFadeSize, | |
sigmaY: verticalFadeSize, | |
tileMode: TileMode.decal, | |
); | |
p.blendMode = BlendMode.dstIn; | |
// The color doesn't matter, only the alpha value... | |
p.color = Colors.purple; | |
// You need to use a smaller rect than the space you are trying to fill. | |
// | |
// With no inset then the fading at the edge will go from 100% to 50% as | |
// you approach the edge and then drop suddenly to 0% at the edge. That's | |
// because the blur is centered on the edge with half of it on the | |
// interior and half on the exterior, but you are clipped to the size, | |
// so you never see the ramp from 50% to 0%. | |
// | |
// By using a smaller rect, you can bring more of that final ramp to 0 | |
// into the visible area. Or, maybe you want the blur to jump from | |
// 50% to 0% at the edge - play with the bias to check out different looks. | |
double hGap = horizontalFadeSize * (2.0 * bias - 1.0); | |
double vGap = verticalFadeSize * (2.0 * bias - 1.0); | |
Rect r = Rect.fromLTRB(hGap, vGap, size.width - hGap, size.height - vGap); | |
canvas.drawRect(r, p); | |
} | |
@override | |
bool shouldRepaint(covariant CustomPainter oldDelegate) => true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment