注册

Flutter实现微信朋友圈高斯模糊效果

1. 背景


最近一个需求改版UI视觉觉得微信朋友圈的边缘高斯模糊挺好看,然后就苦逼吭哧的尝试在Flutter实现了,来看微信朋友圈点击展开的大图效果图:


image.png|400


微信朋友圈高斯模糊效果大概分4部分区域实现,如下图:
image.png


居中图片为原始图,然后背景模糊全图是原始图放大cover模式的高斯模糊,在上下两个区域分别是两层单独处理边界的高斯模糊效果特殊处理,因此有时候可以看到微信朋友圈在上下两侧有明显分界线;


2. 实践


在Flutter侧实现高斯模糊比较简单,可以直接使用系统的BackdropFilter函数实现,需要传入一个filter方式,然后对child区域进行模糊过滤;


  const BackdropFilter({
Key? key,
required this.filter,
Widget? child,
this.blendMode = BlendMode.srcOver,
}) : assert(filter != null),
super(key: key, child: child);

Flutter提供了简化ImageFiltered实现高斯模糊,代码如下:


ImageFiltered(
imageFilter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Image.network(url,fit: BoxFit.cover, height: expandedHeight, width: width),
),

通过此方式,可以非常简约实现全屏高斯模糊~,现在难点是上下边界区域的边界模糊处理,这里需要使用一个ShaderMask组件,在Flutter侧ShaderMask主要是实现渐变过渡能力的;


  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从上到下的渐变过渡,代码如下:


             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,


             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,这个我也解释不清楚:这里有一篇相关文章juejin.cn/post/684490…


这里我们将混合模式替换为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视觉吧~~费劲~~~~


作者:上班多喝水
链接:https://juejin.cn/post/7126099548409167879
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册