Flutter 入门:教程 2 – StateFulWidget  
让我们探讨 Flutter 中的 StateFulWidget 世界。
引言
在本文中,我将讨论Flutter中一个非常重要的概念,即StatefulWidget。如果您阅读了我第一篇文章,我创建的所有小部件在那里都是无状态的,根据Flutter网站的定义,小部件是不可变的,意味着它们的属性无法更改——所有值都是最终的,一旦显示。
那么,为什么我们要使用这种语言/框架,而又不能更改移动屏幕上的任何内容呢?答案在于Flutter开发人员卓越的架构方法StatefulWidget,它维护了在小部件生命周期中可能发生变化的state。
背景
实现一个状态管理小部件至少需要两个类,它们是:
- 一个StatefulWidget类,它创建了一个State类的实例
- StatefulWidget类本身是不可变的,但是- State类在小部件的生命周期内保持不变
我们将在本教程中进一步讨论这一点。
教程目标
我将创建一个小型移动应用程序,其中将包含以下内容:
- 显示应用程序名称的AppBar
- 用于接受用户输入的TextField
- 带有两个按钮的ButtonBar- Clear 按钮:用于清除- TextBox中的文本
- Add City 按钮:用于将用户输入添加到列表视图
 
- 用于持久化用户输入的ListView
总的来说,基本思想是创建一个应用程序中访问过的城市列表,这将展示StatefulWidget如何在小部件/类中的任何值发生变化时刷新屏幕。
Using the Code
那么,让我们直接深入应用程序,以下是如果您想动手实践的步骤:
- 打开Android Studio并创建一个名为flutter2_sfw(StatefulWidget的缩写)的新Flutter项目。如果您使用Visual Studio Code,请在终端窗口中使用命令flutter create flutter2_sfw。请确保Flutter bin路径已添加到您的环境变量中。
- 项目创建完成后,解决方案的基本结构将如下所示: 
- 现在,在lib文件夹下的main.dart文件中,删除所有内容并写入以下代码:import 'package:flutter/material.dart'; import 'package:flutter2_sfw/widgets/mainpage.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.amber, ), home: new MainPage(title: 'Flutter 2 - Add City'), ); } }代码解释- 在这里,我们正在创建我们的主小部件,它将是我们移动应用程序的起点。
- MyApp是一个- StatelessWidget,它将作为第一页托管我们的- StatefulWidget,这就是为什么我们传递- MainPage(待创建)作为对象。 
 
- 现在,右键单击lib文件夹,点击新建,然后添加一个名为widgets的包到文件夹。
- 添加一个名为mainpage.dart的新dart文件,其中将包含我们Stateful小部件的代码。
- Stateful和- Stateless小部件之间的区别,对于创建- StatefulWidget,您至少需要两个类,一个是实际的页面,另一个是保存页面状态的类。- class MainPage extends StatefulWidget { MainPage({Key key, this.title}) : super(key: key); final String title; @override _MainPageState createState() => new _MainPageState(); } class _MainPageState extends State<MainPage> { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), )); }- 代码解释- 在这里,我们创建MainPage类,它继承自StatefulWidget。
- 我们覆盖createState()函数,并为其提供链接的状态类。
- _MainPageState类扩展了- MainPage的通用状态,将包含构建UI的代码。
- 如果我们此时运行我们的项目,在模拟器中就会看到以下视图: 
 
- 在这里,我们创建
- 现在是时候在上述步骤的scaffold的body中添加TextField和按钮了,build函数将如下所示:Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new ListView( padding: const EdgeInsets.symmetric(horizontal: 5.0), children: <Widget>[ new TextField( decoration: InputDecoration( filled: true, labelText: 'City Name', ), controller: _cityNameController, ), new ButtonBar( children: <Widget>[ new RaisedButton( onPressed: () { _cityNameController.clear(); }, child: new Text('Clear'), ), new RaisedButton( child: new Text('Add City'), onPressed: _onAddCityBtnPressed, color: Colors.amber, ) ], ), ] ) ); }新类成员 (_MainPageState)final TextEditingController _cityNameController = TextEditingController(); final List<Widget> _lstText = new List<Widget>(); _onAddCityBtnPressed() 函数当我们点击应用程序中的Add City 按钮时,将调用此函数。_onAddCityBtnPressed() { setState(() { _lstText .add( new Text("${_lstText.length + 1} ${_cityNameController.text}", textAlign: TextAlign.justify, style: new TextStyle(fontWeight:FontWeight.bold),)); _cityNameController.clear(); });代码解释- 在这里,我们创建了一个ListView控件,它是一个滚动控件,允许您放置可以垂直滚动的多个小部件。如果您现在不理解它,请不用担心,我将单独写一篇关于ListView的文章,讨论它提供的所有主要属性和方法。
- 由于它可以接受多个子项,我们将添加的第一个子项将是TextField,这里我们有两个TextField的属性:- controller -这将充当用于在屏幕上设置和获取值的对象。在这里,我们传递类成员- _cityNameController。
- decoration -此属性将定义- textfield的UI,我提供了- labelText为'Add City',并将- filled设置为- true。
 
- 第二个子项是ButtonBar,它提供了添加多个按钮的模板,我创建了两个RaisedButton,第一个按钮将清除TextField的内容,另一个按钮将把TextField中存在的任何文本添加到名为_lstText的本地列表中。- Clear 按钮:- OnPressed将调用- _cityNameController.clear();,这将清除- textField控件。
- Add City 按钮:- OnPressed将调用- _onAddCityBtnPressed()方法,我将在下一步中说明其工作原理。
 
- _onAddCityBtnPressed()- 将把- TextField中的任何内容添加到- _lstText中,并清除- textField。- _lstText的类型是- List,您可以从我的另一篇文章这里阅读更多关于它的信息。
- 如果您没有注意到,当我们更新_lstText时,它被包装在setState()函数中,这是为了向框架指示某些对象已更新,您需要刷新状态。
 
- 在这里,我们创建了一个
- 现在是时候添加魔法了,直到完成第7点,我们才能够获取用户输入并将其存储在类变量中,然而屏幕上没有任何显示,即使在我们设置了状态并让框架知道我们更新了某些内容之后。现在是时候添加我们的ListView了,它会在_lstText更新时自动更新。我创建了两个函数来处理这种情况,在_MainPageState的build函数中添加getListViewBuilder()函数,并且我添加了这两个函数来创建ListView。// Provide ListView from ListView.builder ListView getListViewBuilder() { return new ListView.builder( shrinkWrap: true, itemCount: _lstText.length, padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0), itemBuilder: getListItems, ); } // Call back function, will called for each item in the Widget getListItems(BuildContext context, int index) { return _lstText[index]; }代码解释- 在这里,我们借助ListView.builder创建了一个ListView,通过传递itemCount和itemBuilder属性的回调函数。
- getListItems()函数将返回基于由回调函数传递给它的索引的小部件。
 
- 在这里,我们借助
- 最终运行 
至此,本文已接近尾声。请在留言板上告诉我您的评论。谢谢!
关注点
请阅读这些文章。它们可能会给您一个方向,告诉您真正的发展趋势。
- Flutter — 你可能喜欢它的 5 个理由
- Flutter 的革命性之处
- 为什么 Flutter 使用 Dart
- Github: https://github.com/thatsalok/FlutterExample/tree/master/flutter2_sfw
Flutter 教程
Dart 教程
历史
- 2018年7月9日:第一个版本



