黑屏时间这么长是因为我在输入密码的时候Flutter自动给我黑的,输完之后就好了
还是老套路,先不急着写代码,先来分析一波:
好了,分析完成了,接下来根据分析的来进行分阶段完成即可
先使用Stack()组件让他们叠加起来
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
//设置为屏幕为屏幕的宽 也可以使用MediaQuery.of(context).size.width
width: double.infinity,
//设置为屏幕为屏幕的高 也可以使用MediaQuery.of(context).size.height
height: double.infinity,
child: Stack(
children: [
//渐变背景
buildGradientBackground(),
//随机气泡
buildRandomBubble(context),
//高斯模糊
buildGaussianBlur(),
//Hellow Word
buildTopText(),
//底部输入框
buildButtonTextField(),
],
),
));
}
//渐变背景
buildGradientBackground() {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight, //LinearGradient属性
end: Alignment.bottomLeft, //属性
colors: [
Colors.blue.withOpacity(0.3),
Colors.blueAccent.withOpacity(0.3),
Colors.lightBlueAccent.withOpacity(0.3),
Colors.lightBlue.withOpacity(0.3),
]),
),
);
}
这段代码很简单,来看看效果吧:
效果图(1.2)
要想画出随机的透明的圆,咋们第一步得先知道怎么画一个随机的透明圆,然后再让他随机起来就好了
///随机气泡
buildRandomBubble(BuildContext context) {
return CustomPaint(
painter: MyCustomPaint(),
//设置屏幕大小 MediaQuery.of(context).size是屏幕的宽和高
size: MediaQuery.of(context).size,
);
}
class MyCustomPaint extends CustomPainter {
//设置锯齿
Paint _paint = new Paint()
..isAntiAlias = true
..color = Colors.white.withOpacity(0.5)
..strokeWidth = 50;
List<BubbleBean> _list;
Random _random;
MyCustomPaint(this._list, this._random);
@override
void paint(Canvas canvas, Size size) {
canvas.drawCircle(Offset(100, 100), 50, _paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
先开看看效果,在一步步解释:
效果图(1.3)
:
可以从效果图并结合代码看出,这是一个100*100半透明的白色圆
在来分析一下代码吧:
在Flutter中,自定义Widget是使用的CustomPaint()组件,最重要的有2个参数
class MyCustomPaint extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
继承自CustomPainter需要重写2个方法:
paint()画布举例:
绘制线:
//定义画笔
Paint _paint = new Paint()
//设置锯齿
..isAntiAlias = true
//透明颜色
..color = Colors.white.withOpacity(0.5)
//粗细
..strokeWidth = 50;
//绘制线
canvas.drawLine(Offset(100, 100), Offset(350, 350), _paint);
走到这里,咋们就可以完成自己画一个自定义半透明的白色圆了
因为咋们在效果图中并不是一个圆,而是20个随机的圆,所以需要定义一个类,吧这些属性都单独放到类里面执行;
定义圆的属性:
class BubbleBean {
//位置
Offset postion;
//颜色
Color color;
//运动的速度
double speed;
//运动的角度
double theta;
//半径
double radio;
}
初始化圆的属性,并使用随机数:
//随机数
Random _random = new Random();
//最大运行速度
double _maxSpeed = 1.0;
//最大的半径
double _maxRadius = 100;
//设置角度
double _maxTheta = 2 * pi;
@override
void initState() {
// TODO: implement initState
super.initState();
/**
* 循环得到30个气泡
*/
for (int i = 0; i < 30; i++) {
BubbleBean bubbleBean = new BubbleBean();
//获取随机透明度
bubbleBean.color = getRandomColor(_random);
//初始化设置位置
bubbleBean.postion = Offset(-1, -1);
/**
* _random.nextDouble() 获取的是0-1之间不为负数的小数
*/
//设置随机运动
bubbleBean.speed = _random.nextDouble() * _maxSpeed;
//设置随机半径
bubbleBean.radio = _random.nextDouble() * _maxRadius;
//随机的角度
bubbleBean.theta = _random.nextDouble() * _maxTheta;
_list.add(bubbleBean);
}
}
Color getRandomColor(Random random) {
//透明度为0-255
int a = random.nextInt(200);
//这里设置的是白色透明度为0-200的圆
return Color.fromARGB(a, 255, 255, 255);
}
补充:
使用随机数Random()需要导入import ‘dart:math’;包
_random.nextDouble() 获取的是0-1之间不为负数的小数
_random.nextInt(200); 获取的是0-200之间的数
这样的话,30个随机大小,随机透明度的圆就创建好了~~
在来重新写MyCustomPaint()这个类
///随机气泡
buildRandomBubble(BuildContext context) {
return CustomPaint(
painter: MyCustomPaint(_list, _random),
//设置屏幕大小 MediaQuery.of(context).size是屏幕的宽和高
size: MediaQuery.of(context).size,
);
}
class MyCustomPaint extends CustomPainter {
Paint _paint = new Paint()
//设置锯齿
..isAntiAlias = true;
List<BubbleBean> _list;
Random _random;
MyCustomPaint(this._list, this._random);
@override
void paint(Canvas canvas, Size size) {
_list.forEach((element) {
//重新根据速度与运动角度计算Offset
Offset newOffset = coordinates(element.speed, element.theta);
print("szjpaintdx${newOffset.dx}paintdy${newOffset.dy}");
double dx = newOffset.dx + element.postion.dx;
double dy = newOffset.dy + element.postion.dy;
/**
* 通过if判断 保证dx坐标在屏幕里面
*/
if (dx < 0 || dx > size.width) {
//_random.nextDouble() 生成的是0-1之间不为负数的小数 size.width是屏幕的宽
dx = _random.nextDouble() * size.width;
}
/*通过if判断 保证dy坐标在屏幕里面*/
if (dy < 0 || dy > size.height) {
//_random.nextDouble() 生成的是0-1之间不为负数的小数 size.height是屏幕的高
dy = _random.nextDouble() * size.height;
}
element.postion = Offset(dx, dy);
});
//绘制
_list.forEach((element) {
_paint.color = element.color;
canvas.drawCircle(element.postion, element.radio, _paint);
});
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
//刷新
return true;
}
//重新计算Offset
Offset coordinates(double speed, double theta) {
return Offset(speed * sin(theta), speed * cos(theta));
}
}
这里主要分析的就是paint()方法里面的代码:
//重新计算Offset
Offset coordinates(double speed, double theta) {
return Offset(speed * sin(theta), speed * cos(theta));
}
这个方法是通过速度和角度计算出圆是朝什么方向移动的,
先来看看Log:
可以看到得到的dx和dy,是一些-1到1之间的数
再往下看代码:
//重新根据速度与运动角度计算Offset
Offset newOffset = coordinates(element.speed, element.theta);
print("szjpaintdx${newOffset.dx}paintdy${newOffset.dy}");
double dx = newOffset.dx + element.postion.dx;
double dy = newOffset.dy + element.postion.dy;
这段代码就是为了获取到一个最新的值,拿第一个气泡来举例,比如说,第一个气泡计算出新的dx == 0.1 dy == -0.3 那么计算第二次的时候,就会以dx == 0.1 dy == -0.3这个基础来计算,这段代码的意思就是如果第一个气泡是向上移动的,他就会一直向上移动,
在来看看最后的这个if判断:
/**
* 通过if判断 保证dx坐标在屏幕里面
*/
if (dx < 0 || dx > size.width) {
//_random.nextDouble() 生成的是0-1之间不为负数的小数 size.width是屏幕的宽
dx = _random.nextDouble() * size.width;
}
/*通过if判断 保证dy坐标在屏幕里面*/
if (dy < 0 || dy > size.height) {
//_random.nextDouble() 生成的是0-1之间不为负数的小数 size.height是屏幕的高
dy = _random.nextDouble() * size.height;
}
element.postion = Offset(dx, dy);
这个判断比较好理解,刚刚提到了,如果第一个气泡是向上的就会让他一直向上,然后如果他已经出了屏幕,则把它取消掉重新创建一个新的气泡来变化
最后这部分代码最简单,绘制圆
//绘制
_list.forEach((element) {
canvas.drawCircle(element.postion, element.radio, _paint);
});
来看看效果吧:
效果图(1.6)
@override
void initState() {
super.initState();
//创建动画,执行为1秒
_animationController =
new AnimationController(vsync: this, duration: Duration(seconds: 1));
//动画监听器
_animationController.addListener(() {
setState(() {
});
});
//重复执行
_animationController.repeat();
在来看看添加动画之后的效果:
效果图(1.7)
:
是不是大家有迷惑的地方嘞~迷惑的地方就是代码能看懂,但是你设置了动画,并没有使用呀,是不是我忘记写了,哈哈哈哈哈当然不是啦,
注意看这里:
这里刷新的是这个地方:
刚刚也说了,shouldRepaint()这个方法返回的是时候刷新,咋们这里返回true就代表了需要刷新,刷新的话就会调用paint方法,则一直会计算,一直会刷新,气泡就都动起来啦~
走到这里,气泡就可以动起来了!
/**
* 高斯模糊
*/
buildGaussianBlur() {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 0.3, sigmaY: 0.3),
child: Container(
color: Colors.white.withOpacity(0.2),
),
);
}
//顶部Hellow Word文本
buildTopText() {
return Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.only(top: 100),
child: Text("Hellow Word",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 40,
color: Colors.lightBlue,
fontWeight: FontWeight.bold)),
);
}
//底部按钮
buildButtonTextField() {
return Positioned(
left: 20,
right: 20,
bottom: 20,
child: FadeTransition(
//设置渐变动画
opacity: _tweenAnimation,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
MyTextField("账号", Icon(Icons.format_list_numbered), false),
SizedBox(
height: 10,
),
MyTextField("密码", Icon(Icons.qr_code), true),
SizedBox(
height: 10,
),
Container(
height: 50,
width: double.infinity,
child: ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return GalleryUnit();
}));
},
child: Text("注册", textAlign: TextAlign.center),
),
),
SizedBox(
height: 10,
),
Container(
height: 50,
width: double.infinity,
child: ElevatedButton(
onPressed: () {
//跳转页面并吧当前状态取消掉
Navigator.pushAndRemoveUntil(context,
new MaterialPageRoute(builder: (BuildContext context) {
return MainPage();
}), (route) => route == null);
},
child: Text("登录", textAlign: TextAlign.center),
),
),
],
),
),
);
}
class MyTextField extends StatelessWidget {
String _lable;
Icon _icon;
bool isobscureText;
MyTextField(this._lable, this._icon, this.isobscureText);
@override
Widget build(BuildContext context) {
return Container(
child: TextField(
obscureText: isobscureText,
decoration: InputDecoration(
//透明填充
// filled: true,
icon: _icon,
labelText: _lable,
//选中
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
//未选中
/* enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
//不显示边框
borderSide: BorderSide(color: Colors.black38))*/
),
),
);
}
}
这里的布局非常简单,就不细说了
好了,到这里就结束了,来看看最终的效果吧:
效果图(1.9)
:
刚开始这个二维码是我的公众号哦,~
完整代码
猜你喜欢:
Flutter 轮子之下雪动画(2)
原创不易,您的点赞就是对我最大的支持,留下您的点赞吧~