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; }