在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天、每周,都会留下一些脚印,就是这些创作的内容,有一种执着,就是不知为什么,如果你迷茫,不妨来瞅瞅码农的轨迹。

  • 优美的音乐节奏带你浏览这个效果的编码过程
  • 坚持每一天,是每个有理想青年的追求
  • 追寻年轻人的脚步,也许你的答案就在这里

本文章实现的效果如下图所示:

1 前言

Flutter中任何一个单页面都可以作为一个应用程序的启动页,只要写上入口函数即可

void main() => runApp(MaterialApp(home: BobbleLoginPage(),),);

首先定义气泡 每个气泡都有位置、颜色、运动速率、角度等:

///气泡属性配置
class BobbleBean {//位置Offset postion;//颜色Color color;//运动的速度double speed;//角度double theta;//半径double radius;
}

气泡是使用生成的随机透明度白色背景,代码如下:

///获取随机透明的白色
Color getRandonWhightColor(Random random) {//0~255 0为完全透明 255 为不透明//这里生成的透明度取值范围为 10~200int a = random.nextInt(190)+10;return Color.fromARGB(a, 255, 255, 255);
}

2 页面的初始化

气泡需要运动,所以这里使用到了动画控制器,同时创建气泡数据,每个气泡的位置、速率、角度都是随机不一样的

class BobbleLoginPage extends StatefulWidget {@override_BobbleLoginPageState createState() => _BobbleLoginPageState();
}class _BobbleLoginPageState extends State<BobbleLoginPage>with TickerProviderStateMixin {//创建的气泡保存集合List<BobbleBean> _list = [];//随机数据Random _random = new Random(DateTime.now().microsecondsSinceEpoch);//气泡的最大半径double maxRadius = 100;//气泡动画的最大速度double maxSpeed = 0.7;//气泡计算使用的最大弧度(360度)double maxTheta = 2.0 * pi;//动画控制器AnimationController _animationController;//流控制器StreamController<double> _streamController = new StreamController();AnimationController _fadeAnimationController;@overridevoid initState() {super.initState();for (var i = 0; i < 20; i++) {BobbleBean particle = new BobbleBean();//获取随机透明度的白色颜色particle.color = getRandonWhightColor(_random);//指定一个位置 每次绘制时还会修改particle.postion = Offset(-1, -1);//气泡运动速度particle.speed = _random.nextDouble() * maxSpeed;//随机角度particle.theta = _random.nextDouble() * maxTheta;//随机半径particle.radius = _random.nextDouble() * maxRadius;//集合保存_list.add(particle);}//动画控制器_animationController = new AnimationController(vsync: this, duration: Duration(milliseconds: 1000));//刷新监听_animationController.addListener(() {//流更新_streamController.add(0.0);});_fadeAnimationController = new AnimationController(vsync: this, duration: Duration(milliseconds: 500));_fadeAnimationController.addStatusListener((status) {if (status == AnimationStatus.completed) {//重复执行动画_animationController.repeat();}});//重复执行动画_fadeAnimationController.forward();}...
}

我们养成一个习惯,动画控制器有创建就有销毁:

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

