Flutter實現微信朋友圈高斯模糊效果
1. 背景
最近一個需求改版UI視覺覺得微信朋友圈的邊緣高斯模糊挺好看,然後就苦逼吭哧的嘗試在Flutter實現了,來看微信朋友圈點選展開的大圖效果圖:
微信朋友圈高斯模糊效果大概分4部分割槽域實現,如下圖:
居中圖片為原始圖,然後背景模糊全圖是原始圖放大cover模式的高斯模糊,在上下兩個區域分別是兩層單獨處理邊界的高斯模糊效果特殊處理,因此有時候可以看到微信朋友圈在上下兩側有明顯分界線;
2. 實踐
在Flutter側實現高斯模糊比較簡單,可以直接使用系統的BackdropFilter函式實現,需要傳入一個filter方式,然後對child區域進行模糊過濾;
dart
const BackdropFilter({
Key? key,
required this.filter,
Widget? child,
this.blendMode = BlendMode.srcOver,
}) : assert(filter != null),
super(key: key, child: child);
Flutter提供了簡化ImageFiltered實現高斯模糊,程式碼如下:
dart
ImageFiltered(
imageFilter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Image.network(url,fit: BoxFit.cover, height: expandedHeight, width: width),
),
通過此方式,可以非常簡約實現全屏高斯模糊~,現在難點是上下邊界區域的邊界模糊處理,這裡需要使用一個ShaderMask元件,在Flutter側ShaderMask主要是實現漸變過渡能力的;
dart
const ShaderMask({
Key? key,
required this.shaderCallback,
this.blendMode = BlendMode.modulate,
Widget? child,
}) : assert(shaderCallback != null),
assert(blendMode != null),
super(key: key, child: child);
其需要shaderCallback回撥漸變Shader,共提供3種漸變模式: * RadialGradient:放射狀漸變 * LinearGradient:線性漸變 * SweepGradient:扇形漸變
這裡我們需要使用線性漸變LinearGradient從上到下的漸變過渡,程式碼如下: ```dart ShaderMask( shaderCallback: (Rect bounds) { return const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.white, Colors.transparent ], ).createShader(bounds); }, child: Image.network(url, fit: BoxFit.cover, height: closeHeight, width: width), )
```
就這樣實現了?當我執行時候出現如下效果,效果還挺好的:
但是當我把封面圖url替換了一個淺色圖片,卻出現如下效果,中間區域變成了黑色的,看來是我想的簡單了:
分析了下Flutter線性過度原始碼,其將顏色進行過渡, Color transparent = Color(0x00000000) , 而 Color white = Color(0xFFFFFFFF),可以看到除了透明度之外,需要保證顏色不要發生大變化,其實我們訴求只是需要將透明度發生漸變即可,因此將Colors.white改為Colors.black, ```dart ShaderMask( shaderCallback: (Rect bounds) { return const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black, Colors.transparent ], ).createShader(bounds); }, child: Image.network(url, fit: BoxFit.cover, height: closeHeight, width: width), )
```
出現如下效果:
這裡顏色貌似符合預期,但是混合模式出現了問題,學過Android開發的一定屬性如下這張BlendMode混合模式圖片:
ShaderMaster預設的混合模式是BlendMode.modulate,這個我也解釋不清楚:這裡有一篇相關文章http://juejin.cn/post/6844903716244422670
這裡我們將混合模式替換為BlendMode.dstIn:只顯示src和dst重合部分,且src的重合部分只有不透明度有用,經過這些操作後,整體效果最後如下所示:
最後奉上完整demo的相關程式碼:
Widget buildCover(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double expandedHeight = 600;
double closeHeight = 300;
const String url =
'http://img.alicdn.com/imgextra/i2/O1CN01YWcPh81fbUvpcjUXp_!!6000000004025-2-tps-842-350.png';
return Container(
height: expandedHeight,
alignment: Alignment.center,
child: Stack(
children: [
ImageFiltered(
imageFilter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Image.network(url,
fit: BoxFit.cover, height: expandedHeight, width: width),
),
Container(
height: expandedHeight,
alignment: Alignment.center,
child: ShaderMask(
shaderCallback: (Rect bounds) {
return const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black,
Colors.transparent
],
).createShader(bounds);
},
blendMode: BlendMode.dstIn,
child: Image.network(url,
fit: BoxFit.cover, height: closeHeight, width: width),
),
)
],
),
);
}
3. 總結
通過實踐,發現Flutter實現高斯模糊BackdropFilter/ImageFiltered元件,漸變實現方式ShaderMask,此外還需要掌握圖形學的BlendMode混合模式,以後在碰到類似需求時候建議直接砍了UI視覺吧~~費勁~~~~