[Flutter] Flutter 波浪波动效果的登录页面

转自: https://biglead.blog.csdn.net/article/details/109911109

作者:

 

B站视频教程: https://www.bilibili.com/video/BV1hK4y1j7mT

效果如下图所示

 

 

 

页面初始化

波浪也是在波动,所以先来个动画控制器 Example820 的构建如下:

class Example820 extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _ExampleState();
  }
}

class _ExampleState extends State with SingleTickerProviderStateMixin {
  ///动画控制器
  AnimationController _animationController;

  @override
  void initState() {
    super.initState();
    //创建动画控制器
    _animationController = AnimationController(
      //默认的初始值
      value: 0.0,
      //执行时间
      duration: Duration(seconds: 10),
      //值变化范围
      upperBound: 1,
      lowerBound: -1,
      vsync: this,
    );
    //重复执行
    _animationController.repeat();
  }

  @override
  void dispose() {
    //销毁
    _animationController.dispose();
    super.dispose();
  }
  ... 
}

 

页面的主体UI构建

页面主体使用 Scaffold脚手架构建,通过层叠布局Stack来组合,代码如下:

  @override
  Widget build(BuildContext context) {
    //获取当前组件的大小
    Size size = MediaQuery
        .of(context)
        .size;
    return Scaffold(
      //允许键盘弹出布局文件上移
      resizeToAvoidBottomPadding: true,
      body: Container(
        //填充
        width: size.width,
        height: size.height,
        //层叠
        child: Stack(
          children: <Widget>[
            //第一部分 水波纹背景
            buildFirstAnimation(size),
            //第二部分 顶部的文本
            buildTopText(size),
            //第三部分 底部的按钮
            buildBottomButton(size),
          ],
        ),
      ),
    );
  }

 

波浪样式的背景构建

先通过 Container 的BoxDecoration 来设置渐变的背景样式,然后再通过 ClipPath 来裁剪,最后通过 AnimatedBuilder 与 AnimationController 动起来,代码如下:

  ///  构建 AnimatedBuilder 与裁剪水波纹
  /// 
  AnimatedBuilder buildFirstAnimation(Size size) {
    return AnimatedBuilder(
      //绑定动画控制器
      animation: _animationController,
      builder: (BuildContext context, Widget child) {
        //裁剪组件
        return ClipPath(
          //自定义裁剪路径
          clipper: HeaderClipper(_animationController.value),
          //裁剪的子Widget
          child: Container(
            //高度
            height: size.height * 0.5,
            //线性渐变颜色的样式
            decoration: BoxDecoration(
              gradient: LinearGradient(
                //线性渐变的方向
                  begin: Alignment.bottomLeft,
                  end: Alignment.topRight,
                  colors: [Color(0xFFE0647B), Color(0xFFFCDD89)]),
            ),
          ),
        );
      },
    );
  }

ClipPath 用来裁剪自定义图片,定义如下:

/// 代码清单 8-34 自定义 Clipper
/// 
class HeaderClipper extends CustomClipper<Path> {
  ///取值为 -1 ~ 1.0
  double moveFlag = 0;

  HeaderClipper(this.moveFlag);

  @override
  Path getClip(Size size) {
    //创建 Path
    Path path = Path();
    //移动到点 P0点 也是曲线的起点
    path.lineTo(0, size.height * 0.8);
    //计算控制点 P1 的坐标
    double xCenter = size.width * 0.5 +
        (size.width * 0.6 + 1) * sin(moveFlag * pi);
    double yCenter = size.height * 0.8 + 69 * cos(moveFlag*pi);
    //构建 二阶贝塞尔曲线
    path.quadraticBezierTo(xCenter, yCenter, size.width, size.height * 0.8);

    path.lineTo(size.width, 0);
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    //刷新
    return true;
  }
}

用到了 二阶贝塞尔曲线 ,占位分析图如下:

 

 

 

二阶贝塞尔曲线:

 

 

也用到了 pi :

 

pi 、cos、sin 是Flutter Sdk dart.math类中提供的。

 

底部的文本输入区域

使用线性布局Column 来构建,代码如下:

 ///底部对齐的输入框
  Positioned buildBottomButton(Size size) {
    return Positioned(
      bottom: 60,
      left: 0,
      right: 0,
      child: Column(
        //包裹子Widget
        mainAxisSize: MainAxisSize.min,
        //主方向子Widget 底部对齐 (Column的垂直方向)
        mainAxisAlignment: MainAxisAlignment.end,
        //次方向子Widget居中对齐 (Column的水平方向)
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Container(
            width: size.width * 0.8,
            margin: EdgeInsets.only(top: 18),
            child: buildInputWidget('请输入账号'),
          ),
          Container(
            width: size.width * 0.8,
            margin: EdgeInsets.only(top: 18),
            child: buildInputWidget('请输入密码', isPass: true),
          ),
          Container(
            margin: EdgeInsets.only(top: 20),
            padding: EdgeInsets.only(bottom: 60),
            width: size.width * 0.7,
            child: ElevatedButton(
              onPressed: () {},
              child: Text(
                '登录',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

因为这里的文本输入框是比较类似的,所以也进行了封装调用 ,代码如下:

/// 
Widget buildInputWidget(String hint, {bool isPass = false}) {
  return TextField(
    //是否隐藏文本
    obscureText: isPass,
    //文本的边框装饰
    decoration: InputDecoration(
      //提示文本
      hintText: hint,
      //提示文本的样式
      hintStyle: TextStyle(color: Color(0xFFACACAC), fontSize: 14),
      //输入内容的内边距
      contentPadding: EdgeInsets.only(top: 20, bottom: 20, left: 38),
      //输入框可用时的边框样式
      enabledBorder: OutlineInputBorder(
        borderSide: BorderSide(color: Colors.lightBlueAccent),
        borderRadius: BorderRadius.all(Radius.circular(30.0)),
      ),
      //输入框获取输入焦点时的边框样式
      focusedBorder: OutlineInputBorder(
        borderSide: BorderSide(color: Colors.green),
        borderRadius: BorderRadius.all(Radius.circular(30.0)),
      ),
    ),
  );
}

 

posted @ 2021-02-22 10:48  我爱我家喵喵  阅读(502)  评论(0编辑  收藏  举报