范文芳

Flutter 一个气泡动画背景的登录页面

 

 

import 'dart:math';
import 'dart:ui';

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: DemoLoginUI(),
  ));
}

class DemoLoginUI extends StatefulWidget {
  @override
  _DemoLoginUIState createState() => _DemoLoginUIState();
}

//全局定义获取颜色的方法
Color getRandomWhiteColor(Random random) {
  int a = random.nextInt(200);
  return Color.fromARGB(a, 255, 255, 255);
}

class _DemoLoginUIState extends State<DemoLoginUI>
    with TickerProviderStateMixin {
  List<BobbleBean> _list = [];

  Random _random = new Random(DateTime.now().microsecondsSinceEpoch);

  // 运动速度控制
  double _maxSpeed = 2.0;

  // 设置最大半径
  double _maxRadius = 100;

  // 设置最大角度
  double _maxTheta = 2 * pi;

  AnimationController _animationController;

  AnimationController _fadeAnimationController;

  @override
  void initState() {
    super.initState();

    for (int i = 0; i < 20; i++) {
      BobbleBean bean = new BobbleBean();

      bean.color = getRandomWhiteColor(_random);
      bean.position = Offset(-1, -1);

      bean.speed = _random.nextDouble() * _maxSpeed;

      bean.radius = _random.nextDouble() * _maxRadius;
      bean.theta = _random.nextDouble() * _maxTheta;

      _list.add(bean);
    }
    //创建动画控制器 1秒
    _animationController = new AnimationController(
        vsync: this, duration: Duration(milliseconds: 1000));
    // 执行刷新监听
    _animationController.addListener(() {
      setState(() {});
    });
    // 重复执行
    // _animationController.repeat();

    _fadeAnimationController = new AnimationController(
        vsync: this, duration: Duration(milliseconds: 1800));
    _fadeAnimationController.forward();
    _fadeAnimationController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _animationController.repeat();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Container(
        width: double.infinity,
        height: double.infinity,
        child: Stack(
          children: [
            // 第一部分 渐变背景
            buildBackground(),
            // 第二部分 气泡
            buildBobbleWeight(context),
            //  第三部分 高斯模糊
            buildBlurWeight(),
            //  第四部分 顶部的稳步
            buildTopText(),
            //  第五部分 输入区域
            buildBottomColumn()
          ],
        ),
      ),
    );
  }

  // 第一部分 渐变背景
  buildBackground() {
    return Container(
      decoration: BoxDecoration(
          gradient: LinearGradient(
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
              colors: [
            Colors.lightBlueAccent.withOpacity(0.3),
            Colors.lightBlue.withOpacity(0.3),
            Colors.blue.withOpacity(0.3),
          ])),
    );
  }

  buildBobbleWeight(BuildContext context) {
    //画板
    return CustomPaint(
      size: MediaQuery.of(context).size,
      painter: CustomMyPainter(list: _list, random: _random),
    );
  }

  buildBlurWeight() {
    return BackdropFilter(
      filter: ImageFilter.blur(sigmaX: 0.3, sigmaY: 0.3),
      child: Container(
        color: Colors.white.withOpacity(0.1),
      ),
    );
  }

  buildTopText() {
    return Positioned(
      left: 0,
      right: 0,
      top: 160,
      child: Text(
        'Hello World',
        style: TextStyle(fontSize: 44, color: Colors.deepPurple),
        textAlign: TextAlign.center,
      ),
    );
  }

  buildBottomColumn() {
    return Positioned(
      left: 44,
      right: 44,
      bottom: 84,
      child: FadeTransition(
        opacity: _fadeAnimationController,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            //  自定义文本输入框
            TextFieldWidget(
              obscureText: false,
              labelText: "账号",
              prefixIconData: Icons.phone_android_outlined,
            ),
            SizedBox(
              height: 14,
            ),
            TextFieldWidget(
              obscureText: true,
              labelText: "密码",
              prefixIconData: Icons.lock_outline,
              suffixIconData: Icons.visibility,
            ),
            SizedBox(
              height: 14,
            ),
            Container(
              alignment: Alignment.centerRight,
              child: Text(
                '忘记密码',
                textAlign: TextAlign.end,
                style: TextStyle(fontSize: 14, color: Colors.blue),
              ),
            ),

            SizedBox(
              height: 14,
            ),

            Container(
              height: 42,
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () {},
                child: Text('登录'),
              ),
            ),

            SizedBox(
              height: 12,
            ),

            Container(
              height: 42,
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () {},
                child: Text('注册'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class TextFieldWidget extends StatelessWidget {
  Function(String value) onChanged;
  bool obscureText;
  String labelText;
  IconData prefixIconData;
  IconData suffixIconData;

  TextFieldWidget(
      {this.onChanged,
      this.obscureText,
      this.labelText,
      this.prefixIconData,
      this.suffixIconData});

  @override
  Widget build(BuildContext context) {
    return TextField(
      onChanged: onChanged,
      obscureText: obscureText,
      style: TextStyle(color: Colors.blue, fontSize: 14.0),
      //  输入框可用时边框配置
      decoration: InputDecoration(
        filled: true,
        labelText: labelText,
        // 去掉默认的下划线
        enabledBorder: UnderlineInputBorder(borderSide: BorderSide.none),
        // 获取输入焦点时的边框样式
        focusedBorder: OutlineInputBorder(
            borderRadius: BorderRadius.all(Radius.circular(10)),
            borderSide: BorderSide(color: Colors.blue)),
        prefixIcon: Icon(
          prefixIconData,
          size: 18,
          color: Colors.blue,
        ),
        suffixIcon: Icon(
          suffixIconData,
          size: 18,
          color: Colors.blue,
        ),
      ),
    );
  }
}

class CustomMyPainter extends CustomPainter {
  List<BobbleBean> list;

  Random random;

  Paint _paint = new Paint()..isAntiAlias = true;

  CustomMyPainter({this.list, this.random});

  @override
  void paint(Canvas canvas, Size size) {
    list.forEach((element) {
      Offset newCenterOffset = calculateXY(element.speed, element.theta);

      double dx = newCenterOffset.dx + element.position.dx;
      double dy = newCenterOffset.dx + element.position.dy;

      if (dx < 0 || dx > size.width) {
        dx = random.nextDouble() * size.width;
      }

      if (dy < 0 || dy > size.height) {
        dy = random.nextDouble() * size.height;
      }

      element.position = Offset(dx, dy);
    });

    list.forEach((element) {
      _paint.color = element.color;

      canvas.drawCircle(element.position, element.radius, _paint);
    });
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }

  Offset calculateXY(double speed, double theta) {
    return Offset(speed * cos(theta), speed * sin(theta));
  }
}

/// 定义气泡
class BobbleBean {
  //位置
  Offset position;

  //颜色
  Color color;

  //速度
  double speed;

  // 角度
  double theta;

  // 半径
  double radius;
}
View Code

 

 

原文地址

 

posted @ 2021-02-22 18:05  布I衣  阅读(395)  评论(0编辑  收藏  举报