然后页面的主体是由层叠布局构建

  @overrideWidget build(BuildContext context) {return Scaffold(backgroundColor: Colors.white,///填充布局body: Stack(children: [//第一部分 第一层 渐变背景buildBackground(),//第二部分 第二层 气泡buildBubble(context),//第三部分 高斯模糊buildBlureWidget(),//第四部分 顶部的文字buildTopText(),//第五部分 输入框与按钮FadeTransition(opacity: _fadeAnimationController, child: buildColumn(context)),],),);}
  //第一部分 第一层 渐变背景Container buildBackground() {return Container(decoration: BoxDecoration(//线性渐变gradient: LinearGradient(//渐变角度begin: Alignment.topLeft,end: Alignment.bottomRight,//渐变颜色组colors: [Colors.lightBlue.withOpacity(0.3),Colors.lightBlueAccent.withOpacity(0.3),Colors.blue.withOpacity(0.3),],),),);}
  //第二部分 第二层 气泡Widget buildBubble(BuildContext context) {//使用Stream流实现局部更新return StreamBuilder<double>(stream: _streamController.stream,builder: (BuildContext context, AsyncSnapshot<double> snapshot) {//自定义画板return CustomPaint(//自定义画布painter: CustomMyPainter(list: _list,random: _random,),child: Container(height: MediaQuery.of(context).size.height,),);},);}

绘制气泡的精华就是这个画布了,因为动画控制器一直在重复执行,一直在重复刷新画布,所以画成会反复执行paint绘制方法,每次都重新计算坐标偏移量就形成了动画效果,代码如下

class CustomMyPainter extends CustomPainter {//创建画笔Paint _paint = Paint();//保存气泡的集合List<BobbleBean> list;//随机数变量Random random;CustomMyPainter({this.list, this.random});@overridevoid paint(Canvas canvas, Size size) {//每次绘制都重新计算位置list.forEach((element) {//计算偏移var velocity = calculateXY(element.speed, element.theta);//新的坐标 微偏移var dx = element.postion.dx + velocity.dx;var dy = element.postion.dy + velocity.dy;//x轴边界计算if (element.postion.dx < 0 || element.postion.dx > size.width) {dx = random.nextDouble() * size.width;}//y轴边界计算if (element.postion.dy < 0 || element.postion.dy > size.height) {dy = random.nextDouble() * size.height;}//新的位置element.postion = Offset(dx, dy);print("dx $dx dy $dy  ${element.postion}");});//循环绘制所有的气泡list.forEach((element) {//画笔颜色_paint.color = element.color;//绘制圆canvas.drawCircle(element.postion, element.radius, _paint);});}@overridebool shouldRepaint(covariant CustomPainter oldDelegate) {return true;}
}

x 与 y 的偏移量计算方法如下:

///计算坐标
Offset calculateXY(double speed, double theta) {return Offset(speed * cos(theta), speed * sin(theta));
}

3 高斯模糊

再使用高斯模糊效果 覆盖到绘制的气泡上,造成一种晕晕的效果

  //第三部分 高斯模糊buildBlureWidget() {return BackdropFilter(filter: ImageFilter.blur(sigmaX: 0.3, sigmaY: 0.3),child: Container(color: Colors.white.withOpacity(0.1),),);}

4 Hello World

使用 Positioned 实现的 Stack 中的顶部对齐效果

//第四部分 顶部的文字Positioned buildTopText() {//顶部对齐return Positioned(top: 120,left: 0,right: 0,child: Text('Holl World',textAlign: TextAlign.center,style: TextStyle(color: Colors.blue,fontSize: 40.0,fontWeight: FontWeight.w900,),),);}

5 输入框与按钮

最后就是表层的输入框与按钮

  //第五部分 输入框与按钮Widget buildColumn(BuildContext context) {return Container(padding: EdgeInsets.all(44),child: Column(//子Widget 底部对齐mainAxisAlignment: MainAxisAlignment.end,children: <Widget>[TextFieldWidget(hintText: '邮箱',obscureText: false,prefixIconData: Icons.mail_outline,onChanged: (value) {},),SizedBox(height: 10.0,),Column(crossAxisAlignment: CrossAxisAlignment.end,children: <Widget>[TextFieldWidget(hintText: '密码',obscureText: true,prefixIconData: Icons.lock_outline,suffixIconData: Icons.visibility,),SizedBox(height: 10.0,),Text('忘记密码?',style: TextStyle(color: Theme.of(context).accentColor,),),],),SizedBox(height: 20.0,),ButtonWidget(buttonLabel: '登录',onTap: () {},hasBorder: false,),SizedBox(height: 10.0,),ButtonWidget(buttonLabel: '跳过',onTap: () {},hasBorder: true,),],),);}
6 自定义输入框

///自定义文本输入框
class TextFieldWidget extends StatelessWidget {//占位提示文本final String hintText;//输入框前置图标final IconData prefixIconData;//输入框后置图标final IconData suffixIconData;//是否隐藏文本final bool obscureText;//输入实时回调final Function onChanged;TextFieldWidget({Key key,this.hintText,this.prefixIconData,this.suffixIconData,this.obscureText,this.onChanged,}) : super(key: key);@overrideWidget build(BuildContext context) {//构建输入框return TextField(//实时输入回调onChanged: onChanged,//是否隐藏文本obscureText: obscureText,//隐藏文本小圆点的颜色cursorColor: Theme.of(context).accentColor,//文本样式style: TextStyle(color: Theme.of(context).accentColor,fontSize: 14.0,),//输入框的边框decoration: InputDecoration(//提示文本labelText: hintText,//提示文本的样式labelStyle: TextStyle(color: Theme.of(context).accentColor),//可编辑时的提示文本的颜色focusColor: Theme.of(context).accentColor,//填充filled: true,//可编辑时 无边框样式enabledBorder: UnderlineInputBorder(borderSide: BorderSide.none,),//获取输入焦点时的边框样式focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10),borderSide: BorderSide(color: Theme.of(context).accentColor),),//文本前置的图标prefixIcon: Icon(prefixIconData,size: 18,color: Theme.of(context).accentColor,),//文本后置的图标suffixIcon: GestureDetector(onTap: () {},child: Icon(suffixIconData,size: 18,color: Theme.of(context).accentColor,),),),);}
}

7 自定义按钮

///自定义按钮
class ButtonWidget extends StatelessWidget {//按钮上的文字final String buttonLabel;//是否填充背景final bool hasBorder;//点击事件回调final GestureTapCallback onTap;ButtonWidget({Key key,@required this.buttonLabel,this.hasBorder = false,this.onTap,}) : super(key: key);@overrideWidget build(BuildContext context) {return Material(child: Ink(//边框decoration: BoxDecoration(//定义填充颜色color: hasBorder ? Colors.white : Theme.of(context).accentColor,//点击事件高亮的边框圆角borderRadius: BorderRadius.circular(10),//边框设置border: hasBorder? Border.all(color: Theme.of(context).accentColor,width: 1.0,): Border.fromBorderSide(BorderSide.none),),//事件监听回调child: buildInkWell(context),),);}InkWell buildInkWell(BuildContext context) {return InkWell(//事件回调onTap: onTap,//点击的水波纹与高亮颜色 与Ink设置的背景圆角一致borderRadius: BorderRadius.circular(10),//按钮样式child: Container(height: 60.0,child: Center(child: Text(//文本内容buttonLabel,//文本样式style: TextStyle(//文本颜色color: hasBorder ? Theme.of(context).accentColor : Colors.white,//加粗fontWeight: FontWeight.w600,//文字大小fontSize: 16.0,),),),),);}
}

【x1】微信公众号的每日提醒 随时随记 每日积累 随心而过 文章底部扫码关注

【x2】各种系列的视频教程 免费开源 关注 你不会迷路

【x3】系列文章 百万 Demo 随时 复制粘贴 使用

【x4】简短的视频不一样的体验

【x5】必须有源码

周末也需要学习 Flutter 一个气泡动画背景的登录页面-倾心之作 如果你迷茫 不妨来瞅瞅 年轻人每日都会分享


不局限于思维,不局限语言限制,才是编程的最高境界。

以小编的性格,肯定是要录制一套视频的,随后会上传

有兴趣 你可以关注一下 西瓜视频 — 早起的年轻人

Flutter 气泡背景效果 仿苹果桌面运动的气泡相关推荐

  1. iPhone 计算机 桌面,仿苹果电脑桌面软件 仿苹果桌面软件

    有什么好用的windows10仿苹果的桌面导航软件 windows还是无法象真正的苹果系统那样漂亮的,如果你装了mac的虚拟机,就知道苹果系统是如何的好了. 怎么说呢,你装了后发现,那才叫享受,接着, ...

  2. html仿苹果桌面导航js css,CSS简单实现弹出框、输入框等的背景幕布,模仿苹果官网导航块的半透明效果。...

    需求提要 我们如果想写一个效果类似弹出框的组件,首先简单分析一下弹出框的几个特性:弹出框肯定位于当前页面的最顶端,并且在弹出框关闭之前,其他控件都无法点击.focus等. 为了更好突出弹出框的效果,除 ...

  3. wpf仿苹果桌面图标动画效果

    开局一张图后面全靠编. 源码下载地址:https://download.csdn.net/download/musx01230/10912990

  4. android仿iphone苹果桌面源码拖拉排

    android仿iphone苹果桌面源码拖拉排序哦 仿苹果桌面 仿iphone ios桌面 launcher 本人见市场上很少仿排序拖拉这样的算法.所以改android源码.供大家学习使用哦. 这是a ...

  5. android 仿苹果 小组件,仿ios14桌面小部件

    仿ios14桌面小部件,这是一款面向广大安卓手机用户推出的高仿iOS14桌面插件软件,大家可以使用这款软件快速完成自己想要的桌面显示,多种插件一键点击轻松完成设置过程,让大家体验到同款iOS14桌面强 ...

  6. android 仿苹果 小组件,安卓仿ios14桌面小部件

    安卓仿ios14桌面小部件,是一个可以让安卓手机的界面看起来像苹果界面的软件,功能非常强大,它可以提供多种不同主题的壁纸桌面,随心选择,设置后的效果还是非常不错的,很有高级感,操作简单,上手也很快. ...

  7. win 2016 ssh_win仿苹果模仿MAC桌面,完美高仿主题推荐

    所需工具:Mac主题(后台回复1127获取) 适用系统:Win 哈喽大家好,欢迎来到蜜蜂科技f.Mac电脑一向都是追求工匠精神,独有的设计,深受用户喜欢.可是动不动就需要将近上w的售价,让很多消费者望 ...

  8. iphonex重量_精仿苹果iPhone X手机配置介绍

    精仿苹果iPhone X手机配置介绍 [上市时间] 2017年10月最新版 [屏幕色彩] 1600万 [分 辨 率] 1920X1080 [屏幕尺寸] 5.8英寸IPS全视角电容式触摸屏 [处 理 器 ...

  9. android os仿ios,安卓仿ios12桌面全套仿安卓完美版

    详情 安卓仿ios12桌面全套仿安卓完美版,苹果系统的仿安卓系统工具,使用便捷,安卓手机也能体验苹果系统,从桌面.主题.UI.系统内的各种细节轻松体验效果,画面流畅轻松使用启动器操作使用,喜欢的朋友快 ...

最新文章

  1. 收藏 | 75道常见AI面试题助你清扫知识盲点(附解析)
  2. golang枚举类型 - iota用法拾遗
  3. xml内容过多装不下,怎么实现下滑功能(最简单的下滑功能实现)
  4. 高职院校计算机基础课程要求,浅谈高职院校计算机的应用基础课程的改革.doc...
  5. 【OS学习笔记】五 VirtualBox的下载、安装和配置
  6. 备份mysql_mysql备份及pymysql
  7. Flask 框架中 上下文基础理念,包括cookie,session存储方法,requset属性,current_app模块和g模块...
  8. 数据-第18课-栈与递归
  9. Modelsim的下载及安装
  10. 注释 护眼色 绿色 RGB
  11. matlab中zeros和ones函数使用方法
  12. android 遥控器 地址码,RK3128平台android系统修改添加遥控器键值码值
  13. Justinmind 如何让自己的项目可以发布到网上,实现各个端打开网页就能看
  14. 为什么一个概念会非常难懂呢?人是如何理解的呢?
  15. 数据库常用命令及关键字
  16. Visual Studio 好用的插件
  17. Mysql迁移到GaussDb_GaussDB T 使用DUMP/LOAD导出导入迁移备份数据
  18. HTML+CSS+JS实现轮播效果
  19. (JavaScript)贪婪模式和非贪婪模式(懒惰模式)
  20. Redis实现点赞与关注

热门文章

  1. 如何从900万张图片中对600类照片进行分类,附代码
  2. 田忌赛马贪心算法_acm田忌赛马问题在线等急求!!
  3. 【机器学习】机器学习从零到掌握之二 -- 教你实现K近邻算法
  4. 一个实例带你搞懂Apriori关联分析算法
  5. 机器学习之特征工程-特征选择
  6. 更新版 | GPU CUDA 进阶课程
  7. java checkproperties(this)_【转载】java读取.properties配置文件的几种方法
  8. 字符串固定长度 易语言_易语言宽字符数据类型怎么设置
  9. win10你的组织已关闭自动更新问题怎么解决?
  10. 15个Linux Yum命令实例--安装/卸载/更新