之前我们根据逛丢的数据,介绍了flutter app的开发教程。现在我们得开始完善一下了。
一般的app在新打开的时候都会有欢迎页(各叫法不同,此处为个人习惯),所以本章我们就要为逛丢app增加一个欢迎页。
flutter加载资源文件有点不同,需要在pubspec.yaml中注册一下:
首先我们先在项目根目录下新建 images文件夹,将我们的图片放进去
然后打开 pubspec.yaml 文件,在 flutter 下面注册图片,在 assets下面列举图片的路径:
此处需要注意的是 assets以及各文件路径的缩进。
新建 welcome.dart 页面,加载图片,此处我们用的是 Image.asset(".......") 来加载刚才我们注册的图片。
class Welcome extends StatefulWidget{ @override State<StatefulWidget> createState() { // TODO: implement createState return WelcomeWidget(); } } class WelcomeWidget extends State<Welcome>{ @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( body: Image.asset("images/welcome.png"), ); } }
修改 main.dart 代码:
class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( home: Welcome(), ); } }
重新运行代码,效果如下:
欢迎页初步的效果就出来了。但是细心的我们发现,右侧图片没有到边,这是因为我们没有设置图片所在区域的宽高,而且下部因为虚拟键的存在导致我们界面显示区域大小不固定,所以还需要设置图片的缩放。
修改后的代码:
class WelcomeWidget extends State<Welcome>{ @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( body: Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: Image.asset("images/welcome.png",fit: BoxFit.fill,), ), ); } }
其中 MediaQuery.of(context).size 用来获取屏幕的相关参数,Image.asset("......",fit: BoxFit.fill,)的fit属性用来设置图片的缩放,fill表示填充满控件不保证比例缩放。
效果图片:
虚拟键存在与不存在的两种情况,都显示的没有问题了。
通常情况下我们需要在欢迎页等待几秒钟用来展示欢迎页的内容或者广告,所以都会在此处增加一个倒计时,计时结束时我们跳转离开。所以我们也需要在此处增加一个计时器。
首先因为计时结束时需要跳转到主页,所以先把main.dart 中的 main页面移走。新建一个main_index.dart 将main的内容剪贴进去,剪贴之后别忘了导入需要的依赖路径。
开始在 welcome 中添加计时器,此处我们把计时的控件选定在右下角(或者其他位置,原理都一样),所以我们用 stack 来实现:
class WelcomeWidget extends State<Welcome>{ int second_index=5; @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( body: Stack( children: <Widget>[ Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: Image.asset("images/welcome.png",fit: BoxFit.fill,), ), Container( alignment: Alignment.bottomRight,//设置内容的显示对其方式为 靠右下角显示 child: Container( width: 50, height: 50, decoration: BoxDecoration( color: Colors.green,//设置背景色,为了看清控件位置设置,在界面完成之后屏蔽此代码 borderRadius: BorderRadius.all(Radius.circular(25)),//设置圆角 ), alignment: Alignment.center,//居中显示 margin: EdgeInsets.all(25),//设置外边距 child: Text("$second_index"), ), ), ], ), ); } }
其中 second_index 用了作为倒计时的计数变量,界面效果:
我们需要的是计时,所以开始加入计时器,此处需要使用 Timer 需要导入 dart/async 依赖:
class WelcomeWidget extends State<Welcome>{ int second_index=5; Timer timer; @override void initState() { // TODO: implement initState super.initState(); timer=Timer.periodic(Duration(seconds: 1), (sd){ setState(() { second_index-=1; }); }); } @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( body: Stack( children: <Widget>[ Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: Image.asset("images/welcome.png",fit: BoxFit.fill,), ), Container( alignment: Alignment.bottomRight,//设置内容的显示对其方式为 靠右下角显示 child: Container( width: 50, height: 50, decoration: BoxDecoration( color: Colors.green,//设置背景色,为了看清控件位置设置,在界面完成之后屏蔽此代码 borderRadius: BorderRadius.all(Radius.circular(25)),//设置圆角 ), alignment: Alignment.center,//居中显示 margin: EdgeInsets.all(25),//设置外边距 child: Text("$second_index"), ), ), ], ), ); } }
效果示例:
我们使用 Timer.periodic(Duration(seconds: 1), (sd){ }) 来实现计时,表示每秒钟执行一次。现在我们实现了倒计时的功能,但是每秒一次的计数看起来不是很直观,按照大多数的app的效果应该是一个圆形的进度条来显示,在中间显示跳过。
我们先实现进度条变化,首先设置倒计时的时间,以及timer的计时间隔,然后我们此处设置的倒计时为5秒中,我们设置 Duration(milliseconds: 50) 即每50毫秒计时一次,5秒钟一共计时 100 次,这样每次执行的时候开改变一次进度,倒计时的显示可以每 20次改变一次,这样就实现了倒计时以及进度条的变化。
创建一个 changeIndex 的变量,用来计数。
修改代码:
class WelcomeWidget extends State<Welcome>{ int second_index=5; int changeIndex=1; Timer timer; @override void initState() { // TODO: implement initState super.initState(); timer=Timer.periodic(Duration(milliseconds: 50), (sd){ setState(() { changeIndex+=1; if(changeIndex%20==0){ second_index-=1; } }); }); } @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( body: Stack( children: <Widget>[ Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: Image.asset("images/welcome.png",fit: BoxFit.fill,), ), Container( alignment: Alignment.bottomRight,//设置内容的显示对其方式为 靠右下角显示 child: Container( width: 60, height: 60, decoration: BoxDecoration( // color: Colors.green,//设置背景色,为了看清控件位置设置,在界面完成之后屏蔽此代码 borderRadius: BorderRadius.all(Radius.circular(30)),//设置圆角 ), alignment: Alignment.center,//居中显示 margin: EdgeInsets.all(25),//设置外边距 child: Stack( children: <Widget>[ Container( alignment: Alignment.center, child: CircularProgressIndicator( value: changeIndex/100, strokeWidth: 1, ), ), Container( alignment: Alignment.center, child: Text("$second_index"), ), ], ), ), ), ], ), ); } }
效果示例:
倒计时的效果已经实现了,现在可以添加跳转的代码了,当 changeIndex>=100 时,我们就可以判定 计时结束,需要跳转了,此时我们不仅需要跳转还需要结束次欢迎页,不能通过返回键,返回此页,此处我们使用 Navigator.pushAndRemoveUntil 来跳转。
timer=Timer.periodic(Duration(milliseconds: 50), (sd){ setState(() { changeIndex+=1; if(changeIndex%20==0){ second_index-=1; } }); if(changeIndex>=100){ _openMain(); } });
void _openMain(){ timer?.cancel(); timer=null; Navigator.pushAndRemoveUntil(context, MaterialPageRoute( builder: (context)=>Main() ), (Route route)=>route==null); }
代码修改后的示例:
计时跳转已经完成了,但是并不是所有的人都想安安静静的看完倒计时,所有我们把进度圈圈中间的倒计时数字改成跳过两个字,并添加点击跳转事件,让点击时直接跳转到主页面。
欢迎页面这样看过于空旷,我们再添加一些免责声明。
修改后的代码为:
class WelcomeWidget extends State<Welcome>{ // int second_index=5; int changeIndex=0; Timer timer; @override void initState() { // TODO: implement initState super.initState(); timer=Timer.periodic(Duration(milliseconds: 50), (sd){ setState(() { changeIndex+=1; // if(changeIndex%20==0){ // second_index-=1; // } }); if(changeIndex>=100){ _openMain(); } }); } @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( body: Stack( children: <Widget>[ Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: Image.asset("images/welcome.png",fit: BoxFit.fill,), ), Container( alignment: Alignment.bottomRight,//设置内容的显示对其方式为 靠右下角显示 child: Container( width: 60, height: 60, decoration: BoxDecoration( // color: Colors.green,//设置背景色,为了看清控件位置设置,在界面完成之后屏蔽此代码 borderRadius: BorderRadius.all(Radius.circular(30)),//设置圆角 ), alignment: Alignment.center,//居中显示 margin: EdgeInsets.all(25),//设置外边距 child: GestureDetector( child: Stack( children: <Widget>[ Container( alignment: Alignment.center, child: CircularProgressIndicator( value: changeIndex/100, strokeWidth: 1, ), ), Container( alignment: Alignment.center, child: Text("跳过"), ), ], ), onTap: (){ _openMain(); }, ), ), ), Container( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( height: 50, child: Text("flutter学习项目"), ), Container( height: 50, child: Text("仿照逛丢,数据来源逛丢"), ), Container( height: 50, child: Text("仅供flutter学习使用不得作为任何商业用途"), ), Container( height: 50, child: Text("逛丢官网 https://guangdiu.com"), ), ], ), ), ], ), ); } void _openMain(){ timer?.cancel(); timer=null; Navigator.pushAndRemoveUntil(context, MaterialPageRoute( builder: (context)=>Main() ), (Route route)=>route==null); } }
void _openMain(){ timer?.cancel(); timer=null; Navigator.pushAndRemoveUntil(context, MaterialPageRoute( builder: (context)=>Main() ), (Route route)=>route==null); } }
效果示例:
到此欢迎页完成。