起底Flutter中的拟态美学-高斯模糊毛玻璃特效

3,819 阅读3分钟

最近看到一个关于App UI 界面中的毛玻璃特效,给人一种高级的设计感,强烈的视觉冲击力,感觉非常赞,用上他可以为我们的App增色不少,于是我用Flutter复刻了一把,最后送上完整代码,学会了可以直接拿去用哦,快来看看吧

先看成品

image.png

  • 用到的主要技术点有:
    • Flutter中的高斯模糊滤镜
    • 自定义渐变色边框

下面我们开始制作吧

1. 先找个底图做背景

image.png

核心代码

Stack(
  children: [
    /// 底图
    SizedBox(
      width: MediaQuery.of(context).size.width,
      height: MediaQuery.of(context).size.height,
      child: Image.asset(
        'assets/images/test.png',
        fit: BoxFit.fitWidth,
      ),
    ),
    ///

  ],
)

2. 再画个矩形

image.png 核心代码

/// 画个矩形
Container(
  width: 300,
  height: 200,
  colosr: Colosr.white,
)

3.给矩形加个渐变色

image.png 这里注意不是简单的纯色。 选择纯白色(#FFFFFF)作为渐变颜色,但不同之处在于两端的不透明度不同:

  • 渐变起点颜色:不透明度 40%
  • 渐变终点颜色:不透明度 10%

这样的渐变让卡片显得更加细腻、光滑,模拟出玻璃表面的微妙变化。 核心代码

decoration: BoxDecoration(
  borderRadius: BorderRadius.circular(20),
  gradient: LinearGradient(
    colors: [
      Colors.white.withOpacity(0.4),
      Colors.white.withOpacity(0.1),
    ],
    begin: const Alignment(-1, -1),
    end: const Alignment(0.3, 0.5),
  ),
),

4. 添加背景模糊

image.png 这里我们实用了Flutter中的BackdropFilter组件,同时配合ClipRRect剪出圆角矩形的形状,同时只让卡片部分是模糊的,其他部分是清晰的,这点很重要。为了模仿玻璃的质感,我们需要添加 背景模糊 效果。模糊能为设计增添空间感,模拟出真实玻璃的视觉效果。建议将模糊值设置在 20px 左右,你可以根据实际需求进行微调。模糊会让背景元素呈现朦胧感,增强设计的深度。

核心代码

 ClipRRect(
  borderRadius: BorderRadius.circular(20),
  child: BackdropFilter(
    filter: ImageFilter.blur(
      sigmaX: 20,
      sigmaY: 20,
    ),
    ///省略...
  ),
),

5. 加个阴影

boxShadow: [
  BoxShadow(
    color: const Color(0x000000).withOpacity(0.1),
    offset: const Offset(0, 1),
    blurRadius: 24,
    spreadRadius: -1,
  )
],
    

6. 渐变色边框

image.png 这一步是点睛之笔,加了边框后会模拟出璃材质的边缘通常具有光滑的反射感。为卡片添加一个优雅的 3px 边框,并使用渐变填充,以模拟定向光源的反射效果。这里实用到了Flutter中的自定义画布,再自定义画布上画出渐变色边框

CustomPaint(
    painter: GradientBoundPainter(
      colors: [
        const Color(0xFFFFFFFF).withOpacity(0.5),
        const Color(0xFFFF48DB).withOpacity(0.5),
      ],
      width: bc.maxWidth,
      height: bc.maxHeight,
    ),
    child: ///省略...
)

7. 加点文字

image.png 卡片的基础已经完成,现在是时候为其添加内容了。这里注意要使用白色文字,设置不透明度为 50%,创造出一种 压印效果。

最后,送上完整代码,欢迎关注微信公众号 【技术万有引力】

import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class Test extends StatefulWidget {
  const Test({Key? key}) : super(key: key);

  @override
  State<Test> createState() => _TestState();
}

class _TestState extends State<Test> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          SizedBox(
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: Image.asset(
              'assets/images/test.png',
              fit: BoxFit.fitWidth,
            ),
          ),
          Align(
            alignment: const Alignment(0, -0.1),
            child: ClipRRect(
              borderRadius: BorderRadius.circular(20),
              child: BackdropFilter(
                filter: ImageFilter.blur(
                  sigmaX: 20,
                  sigmaY: 20,
                ),
                child: SizedBox(
                  width: 300,
                  height: 200,
                  child: LayoutBuilder(
                    builder: (BuildContext _, BoxConstraints bc) {
                      return CustomPaint(
                        painter: GradientBoundPainter(
                          colors: [
                            const Color(0xFFFFFFFF).withOpacity(0.5),
                            const Color(0xFFFF48DB).withOpacity(0.5),
                          ],
                          width: bc.maxWidth,
                          height: bc.maxHeight,
                        ),
                        child: Container(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 30,
                            vertical: 40,
                          ),
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(20),
                            gradient: LinearGradient(
                              colors: [
                                Colors.white.withOpacity(0.4),
                                Colors.white.withOpacity(0.1),
                              ],
                              begin: const Alignment(-1, -1),
                              end: const Alignment(0.3, 0.5),
                            ),
                            boxShadow: [
                              BoxShadow(
                                  color: const Color(0x000000).withOpacity(0.1),
                                  offset: const Offset(0, 1),
                                  blurRadius: 24,
                                  spreadRadius: -1)
                            ],
                          ),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Row(
                                children: [
                                  Text(
                                    'MEMBERSHIP',
                                    style: TextStyle(
                                      color: Colors.white.withOpacity(0.5),
                                      fontSize: 18,
                                      fontWeight: FontWeight.bold,
                                    ),
                                  ),
                                  Expanded(child: SizedBox()),
                                ],
                              ),
                              Expanded(child: SizedBox()),
                              Text(
                                'GRAVITY TECHNOLOGY',
                                style: TextStyle(
                                  color: Colors.white.withOpacity(0.5),
                                  fontSize: 18,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                              Text(
                                'SOFTMAX.TOOL',
                                style: TextStyle(
                                  color: Colors.white.withOpacity(0.5),
                                  fontSize: 16,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                            ],
                          ),
                        ),
                      );
                    },
                  ),
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

class GradientBoundPainter extends CustomPainter {
  final List<Color> colors;
  final double width;
  final double height;
  final double strokeWidth;

  const GradientBoundPainter({
    Key? key,
    required this.colors,
    required this.width,
    required this.height,
    this.strokeWidth = 3.0,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final rectWidth = width, rectHeight = height;
    final radius = 20.0;
    //定义矩形的位置和尺寸
    Rect rect = Offset(
            size.width / 2 - rectWidth / 2, size.height / 2 - rectHeight / 2) &
        Size(rectWidth, rectHeight);
    //RRect.fromRectAndRadius一个具有圆角的矩形
    RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(radius));
    //绘制
    final paint = Paint()
      //创建线性渐变着色器
      ..shader = LinearGradient(
        begin: Alignment.centerLeft,
        end: Alignment.centerRight,
        colors: colors,
      ).createShader(rect)
      ..strokeWidth = strokeWidth
      //只绘制边框而不填充
      ..style = PaintingStyle.stroke;
    canvas.drawRRect(rRect, paint);
  }

  @override
  bool shouldRepaint(covariant GradientBoundPainter oldDelegate) {
    return oldDelegate.colors != colors;
  }
}