博客搬迁至https://blog.wangjiegulu.comhtml
RSS订阅:https://blog.wangjiegulu.com/feed.xmlgit
如下代码 Github 地址:https://github.com/wangjiegulu/flutter_test_01github
这个你建立第一个Flutter app的指南。若是你熟悉面向对象的代码,基本的编程概念,好比变量,循环,和条件,你就能够完成本教程。你不须要以前有Dart或者手机的编程经验。编程
你将构建什么数组
你将要实现一个简单的手机 app,为一个初创公司去生成一些推荐的名字。用户能够选择和取消选择这些名字,并保存最好的一些名字。代码一次生成10个名字。当用户滚动时,新的一批名字就会被生成。用户能够点击 app bar 右上角的按钮进入一个新的页面来仅展现被喜欢的名字。app
Gif 动图展现了 app 完成以后的运行效果。框架
你将学到什么less
你将使用什么dom
根据 开始你的第一个 Flutter app 的介绍,建立一个简单,模版的 Flutter app。给项目取名为 startup_namer (替换掉 myapp)。您将修改这个 app 来建立完成的 app。
在这个 codelab 中,你主要编辑 dart 代码存放处的 lib/main.dart。
提示:当复制代码到你的 app 中,缩进可能会歪斜。你可使用 Flutter 工具来自动修正它们:
- Android Studio / IntelliJ IDEA: 在 dart 代码上右键并选择 Reformat Code with dartfmt。
- VS Code: 右键并选择 Format Document。
- Ternimal: 运行 flutter format
。
替换 lib/main.dart。
删除 lib/main.dart 中的全部代码。使用下面的代码进行替换,它会在屏幕的中央展现 "Hello World"。
import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Welcome to Flutter', home: new Scaffold( appBar: new AppBar( title: new Text('Welcome to Flutter'), ), body: new Center( child: new Text('Hello World'), ), ), ); } }
运行App,你将会看到以下的屏幕
=>
)标志,这是一行函数或者方法的简写。build()
方法,它描述了如何根据其余较低级别的 widget 显示 widget。
在这一步,我将使用一个名为 english_words 的开源包,它包含了几千个最经常使用的英文单词和经常使用的工具方法。
在 pub.dartlang.org,你能够找到 english_words,以及不少其它的开源包。
pubspec 文件为 Flutter app 管理 assets。在 pubspec.yaml,增长 english_words (3.1.0或者更高) 到依赖列表。新增行在下面已被高亮:
dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.0 english_words: ^3.1.0
在 Android Studio’s editor 视图查看 pubspec,点击右上角的 Packages get。这会把包拉取到你的项目中。你会在控制台上看到如下信息:
flutter packages get Running "flutter packages get" in startup_namer... Process finished with exit code 0
在 lib/main.dart,增长一个 english_words
的导入,就如高亮展现的那样:
import 'package:flutter/material.dart'; import 'package:english_words/english_words.dart';
因为你的输入,Android Studio 针对库会给你一些导入的建议。而后将导入字符串呈现为灰色,让你知道倒入的库你没有使用它(目前为止)。
使用 English words 包生成文本,用来替换掉以前的 "Hello World" 字符串。
提示:"Pascal case" (也称为 “大驼峰式命名法”),表示字符串中的每一个单词,包括第一个单词,首字母大写。因此,“uppercamelcase” 就变成 “UpperCamelCase”。
作如下改变,以下面高亮处:
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) { final wordPair = new WordPair.random(); return new MaterialApp( title: 'Welcome to Flutter', home: new Scaffold( appBar: new AppBar( title: new Text('Welcome to Flutter'), ), body: new Center( //child: new Text('Hello World'), // Replace the highlighted text... child: new Text(wordPair.asPascalCase), // With this highlighted text. ), ), ); } }
若是 app 正在运行中,使用热重载按钮()来更新运行中的 app。每一次你点击了热重载,或者保存了项目,你将会看见不一样的词对,它在运行的 app 中是随机的。这是由于词对在 build 方法中被生成。在每次 MaterialApp 须要渲染或者在 Flutter Inspector 中切换平台的时候。
若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。
Stateless widget 是不可改变的,意味着它们的属性不能被修改 —— 全部值都是 final 的。
Stateful widgets 维护了状态,它可能会在 widget 的生命周期内被修改。实现一个 statful widget 须要两个类:1)一个 StatefulWidget 类,用来建立一个实例 2)一个 State 类。StatefulWidget 类自己是不可变的,但 State 类在整个 widget 的生命周期中保持不变。
在这一步中,你将会增长一个 stateful widget,RandomWords,增长它的 State class,RandomWordsState。State 类中将最终维护这个 widget 中推荐喜欢的词对。
增长 stateful RandomWords widget 到你的 main.dart 中。它能够被放在任何地方,甚至 MyApp 以外,可是这里的解决方案放在了文件的底部。RandomWords widget 除了建立它的 State 类没有什么特别的。
class RandomWords extends StatefulWidget { @override createState() => new RandomWordsState(); }
增长 RandomWordsState 类。app 的大部分代码将会写在这个类中,它维护拉这个 widget 中的 state。这个类会保存生成的词对,会被用户无限滚动,用户经过列表切换中的心图标来添加或删除它们。
你将逐步编写这个类。做为开始,经过如下高亮的文原本建立一个最小的 class:
class RandomWordsState extends State<RandomWords> { }
在增长了 state class 以后,IDE 警告这个类缺乏一个 build 方法。而后,你将增长一个基本的 build 方法经过从 MyApp 转移生成词对的代码到 RandomWordsState 来生成词对:
class RandomWordsState extends State<RandomWords> { @override Widget build(BuildContext context) { final wordPair = new WordPair.random(); return new Text(wordPair.asPascalCase); } }
经过如下高亮改变,从 MyApp 中移除生成词对的代码:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { final wordPair = new WordPair.random(); // Delete this line return new MaterialApp( title: 'Welcome to Flutter', home: new Scaffold( appBar: new AppBar( title: new Text('Welcome to Flutter'), ), body: new Center( //child: new Text(wordPair.asPascalCase), // Change the highlighted text to... child: new RandomWords(), // ... this highlighted text ), ), ); } }
重启 app,若是你尝试去热重载,你可能会看到一个警告:
Reloading... Not all changed program elements ran during view reassembly; consider restarting.
这多是误报,但考虑从新启动以确保你的更改反映在 app UI 中。
app 应该会跟之前同样,每次你热重载或者保存的时候展现一个词对。
若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。
在这一步,你将扩展 RandomWordsState 来生成和展现一个列表的词对。当用户滚动时,展现在 ListView widget 的列表会无限滚动。ListView 的 builder
factory 构造方法容许你根据须要实现懒加载。
在 RandomWordsState 类中增长一个 _suggestions
list 来保存推荐的词对。注意变量如下划线(_
)开头。在 Dart 语言中,如下划线做为前缀标志表明私有。
也增长一个 biggerFont
变量来使字体变大。
class RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _biggerFont = const TextStyle(fontSize: 18.0); ... }
在 RandomWordsState 类中增长一个 _buildSuggestions()
方法。这个方法构建展现词对的 ListView。
ListView 类提供了一个 builder 属性,itemBuilder
,以匿名方法的方式指定一个工厂构造器和回调方法。两个参数会被传入到方法中 —— BuildContext,和行迭代器,i
。迭代器从0开始,每一次方法被调用时递增,每一个推荐词对配对一次。这个模型容许在用户滚动时推荐列表无限滚动。
增长如下高亮行:
class RandomWordsState extends State<RandomWords> { ... Widget _buildSuggestions() { return new ListView.builder( padding: const EdgeInsets.all(16.0), // The itemBuilder callback is called, once per suggested word pairing, // and places each suggestion into a ListTile row. // For even rows, the function adds a ListTile row for the word pairing. // For odd rows, the function adds a Divider widget to visually // separate the entries. Note that the divider may be difficult // to see on smaller devices. itemBuilder: (context, i) { // Add a one-pixel-high divider widget before each row in theListView. if (i.isOdd) return new Divider(); // The syntax "i ~/ 2" divides i by 2 and returns an integer result. // For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2. // This calculates the actual number of word pairings in the ListView, // minus the divider widgets. final index = i ~/ 2; // If you've reached the end of the available word pairings... if (index >= _suggestions.length) { // ...then generate 10 more and add them to the suggestions list. _suggestions.addAll(generateWordPairs().take(10)); } return _buildRow(_suggestions[index]); } ); } }
_buildSuggestions
方法在每一个词配对时调用。这个方法在一个 ListTile 中展现一个新的配对,在下一步中它容许你在行中增长交互。
在 RandomWordsState
中增长一个 _buildRow
方法:
class RandomWordsState extends State<RandomWords> { ... Widget _buildRow(WordPair pair) { return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), ); } }
使用 _buildSuggestions()
来更新 RandomWordsState 的 build 方法,而不是直接调用生成词对的库。修改如下高亮改变:
class RandomWordsState extends State<RandomWords> { ... @override Widget build(BuildContext context) { final wordPair = new WordPair.random(); // Delete these two lines. Return new Text(wordPair.asPascalCase); return new Scaffold ( appBar: new AppBar( title: new Text('Startup Name Generator'), ), body: _buildSuggestions(), ); } ... }
更新 MyApp 的 build 方法。在 MyApp 中移除 Scaffold 和 AppBar 实例。这些应该由 RandomWordsState 去管理,这让在下一步中导航到另外一个页面时修改 app bar 的名字更简单。
用下面高亮的 build 方法替换原生的方法:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Startup Name Generator', home: new RandomWords(), ); } }
重启 app,你将看到一个词对列表。按你想要的去滚动列表,你会看到新的词对。
若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。
在这一步,你将在没行增长一个可点击的心型图标。当用户点击 list 中的每行时,切换它的 “喜欢” 状态,这会触发词对在保存的集合中增长或者删除。
在 RandomWordsState 中增长一个 _saved
集合。这个集合存储了用户喜欢了的词对。集合首选 List,由于正确的实现是 Set 不容许重复的条目。
class RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _saved = new Set<WordPair>(); final _biggerFont = const TextStyle(fontSize: 18.0); ... }
在 _buildRow
方法中,增长一个 alreadySaved
检查来确保词对是否已经添加到喜欢集合中了。
Widget _buildRow(WordPair pair) { final alreadySaved = _saved.contains(pair); ... }
在 _buildRow()
,增长一个心型的图标到 ListTile 来启用喜欢状态。稍后,你会在这个心型图标上增长一个交互。
增长如下高亮:
Widget _buildRow(WordPair pair) { 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, ), ); }
重启 app,如今你会看到每行都有心型图标,可是它们还不能交互。
在 _buildRow
方法中让心形图标可点击。若是一个词对已经被添加到喜欢集合,再次点击会从喜欢集合中删除。小心形图标被点击,调用setState()
方法来通知系统状态被改变。
增长高亮行:
Widget _buildRow(WordPair pair) { 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); } }); }, ); }
提示:在 Flutter 响应式风格框架中,调用 setState() 触发 State 对象的 build() 方法的调用,结果更新在 UI 中。
热重载 app,你应该会看到点击任意行来喜欢,取消喜欢条目。注意,点击一行会生成从心型图标发出的隐式墨迹飞溅动画。
若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。
在这一步,你将增长一个新的页面(在 Flutter 被称为 router)用来展现喜欢的集合。你将会学习到怎么从首页导航到一个新的页面。
在 Fluter,Navigator 管理包含了 app 路由页面的栈。压入一个页面到 Navigator 的栈,更新展现那个页面。从 Navigator 弹出一个页面,返回展现上一个页面。
在 RandomWordsState 的 build 方法中增长一个列表图标到 AppBar 上。当用户点击这个列表图标,一个包含了喜欢的条目的新页面被压入到 Navigator,展现图标。
提示:一些widget属性接收单个 widget(child),其它的属性,如 action,接收一个数组 widgets(children),经过中括号([])标明。
在 build 方法中增长图标和它对应的 action:
class RandomWordsState extends State<RandomWords> { ... @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Startup Name Generator'), actions: <Widget>[ new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved), ], ), body: _buildSuggestions(), ); } ... }
在 RandomWordsState 类中增长一个 _pushSaved()
方法。
class RandomWordsState extends State<RandomWords> { ... void _pushSaved() { } }
热重载 app,列表图标出如今 app bar 上。点击它还不会发生任何事情,由于 _pushSaved
方法是空的。
当用户点击 app bar 上的列表图标,构建一个页面并压入 Navigator 的栈中。这个 action 会改变屏幕去展现新的页面。
新页面的内容在 MaterialPageRoute 的 builder
属性中经过匿名方法构建。
增长调用 Navigator.push
,以下高亮代码展现,把页面压入到 Navigator 的栈里。
void _pushSaved() { Navigator.of(context).push( ); }
增长 MaterialPageRoute 和它的 builder。如今,增长生成 ListTile 行的代码。ListTile 的 divideTiles()
方法在每一个 ListTile 之间添加水平间距。分割变量保存最后一行,由 convienice 函数 toList()
转换为列表。
void _pushSaved() { Navigator.of(context).push( new MaterialPageRoute( builder: (context) { final tiles = _saved.map( (pair) { return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), ); }, ); final divided = ListTile .divideTiles( context: context, tiles: tiles, ) .toList(); }, ), ); }
builder 属性返回一个 Scaffold,包含了新页面的 app bar,名为 “Save Suggestions”。新页面 body 的构成是一个 ListView 包含了 ListTiles 行;每行由分隔符分割。
增长如下高亮代码:
void _pushSaved() { Navigator.of(context).push( new MaterialPageRoute( builder: (context) { final tiles = _saved.map( (pair) { return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), ); }, ); final divided = ListTile .divideTiles( context: context, tiles: tiles, ) .toList(); return new Scaffold( appBar: new AppBar( title: new Text('Saved Suggestions'), ), body: new ListView(children: divided), ); }, ), ); }
热重载 app,喜欢其中的一些条目并点击 app bar 上的列表图标。新页面展现出来,且包含了喜欢的条目。注意 Navigator 在 app bar 上增长了一个 “Back” 按钮。你不须要明确地实现 Navigator.pop。点击返回按钮来返回首页。
若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。
在这一步,你将玩转 app 的 theme。Theme会控制你的 app 的视觉和感受。你可使用默认的 theme,这依赖于物理设备或者模拟器,或者你能够自定义 theme 来反映出你的品牌。
你能够很简单地经过配置 ThemeData 类来改变 app 的主题。你的 app当前使用的是默认的主题,可是你将修改主要颜色为白色。
经过增长高亮的代码到 MyApop 来改变 app 的主题为白色:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Startup Name Generator', theme: new ThemeData( primaryColor: Colors.white, ), home: new RandomWords(), ); } }
热重载 app,注意,整个背景都是白色的,甚至是 app bar。
做为读者的联系,使用 ThemeData 来改变 UI 的其它方面。Material 库中的 Colors 类提供了不少颜色常量可使用,而后热重载使得 UI 实验变的又快又简单。
若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。
你已写了一个运行在 iOS 和 Android 的具备交互性的 Flutter app。在这个 codelab,你已经: