创建一个最简单的 App
清空 lib/main.dart
,然后写入
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text('Hello World'),
),
),
);
}
}
Flutter 提供了一套丰富的 Material widgets。
有个非常重要的概念 Widget,几乎所有东西都是一个 widget。MyApp 继承 StatelessWidget 就是让自己变成一个 widget。
widget 最主要的工作就是提供 build()
方法来描述如何组织显示内部低层的 widget。
Scaffold 是 Material library 中提供的一个widget,它提供了默认的导航栏、标题和包含主屏幕 widget 树的 body 属性。
引入第三方包
打开 pubspec.yaml
,引入 english_words
这个包。
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
english_words: ^3.1.0
点击上方的 “Packages get” 以加载引入的包,或者执行命令 flutter packages get
也是可以的。
然后在 main.dart
中引入这个包
import 'package:english_words/english_words.dart';
修改代码使用这个包
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 创建 wordPair
final wordPair = WordPair.random();
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
// child: Text('Hello World'),
child: Text(wordPair.asPascalCase),
),
),
);
}
}
wordPair 是随机的,每次 Reload 都能看到不一样的文字。
添加一个 Stateful widget
Stateless widgets 是不可变的,所有属性都是 final 的。MyApp 本身就是一个 Stateless widget,实现了 build 方法。
Stateful widgets 维持着整个生命周期中可变的状态。实现一个 stateful widget 需要至少两个类:State 类和一个创建 State 实例的 StatefulWidget。StatefulWidget 本身不可变,但是 State 类在 widget 生命周期中一直存留。
-
添加 RandomWordsState。大部分代码会在这个类里,它是一个 State,它为 RandomWords widget 保存维持状态。这个例子里它的作用是:
- 保存生成的单词对,随着用户滑动而无限增长
- 用户通过点击列表的心形图标来添加或移除喜欢的单词对
class RandomWordsState extends State
{ // TODO Add build() method } -
添加 stateful 类型的 RandomWords widget,在
main.dart
中,MyApp 类外的任何地方定义都可以。主要作用是创建 State。class RandomWords extends StatefulWidget { @override RandomWordsState createState() => RandomWordsState(); }
-
实现 build 方法
class RandomWordsState extends State
{ @override Widget build(BuildContext context) { final wordPair = WordPair.random(); return Text(wordPair.asPascalCase); } } build 方法返回一个 Widget,而 Text 本身是 StatelessWidget 的子类。
-
使用 RandomWords 这个 Widget
将 MyApp 中 body 里的
child: Text(wordPair.asPascalCase),
修改成
child: RandomWords(),
RandomWords 是一个 stateful widget,它通过 createState 创建了一个 RandomWordsState,这个 State 来为这个 Widget 保存状态,State 本身通过 build 返回了一个 Text。
创建无限滚动的列表
-
在 RandomWordsState 中创建一个变量
_suggestions
,用于保存单词对,在 Dart 语言中,_
开头表示私有权限。再创建_biggerFont
使文字变大class RandomWordsState extends State
{ final _suggestions = []; // 私有变量保存单词对 final _biggerFont = const TextStyle(fontSize: 18.0); // ... } -
RandomWordsState 中创建私有函数
_buildSuggestions()
,用于创建 ListView 并展示单词对。ListView 类提供了一个 builder 属性——itemBuilder,它是一个工厂构建者,且通过匿名函数提供回调功能,这个匿名函数有两个参数,BuildContext 和行迭代器,迭代器从 0 开始且每次调用方法时递增。
class RandomWordsState extends State
{ // ... Widget _buildSuggestions() { return ListView.builder( padding: const EdgeInsets.all(16.0), itemBuilder: (context, i) { // 奇数行,返回 1 像素的分割线,相比于 Android,这里分割线本身也是一个 Item,也占了一个 position if (i.isOdd) return Divider(); // 整除 2,由于奇数行是分割线,所以能执行到这里时,i 的值为 0, 2, 4, ... final index = i ~/ 2; // _suggestions 里的单词都被用过了 if (index >= _suggestions.length) { // 再添加 10 条进去 _suggestions.addAll(generateWordPairs().take(10)); } // 使用单词对创建一个 ListTile return _buildRow(_suggestions[index]); }); } } -
在 RandomWordsState 中添加
_buildRow
函数Widget _buildRow(WordPair pair) { return ListTile( title: Text( pair.asPascalCase, style: _biggerFont, // 使用定义的文字样式控制大小 ), ); }
-
修改 RandomWordsState 的 build 方法,使用
_buildSuggestions
class RandomWordsState extends State
{ // ... @override Widget build(BuildContext context) { return Scaffold ( appBar: AppBar( title: Text('Startup Name Generator'), ), body: _buildSuggestions(), ); } } -
修改 MyApp 的 build 方法
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Startup Name Generator', home: RandomWords(), ); } }
这样整个页面的状态都转移到了 RandomWords 这个 Stateful widget 里,它最后通过 State 的 build 方法来获取显示的 Widget,而这个 Widget 的 body 调用
_buildSuggestions
方法返回 ListView。
添加交互
添加心形图标,相当于收藏功能。
-
在 RandomWordsState 添加私有属性
_saved
存储用户点击收藏后的单词对final _saved = Set
(); -
在
_buildRow
函数中,添加 alreadySaved 属性,是个 bool 类型,用于判断这个单词对是否已经在之前定义的_saved
集合中final alreadySaved = _saved.contains(pair);
-
在
_buildRow
里,添加心形图标,图标是在列表项里的,所以在创建 ListTile 里面添加 IconWidget _buildRow(WordPair pair) { final alreadySaved = _saved.contains(pair); return ListTile( title: Text( pair.asPascalCase, style: _biggerFont, ), trailing: Icon( // 判断是否在收藏,显示不同的图标和颜色 alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null, ), ); }
-
增加交互,当点击心形图标时,调用
setState()
去通知框架状态 State 被改变了,再次点击后,从收藏中移除Widget _buildRow(WordPair pair) { ... return ListTile( ... // 一整行的点击事件 onTap: () { setState(() { if (alreadySaved) { _saved.remove(pair); } else { _saved.add(pair); } }); }, ); }
和 Android 不同,在 Flutter 的响应式风格框架中,调用
setState()
会再次触发 State 的build()
,然后页面就被修改了。不是主动去修改界面,而是修改数据,让界面自动更新。
切换新页面
Flutter 中页面叫 route
,Navigator 维护着一个存放所有 route 的栈,进栈显示,消失就从栈移除,和 Android 差不多。
-
在 RandomWordsState 中的 build 方法里,在 AppBar 添加一个列表图标,点击时就去一个新的 route 页面,包含所有的收藏单词对。
class RandomWordsState extends State
{ ... @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Startup Name Generator'), // 是一个数组,现在有一个 IconButton,点击时就去调用 _pushSaved 方法 actions: [ IconButton(icon: Icon(Icons.list), onPressed: _pushSaved), ], ), body: _buildSuggestions(), ); } void _pushSaved() { } ... } -
用户点击 AppBar 上的图标时,创建一个 route 并通过 Navigator 的 push 方法添加到栈顶
void _pushSaved() { Navigator.of(context).push( MaterialPageRoute
( builder: (BuildContext context) { final Iterable tiles = _saved.map( (WordPair pair) { return ListTile( title: Text( pair.asPascalCase, style: _biggerFont, ), ); }, ); final List divided = ListTile .divideTiles( // 添加水平分隔线 context: context, tiles: tiles, // divided 持有上面的列表项 ) .toList(); // 转化成列表 return Scaffold( // 返回一个页面 appBar: AppBar( title: const Text('Saved Suggestions'), ), body: ListView(children: divided), // 通过上面的 divided 构建 ListView ); }, ), ); } 添加 MaterialPageRoute,在它的 build 方法中创建列表项 ListTile,即 divided,然后新页面用它构建 ListView。
新页面导航栏上有个返回按钮,点击就可以返回。
修改主题
通过 ThemeData 类修改主题为白色
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
theme: ThemeData(
primaryColor: Colors.white,
),
home: RandomWords(),
);
}
}