在Flutter框架中,StateFulWidget是一个非常重要的概念。StateFulWidget允许开发者创建可以改变状态的widget,这在构建动态用户界面时非常有用。与StatelessWidget不同,StatelessWidget的属性是不可变的,一旦显示在屏幕上就不能更改。StateFulWidget通过State类来维护可能在widget生命周期内发生变化的状态。
本教程的目标是创建一个简单的移动应用程序,该程序包含以下功能:
通过这个应用程序,将展示StateFulWidget如何在widget或类/属性的值发生变化时刷新屏幕。
让直接进入应用程序的创建步骤:
flutter create flutter2_sfw
。请确保Flutter的bin路径已添加到环境变量中。在main.dart
文件中,删除所有内容并编写以下代码:
import 'package:flutter/material.dart';
import 'package:flutter2_sfw/widgets/mainpage.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// 这个widget是应用程序的根。
@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'),
);
}
}
在这里,创建了主widget,它将是移动应用程序的起点。MyApp是StatelessWidget,它将托管StateFulWidget作为第一页,这就是为什么传递MainPage(待创建)作为对象的原因。
接下来,在lib文件夹中右键单击并选择新建,然后添加一个名为widgets的包。在其中添加一个新的dart文件,命名为mainpage.dart,它将包含StateFul widget的代码。
StateFul和Stateless widget的区别在于,创建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 {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
);
}
}
在这里,创建了MainPage类,它继承自StateFulWidget。重写了createState()函数,并提供了与之关联的状态类_MainPageState,它扩展了MainPage的泛型状态,将包含构建UI的代码。
如果在这一点上运行项目,这将是模拟器中的视图:
现在是时候在上述步骤的Scaffold主体中添加TextField和按钮了,构建函数将如下所示:
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: [
new TextField(
decoration: InputDecoration(
filled: true,
labelText: 'City Name',
),
controller: _cityNameController,
),
new ButtonBar(
children: [
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 _lstText = new List();
_onAddCityBtnPressed() Function
这个函数将在按下应用程序中的“添加城市”按钮时被调用。
_onAddCityBtnPressed() {
setState(() {
_lstText.add(new Text(
"${_lstText.length + 1} ${_cityNameController.text}",
textAlign: TextAlign.justify,
style: new TextStyle(fontWeight: FontWeight.bold),
));
_cityNameController.clear();
});
}
在这里,创建了一个ListView控件,它是一个滚动控件,允许放置可以垂直滚动的多个widget。不用担心,如果现在不理解,将为ListView编写另一篇文章,讨论它提供的所有突出属性和方法。
由于它可以容纳多个子项,首先添加的子项将是TextField,在这里有两个TextField属性:
第二个子项是ButtonBar,它提供了添加多个按钮的模板,创建了两个RaisedButton,第一个将清除TextField的内容,另一个按钮将把TextField中的任何文本添加到名为_lstText的本地列表中。
_onAddCityBtnPressed() - 将把TextField中的内容添加到_lstText中,并清除textField。_lstText是List类型,可以从另一篇文章中了解更多信息。
如果没有注意到,当更新_lstText时,它被包裹在setState()函数中,它在那里是为了指示框架,一些对象已更新,需要刷新状态。
现在是时候添加魔法了,直到第7点,能够接受用户输入并将其存储在类变量中,但是即使设置了状态并使框架意识到更新了某些内容,屏幕上仍然没有任何显示。现在是时候添加ListView了,它将在_lstText更新时更新自己。在_MainPageState的构建函数中创建了两个函数来处理这种情况,添加getListViewBuilder()函数,并为创建ListView添加了这两个函数:
// 提供ListView从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,
);
}
// 回调函数,将为每个项目调用
Widget getListItems(BuildContext context, int index) {
return _lstText[index];
}
在这里,使用ListView.builder创建了ListView,传递了itemCount和itemBuilder属性的回调函数。getListItems()函数将根据回调函数传递给它的索引返回widget。