接着上一篇,咱们作一个这样的APP:html
开始以前,我发现了一个好玩的东西,每次咱们在终端中输入命令:vue
flutter run
终端里会有这个东西:react
按照上图所示,咱们的进入这个网页看看是个啥:编程
好高大上的感受,具体是干吗的,我也不知道,有兴趣的同窗能够点进去把玩把玩,之后搞明白了再更吧。数组
先建立一个列表。微信
回到main.dart
中,把原来的代码所有清空,复制如下代码:app
import 'package:flutter/material.dart'; import 'package:english_words/english_words.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override //构建一个容器 Widget build(BuildContext context) { return new MaterialApp( title: 'Startup Name Generator', home: new RandomWords(),//定义子组件为有状态控件RandomWords类的实例 ); } } //定义有状态控件RandomWords类 class RandomWords extends StatefulWidget { @override createState() => new RandomWordsState();//建立有状态控件RandomWords的状态类实例:RandomWordsState } //定义状态类RandomWordsState class RandomWordsState extends State<RandomWords> { @override final _suggestions = <WordPair>[]; //用于保存随机字符串词组,注意这是一个数组变量 final _biggerFont = const TextStyle(fontSize: 18.0); //用于标识字符串的样式 //构建一个脚手架,里面塞入前面定义好的_buildSuggestions类 Widget build(BuildContext context) { return new Scaffold ( appBar: new AppBar( title: new Text('Startup Name Generator'), ), body: _buildSuggestions(), ); } //定义一个子控件,这个控件就是放置随机字符串词组的列表 Widget _buildSuggestions() { return new ListView.builder( //ListView(列表视图)是material.dart中的基础控件 padding: const EdgeInsets.all(16.0), //padding(内边距)是ListView的属性,配置其属性值 //经过ListView自带的函数itemBuilder,向ListView中塞入行,变量 i 是从0开始计数的行号 //此函数会自动循环并计数,咋结束的我也不知道,走着瞧咯 itemBuilder: (context, i) { if (i.isOdd) return new Divider();//奇数行塞入分割线对象 final index = i ~/ 2; //当前行号除以2取整,获得的值就是_suggestions数组项索引号 // 若是计算获得的数组项索引号超出了_suggestions数组的长度,那_suggestions就再生10个随机组合的字符串词组 if (index >= _suggestions.length) { _suggestions.addAll(generateWordPairs().take(10)); } return _buildRow(_suggestions[index]);//把这个数据项塞入ListView中 } ); } //定义的_suggestions数组项属性 Widget _buildRow(WordPair pair) { //ListTile和Text都是material.dart中的基础控件 return new ListTile( title: new Text( pair.asPascalCase, //使用驼峰样式 style: _biggerFont, ), ); } }
看到这里,是否是有点晕,各类声明、各类引用,还有回调,把上面的代码,用下面的图解析下结构,看看到底怎么个状况:less
能够发如今StatelessWidget和State类中都有一个Widget类型的函数build()
,感受有点像类的初始化方法construct()
,而RandomWords对象为何只使用了createState()
却没有build()
,我也不知道,走着瞧吧。当对象实例化的时候,首先执行Widget build(BuildContext context){}函数,函数中BuildContext类型的参数context,到目前为止还不知道干吗用的,暂且忽略其意义吧。dom
material类型的子控件都经过回调函数的方式建立,我读起来有些不习惯,但经过回调,免去了先声明再使用的麻烦,而且能够直接对对象的属性进行配置,经过build()
一层层回调,代码简洁很多,而代码中使用到的material内置控件,我就不一一介绍了,有兴趣的同窗请参考material官方API,注意material控件索引在页面右边的列表,别找到左边去了。ide
注意,遇到这种声明类属性的格式:_[变量名]
。按官方的意思是,若是变量名的前缀有_
下划线,表示强制转换为私有变量,至关于声明变量为private,但使用这个变量的时候,仍是要将下划线进行完整的书写。
保存代码后运行一下,能够看到APP变成了这个样子:
向下滚动试试,发现能够一直滚下去~
向列表里加个_收藏_标签按钮,使每行能够标记收藏或取消收藏。这个_收藏_标签就是状态,既然要修改状态,确定要到state中进行啦。
到对象RandomWordsState中定义一个对象,用于存储标记。为何要单独存储标记呢?由于这样就不须要往行对象(ListTile)中添加标记,下降了对象的复杂度。以下:
class RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _saved = new Set<WordPair>(); //新加这一句 final _biggerFont = const TextStyle(fontSize: 18.0); ... }
为何存储标记的是对象而不是一个数组呢?大概是想顺便教咱们使用一下Set对象吧,听说Set对象不容许有重复的项目,方便后面模拟堆栈的效果,很是适合这种场景。而后咱们到每一个行添加一个标记收藏的控件:
Widget _buildRow(WordPair pair) { //定义一个布尔变量,用于判断行控件ListTile是否被标记为收藏 final alreadySaved = _saved.contains(pair); return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), //安放图标控件 trailing: new Icon( alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null, ), //定义点击事件,控制图标的样式的切换 onTap: () { setState(() { if (alreadySaved) { _saved.remove(pair); } else { _saved.add(pair); } }); }, ); }
注意,在onTap
事件中,使用到了setState()
方法(用过vue或react的玩家是否是很熟悉呀),在这个方法里修改变量值,便可触发state对象执行build()
方法重绘对象。这里每次变动对象_saved后,都会重绘ListTile对象,而静态变量alreadySaved
也被从新定义,所以不用担忧alreadySaved
值不被更新的问题,若是去除final
关键字,Dart语法会报错,还请路过大神点拨一下缘由。
保存代码后,能够看到APP刷新了,每一行都添加了一个心型图标,反复戳这个行,还自动配有动画效果:
加入一个导航栏样式的堆栈。
先往主页面控件(AppBar)中添加一个能够进入收藏列表的入口:
class RandomWordsState extends State<RandomWords> { ... @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Startup Name Generator'), //为AppBar对象的actions属性添加一个IconButton对象,actions属性值能够是Widget类型的数组 actions: <Widget>[ new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved), //能够试试添加这两行后APP上有什么效果 new IconButton(icon: new Icon(Icons.add), onPressed: _pushSaved), new IconButton(icon: new Icon(Icons.create), onPressed: _pushSaved), ], ), body: _buildSuggestions(), ); } ... }
因为没有定义_pushSaved
函数,直接复制上面注释说明的代码会报错,所以,定义一个void类型的函数_pushSaved
:
class RandomWordsState extends State<RandomWords> { ... void _pushSaved() { } }
这时候能够看到右上角多了3个图标,如图:
而后咱们往_pushSaved()
函数中添加代码,让收藏夹玩起来:
void _pushSaved() { //建立导航栏控件Navigator,而后往里面塞入MaterialPageRoute控件 Navigator.of(context).push( new MaterialPageRoute( builder: (context) { //经过遍历_saved对象,获取已收藏的行对象 final tiles = _saved.map( (pair) { return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), ); }, ); //函数的的链式调用,获取到添加好分割线的ListTile控件 final divided = ListTile .divideTiles( //divideTiles()函数,向每一个tile间隔插入一个1像素宽的边框 context: context, tiles: tiles, ) .toList(); //不要漏掉这个函数,不然进入收藏夹直接崩溃 return new Scaffold( appBar: new AppBar( title: new Text('收藏的列表项目'), ), body: new ListView(children: divided), //直接将准备好的ListTile塞入其中,完成内容填充 ); }, ), ); }
保存代码后,刷新APP,如图:
如上图所示,红色的箭头表示点击按钮后页面的切换,绿色箭头表示收藏夹的值的对照,有没有发现咱们并无写返回主页按钮的代码,这个返回按钮从哪来的呢?是由Navigator对象自动生成的,而且自动指向到主页面的路由,不过遗憾的是,没有加入返回手势的支持,若有须要,还得本身写,具体怎么写,跟着个人笔记走吧,我如今也不知道。
你们注意看主页列表和收藏夹列表的区别,二者都是列表,都是使用的ListView和ListTile对象,但实现的方式彻底不一样,插入行对象和分割线的差别颇有意思,有兴趣的同窗能够自行修改下代码,看看能不能将两种列表的构建方法对调一下,参考官方资料ListView和ListTile
变动UI主题风格。
这一步超级简单,往MaterialApp对象里添加theme
属性值便可:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Startup Name Generator', //添加theme属性值,塞入ThemeData对象 theme: new ThemeData( primaryColor: Colors.white, ), home: new RandomWords(), ); } }
再保存,刷新APP试试,主题变成了白色风格:
ThemeData()
方法自己提供了不少配色方案,玩家能够参考ThemeData官方说明,掌握其强大功能。没想到变动主题如此轻松,在当前APP界愈来愈追求视觉体验升级的趋势下,使用flutter开发APP的玩家应该欣慰很多吧~
好勒,此次官方萌新课程的搬运就到这里,我是真的超喜欢这个萌新课程,连课程总结都帮我写好了:
自我总结一下,flutter是一个控件高度集成化的开发平台,控件的完整度极高,控件之间的交互实现也倾向于傻瓜化,好比自动生成返回按钮、主题风格全局可控。只要把握好状态值和控件之间的嵌套关系,开发者几乎不须要单独敲代码实现跳转逻辑,代码简直不要太简洁,不知不觉间就写好了一个APP。而VScode中的Dart Code插件也实在太好用,代码提示、函数用法和参数都有详尽的说明,看得出谷歌拿出了十足的诚意要在跨平台开发上面大搞特搞一番。
固然了,满屏幕的回调函数让我这种编程思惟还停留在C语言时代的菜鸟来讲,扶墙~ 头有点晕,还须要点时间慢慢适应一下下,也因为我没有那么深厚的技术功底,对这个教程的理解还比较有限,可能有写的不对或很差的地方,也欢迎你们指正,尤为我花了一天一晚上写了这篇稿也是蛮不容易了,有路过的高手不说两句也是哪啥了是吧。固然,我有空的时候抓紧读一读Dart 语法基础和官方原版,有了新的发现也会写稿分享出来。
好啦就写到这里,广告时间,对flutter感兴趣的小伙伴能够关注我,欢迎你们到Flutter圈子中投稿,也能够联系管理员加入咱们的flutter微信群嗨聊,谢谢捧场~!
flutter 中文社区(官方QQ群:338252156)