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日:第一个版本