flutter 好看的动画登陆页学习

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

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(

        primarySwatch: Colors.blue,

        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

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



class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin{

  List<BobbleBean> _list = [];
  //随机数
  Random _random =new Random(DateTime.now().millisecondsSinceEpoch);
  double _maxSpend = 1.0;
  double _maxRadius = 100;
  double _maxThate = 2*pi;


  AnimationController _animationController;
  AnimationController _fadeAnimationController;


  void initState() {
    setState(() {
      for(int i=0;i<20;i++){
        BobbleBean bean = new BobbleBean();
        print(i);
        bean.color =getRandomWhiteColor(_random);
        //设置位置 先来个默认的
        bean.position = Offset(-1,-1);
        bean.spend = _random.nextDouble() * _maxSpend;
        bean.radius = _random.nextDouble() * _maxRadius;
        bean.theta = _random.nextDouble() * _maxThate;
        _list.add(bean);
      }
      //创建动画一秒
      _animationController = new AnimationController(vsync: this,duration: Duration(microseconds: 1000));
      //执行刷新监听
      _animationController.addListener(() {
        setState(() {
        });
      });
      //重复执行

      // _animationController.repeat();
      _fadeAnimationController = new AnimationController(vsync: this,duration: Duration(microseconds: 1800));
      //执行刷新监听
      _fadeAnimationController.forward();
      _fadeAnimationController.addStatusListener((status){
        if(status==AnimationStatus.completed){
          _animationController.repeat();
        }

      });
    });
  }
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body:  Container(
        child: Stack(
          children: [
            //渐变背景
            buildBackground(),
            //气泡
            buildBobbleWidget(context),
            //高斯模糊
            buildBlurWidget(),
            buildBottomWidget(context),


          ],
        ),
      )
    );
  }

  buildBobbleWidget(BuildContext context){
    return CustomPaint(
      size:MediaQuery.of(context).size,
      painter: CustomMyPainter(list:_list,random:_random),
    );
  }
  buildBlurWidget(){
    return BackdropFilter(
      filter: ImageFilter.blur(sigmaX: .3,sigmaY: .3),
      child: Container(
        color: Colors.white.withOpacity(0.1),
     ),
    );
  }
  buildBottomWidget(BuildContext context){
    return Positioned(
      bottom: 80,left: 40,right: 40,
      child:FadeTransition(
        opacity: _fadeAnimationController,

        child:  Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextFileWidget(
                obscuretext:false,
                labelText:'账号',
                prefixIconData:Icons.phone_android
            ),
            SizedBox(
              height: 14,
            ),
            TextFileWidget(
                obscuretext:true,
                labelText:'密码',
                suffixIconData: Icons.visibility,
                prefixIconData:Icons.lock_outline
            ),
            // Container(
            //   height: 38,
            //   width: double.infinity,
            //   child: ElevatedButton(
            //
            //   ),
            // )
          ],

        ),
      )
    );
  }
}


class TextFileWidget extends StatelessWidget {
  Function(String value) onChanged;
  bool obscuretext;
  String labelText;
  IconData prefixIconData;
  IconData suffixIconData;
  TextFileWidget({this.onChanged,this.labelText,this.obscuretext,this.suffixIconData,this.prefixIconData});
  @override
  Widget build(BuildContext context) {
    return TextField(
      onChanged: onChanged,
      obscureText: obscuretext,
      style: TextStyle(
        color: Colors.blue,
        fontSize: 14,
      ),
      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;
  CustomMyPainter({this.list, this.random});

  //先来个画笔
  Paint _paint = new Paint()..isAntiAlias = true;
  @override
  void paint(Canvas canvas,Size size){
    //绘制前重新计算每个点的位置
    list.forEach((element) {
      Offset newCenterOffset = calcuLateXy(element.spend,element.theta);
      double dx = newCenterOffset.dx+element.position.dx;
      double dy = newCenterOffset.dy+element.position.dy;
      if(dx <0||dx>size.width){
        dx = random.nextDouble()*size.width;
      }
      if(dy <0||dx>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 CustomMyPainter oldDelegate){
    //返回false 不刷新
    return true;
  }


  Offset calcuLateXy(double spend ,double theta){
    return Offset(spend*cos(theta),spend*sin(theta));
  }
}

class buildBackground extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    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)
          ]
        )
      ),
    );
  }
}


class BobbleBean{
  //位置
  Offset position;
  //颜色

  Color color;
  //速度
  double spend;
  double theta;
  double radius;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一些指导和提示。在使用Flutter编写精美的登录页面时,需要考虑以下几个方面: 1. UI设计:为了让登录页面更加吸引人,需要考虑颜色、布局和大小等方面,以使其看起来更加整洁、美观和易于使用。 2. 动画效果:使用一些动画效果可以使您的登录页面更具生动性和活力,例如,当用户输入用户名和密码时,可以使用字符动画来显示。 3. 交互设计:用户对登录页面的交互应该是简单而直接的,包括输入框、按钮和提示信息等,需要考虑用户体验和易用性。 在开始设计和编写登录页面之前,您需要首先了解Flutter的基本概念和代码结构。以下是一个简单的示例代码,展示了如何使用Flutter创建一个基本的登录页面: ```dart import 'package:flutter/material.dart'; class LoginPage extends StatefulWidget { @override _LoginPageState createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { TextEditingController _usernameController = TextEditingController(); TextEditingController _passwordController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Login Page"), ), body: Padding( padding: EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextField( controller: _usernameController, decoration: InputDecoration(hintText: "Username"), ), SizedBox(height: 20), TextField( controller: _passwordController, obscureText: true, decoration: InputDecoration(hintText: "Password"), ), SizedBox(height: 20), RaisedButton( onPressed: () { // handle login }, child: Text("Login"), ) ], ), ), ); } } ``` 上面的示例中,我们创建了一个名为LoginPage的StatefulWidget,并在其状态类中定义了两个TextEditingController,用于处理用户名和密码的输入。在build方法中,我们使用Scaffold组件来创建页面的布局,包括AppBar、TextField和RaisedButton组件来处理登录。您可以根据需要更改这些组件的属性或添加其他组件来提高登录界面的美观度和交互性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值