Flutter實現微信朋友圈高斯模糊效果

語言: CN / TW / HK

1. 背景

最近一個需求改版UI視覺覺得微信朋友圈的邊緣高斯模糊挺好看,然後就苦逼吭哧的嘗試在Flutter實現了,來看微信朋友圈點選展開的大圖效果圖:

image.png|400

微信朋友圈高斯模糊效果大概分4部分割槽域實現,如下圖: image.png

居中圖片為原始圖,然後背景模糊全圖是原始圖放大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), )

```

就這樣實現了?當我執行時候出現如下效果,效果還挺好的:

image.png

但是當我把封面圖url替換了一個淺色圖片,卻出現如下效果,中間區域變成了黑色的,看來是我想的簡單了:

image.png

分析了下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), )

```

出現如下效果:

image.png

這裡顏色貌似符合預期,但是混合模式出現了問題,學過Android開發的一定屬性如下這張BlendMode混合模式圖片:

image.png

ShaderMaster預設的混合模式是BlendMode.modulate,這個我也解釋不清楚:這裡有一篇相關文章https://juejin.cn/post/6844903716244422670

這裡我們將混合模式替換為BlendMode.dstIn:只顯示src和dst重合部分,且src的重合部分只有不透明度有用,經過這些操作後,整體效果最後如下所示:

image.png

最後奉上完整demo的相關程式碼: Widget buildCover(BuildContext context) { double width = MediaQuery.of(context).size.width; double expandedHeight = 600; double closeHeight = 300; const String url = 'https://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視覺吧~~費勁~~~~