这是建立您的第一个Flutter应用程序的指南。 若是您熟悉面向对象的代码和基本编程概念(如变量,循环和条件),则能够完成本教程。 您不须要之前使用Dart或移动编程的经验。html
您将实施一个简单的移动应用程序,为一家创业公司生成建议名称。 用户能够选择和取消选择名称,保存最好的名称。 该代码一次生成十个名称。 当用户滚动时,会生成新批次的名称。 用户能够点击应用栏右上方的列表图标,以移动到仅列出收藏名称的新路由。java
动画GIF显示完成的应用程序的工做方式。git
你会学到什么:github
Flutter应用程序的基本结构。
查找和使用包来扩展功能。
使用热重载加快开发周期。
如何实现有状态的小部件。
如何建立一个无限的,延迟加载的列表。
如何建立并导航到第二个屏幕。
如何使用主题更改应用程序的外观。编程
你会到用什么:数组
您须要安装如下内容:网络
- Flutter SDK
- Flutter SDK包括Flutter的引擎,框架,小部件,工具和Dart SDK。 这个codelab须要v0.1.4或更高版本。
- Android Studio IDE
- 该codelab具备Android Studio IDE,但您可使用其余IDE,或者从命令行运行。
- 您的IDE插件
- Flutter和Dart插件必须为您的IDE单独安装。 除了Android Studio,Flutter和Dart插件也可用于VS Code和IntelliJ IDE。
有关如何设置环境的信息,请参阅Flutter安装和设置。app
使用第一个Flutter应用程序入门中的说明建立一个简单的模板化Flutter应用程序。 将项目命名为startup_namer(而不是myapp)。 你将会修改这个初学者应用程序来建立完成的应用程序。框架
在这个codelab中,你将主要编辑Dart代码所在的lib / main.dart。less
提示:将代码粘贴到应用程序中时,缩进可能会变形。 您可使用Flutter工具自动修复此问题:
- Android Studio / IntelliJ IDEA:右键单击飞镖代码,而后选择Reformat Code with dartfmt格式化代码。
- VS代码:右键单击并选择Format Document。
- 终端:运行flutter格式<filename>。
1.替换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'), ), ), ); } }
2.运行应用程序。 你应该看到下面的屏幕。
意见
在这一步中,您将开始使用名为english_words的开源软件包,其中包含数千个最经常使用的英文单词以及一些实用功能。
您能够在pub.dartlang.org上找到english_words软件包以及其余许多开源软件包。
1.pubspec文件管理Flutter应用程序的资产。 在pubspec.yaml中,将english_words(3.1.0或更高版本)添加到依赖项列表。 新行高亮以下:
dependencies:
flutter:
sdk: fluttercupertino_icons: ^0.1.0
english_words: ^3.1.0
2.在Android Studio编辑器视图中查看pubspec时,单击右上角的Packages get。 这将该包加入您的项目。 您应该在控制台中看到如下内容:
flutter packages get Running "flutter packages get" in startup_namer... Process finished with exit code 0
3.在lib/main.dart中,添加english_words导入语句,如突出显示的行所示:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
在您键入时,Android Studio会为您提供有关库导入的建议。 而后它将呈现灰色的导入字符串,让您知道导入的库还没有使用(到目前为止)。
4.使用英文单词包来生成文本,而不是使用字符串“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.
),
),
);
}
}
5.若是应用程序正在运行,请使用热从新加载按钮(闪电图标)更新正在运行的应用程序。 每次单击热从新加载或保存项目时,都会在正在运行的应用程序中随机选择不一样的单词对。 这是由于配对这个词是在构建方法内部生成的,每次MaterialApp须要渲染时或者在Flutter Inspector中切换平台时都会运行。
问题?
若是您的应用程序运行不正常,请查找错别字。 若是须要,请使用如下连接中的代码从新开始正轨。
无状态小部件是不可变的,这意味着它们的属性不能改变 - 全部的值都是最终的。
有状态的小部件保持在小部件的生命周期中可能改变的状态。 实现一个有状态的小部件至少须要两个类:1)一个StatefulWidget类,它建立一个2)一个State类的实例。 StatefulWidget类自己是不可变的,但State类在整个构件的生命周期中保持不变。
在这一步中,您将添加一个有状态的小部件RandomWords,它建立其状态类RandomWordsState。 State类将最终维护小部件的建议和最喜欢的单词对。
1.将有状态的RandomWords小部件添加到main.dart。 它能够在MyApp以外的文件中的任何位置使用,但解决方案将它放在文件的底部。 RandomWords小部件除了建立State类以外几乎没有其余任何东西:
class RandomWords extends StatefulWidget { @override createState() => new RandomWordsState(); }
2.添加RandomWordsState类。 该应用的大部分代码都驻留在该类中,该类保持RandomWords小部件的状态。 这个类将保存随着用户滚动而无限增加的生成的单词对,以及最喜欢的单词对,由于用户经过切换心脏图标来将它们从列表中添加或删除。
你会一点一点地创建这个类。 首先,经过添加突出显示的文本建立一个最小类:
class RandomWordsState extends State<RandomWords> { }
3.在添加状态类后,IDE会抱怨该类缺乏构建方法。 接下来,您将添加一个基本构建方法,该方法经过将单词生成代码从MyApp移动到RandomWordsState来生成单词对。
将构建方法添加到RandomWordState中,如突出显示的文本所示:
class RandomWordsState extends State<RandomWords> {
@override
Widget build(BuildContext context) {
final wordPair = new WordPair.random();
return new Text(wordPair.asPascalCase);
}
}
4.经过下面突出显示的更改从MyApp中删除单词生成代码:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final wordPair = new WordPair.random();// Delete this linereturn 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
),
),
);
}
}
从新启动应用程序。 若是您尝试从新加载热点,则可能会看到一条警告:
Reloading... Not all changed program elements ran during view reassembly; consider restarting.
这多是误报,但考虑从新启动以确保您的更改反映在应用的用户界面中。
应用程序应该像之前同样运行,每次热从新加载或保存应用程序时都会显示一个字对。
问题?
若是您的应用程序运行不正常,则可使用如下连接中的代码从新进入正轨。
在这一步中,您将展开RandomWordsState以生成并显示单词配对列表。 当用户滚动时,ListView小部件中显示的列表将无限增加。 ListView的builder工厂构造函数容许您根据须要懒惰地构建列表视图。
1.将一个_suggestions列表添加到RandomWordsState类,以保存建议的词对。 该变量如下划线(_)开头 - 在前面加上一个带有下划线的标识符能够强化Dart语言的隐私。
此外,添加一个largerFont变量来使字体变大。
class RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _biggerFont = const TextStyle(fontSize: 18.0); ... }
2.将一个_buildSuggestions()函数添加到RandomWordsState类。 此方法构建显示建议词对的ListView。
ListView类提供了一个构建器属性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]);
}
);
}
}
3._buildSuggestions函数每一个词对调用_buildRow一次。 这个函数在ListTile中显示每一个新对,这容许您在下一步中使行更具吸引力。
向RandomWordsState添加_buildRow函数:
class RandomWordsState extends State<RandomWords> {
...Widget _buildRow(WordPair pair) {
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
);
}
}
4.更新RandomWordsState的build 方法以使用_buildSuggestions(),而不是直接调用单词生成库。 进行突出显示的更改:
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(),
);
}
...
}
5.更新MyApp的build方法。 从MyApp中删除Scaffold和AppBar实例。 这些将由RandomWordsState管理,这使得用户在下一步中从一个屏幕导航到另外一个屏幕时,能够更轻松地更改应用栏中的路由名称。
用下面突出显示的构建方法替换原始方法:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
home: new RandomWords(),
);
}
}
从新启动应用程序。 你应该看到一个单词配对清单。 尽量向下滚动,您将继续看到新的单词配对。
问题?
若是您的应用程序运行不正常,则可使用如下连接中的代码从新进入正轨。
在这一步中,您将为每一行添加可点击的心脏图标。 当用户点击列表中的条目,切换其“收藏”状态时,该词语配对被添加或从一组保存的收藏夹中移除。
1.将一个_saved集添加到RandomWordsState。 这个集合存储用户最喜欢的单词配对。 Set比List更受欢迎,由于正确实施的Set不容许重复输入。
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];final _saved = new Set<WordPair>();
final _biggerFont = const TextStyle(fontSize: 18.0);
...
}
2.在_buildRow函数中,添加alreadySaved检查以确保单词配对还没有添加到收藏夹。
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
...
}
3.一样在_buildRow()中,将心形图标添加到ListTiles以启用收藏。 稍后,您将添加与心脏图标进行交互的功能。
添加下面突出显示的行:
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,
),
);
}
4.从新启动应用程序。 你如今应该在每一行看到开放的心,但它们尚未互动。
5.在_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的更新。
热从新加载应用程序。 你应该可以点击任何一行以得到最喜欢的,或不适合的入口。 请注意,点击一行会生成从心脏图标发出的隐式墨迹飞溅动画。
问题?
若是您的应用程序运行不正常,则可使用如下连接中的代码从新进入正轨。
在这一步中,您将添加一个显示收藏夹的新屏幕(在Flutter中称为路由)。 您将学习如何在主路由和新路由之间导航。
在Flutter中,导航器管理包含应用程序路由的堆栈。 将路由推入导航器的堆栈,将显示更新为该路由。 从导航器的堆栈中弹出路由,将显示返回到前一个路由。
1.向RandomWordsState的构建方法中的AppBar添加列表图标。 当用户点击列表图标时,包含收藏夹项目的新路线被推送到导航器,显示该图标。
提示:某些小部件属性采用单个小部件(子级),而其余属性(如操做)则采用小部件(子级)数组,如方括号([])所示。
将该图标及其相应的操做添加到构建方法中:
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(),
);
}
...
}
2.将一个_pushSaved()函数添加到RandomWordsState类。
class RandomWordsState extends State<RandomWords> {
...
void _pushSaved() {
}
}
热从新加载应用程序。 列表图标出如今应用程序栏中。 点击它什么也没作,由于_pushSaved函数是空的。
3.当用户点击应用栏中的列表图标时,创建一条路由并将其推送到导航器的堆栈。 此操做会更改屏幕以显示新路由。
新页面的内容是使用匿名函数在MaterialPageRoute的builder属性中构建的。
将呼叫添加到Navigator.push,如突出显示的代码所示,将路由推送到导航器的堆栈。
void _pushSaved() {
Navigator.of(context).push(
);
}
4.添加MaterialPageRoute及其构建器。 如今,添加生成ListTile行的代码。 ListTile的divideTiles()方法在每一个ListTile之间添加水平间距。 变量divided保存最后的行,经过便利函数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();
},
),
);
}
5.builder属性返回一个Scaffold,其中包含名为“Saved Suggestions”的新路由的应用栏。新路由的主体由包含ListTiles行的ListView组成; 每行由一个分隔符分隔。
添加下面突出显示的代码:
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),
);
},
),
);
}
6.热从新加载应用程序。 最喜欢的一些选择,并点击应用栏中的列表图标。 新路线显示包含收藏夹。 请注意,导航器会在应用栏中添加一个“返回”按钮。 你没必要显式实现Navigator.pop。 点击后退按钮返回到主页路由。
问题?
若是您的应用程序运行不正常,则可使用如下连接中的代码从新进入正轨。
在最后一步中,您将使用该应用的主题。 主题控制你的应用的外观和感受。 您可使用默认主题,该主题取决于物理设备或模拟器,也能够自定义主题以反映品牌。
1.您能够经过配置ThemeData类轻松更改应用程序的主题。 您的应用程序目前使用默认主题,但您将更改主要颜色为白色。
将突出显示的代码添加到MyApp,将应用程序的主题更改成白色:
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(),
);
}
}
2.热从新加载应用程序。 请注意,整个背景是白色的,甚至是应用栏。
3.做为读者的练习,使用ThemeData来改变UI的其余方面。 材质库中的Colors类提供了许多可使用的颜色常量,而热重载使得用户界面的实验变得快速而简单。
问题?
若是您的应用程序运行不正常,则可使用如下连接中的代码从新进入正轨。
您已经编写了一个在iOS和Android上运行的交互式Flutter应用程序。 在这个codelab中,你有: