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

6,952 阅读3分钟

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视觉吧~~费劲~~~~