你会学到什么:
你如何修改你的应用程序,使其对用户输入做出反应? 在本教程中,您将为仅包含非交互式小部件的应用添加交互性。 具体来说,您将通过创建一个管理两个无状态小部件的自定义状态小部件来修改图标以使其可以点击。
有状态和无状态的小部件 创建一个有状态的小部件
管理状态
其他交互式小部件
资源
如果您已经在Flutter布局中构建布局,请跳到下一节。
确保你已经建立了你的环境。
一旦你有一个连接和启用的设备,或者你已经启动了iOS模拟器(Flutter安装的一部分),你很好!
Flutter的Building Layouts展示了如何为下面的截图创建布局。
当应用第一次启动时,这颗恒星是纯红色的,表明这个湖以前已经被收藏了。 星号旁边的数字表示41个人对此湖感兴趣。 完成本教程后,轻敲星星将删除其偏好状态,用轮廓线代替实心星并减少计数。 再次轻拍湖面,画出星星并增加计数。
为了实现这一点,您将创建一个包含星号和计数的自定义小部件,它们都是小部件。 因为点击明星会更改这两个小部件的状态,所以同一个小部件应该同时管理这两个小部件。
您可以正确触摸第2步:子类StatefulWidget中的代码。 如果您想尝试不同方式管理状态,请跳至管理状态。
重点是什么?
无状态小部件没有内部状态来管理。 Icon,IconButton和Text是StatelessWidget子类的无状态小部件示例。
有状态的小部件是动态的。 用户可以与有状态的小部件进行交互(例如通过输入表单或移动滑块),或者随着时间的推移而变化(可能是数据馈送导致UI更新)。 Checkbox,Radio,Slider,InkWell,Form和TextField是StatefulWidget子类的有状态小部件的示例。
重点是什么?
在本节中,您将创建一个自定义有状态小部件。 您将使用一个自定义状态小部件替换两个无状态小部件 - 纯红星和其旁边的数字计数 - 该小部件用两个子部件管理一行:IconButton和Text。
实现一个定制的有状态小部件需要创建两个类:
本节展示如何为Lakes应用程序构建一个名为Favorite Widget的有状态小部件。 第一步是选择如何管理Favorite Widgets状态。
第1步:决定哪个对象管理小部件的状态
小部件的状态可以通过多种方式进行管理,但在我们的示例中,小部件本身(FavoriteWidget)将管理自己的状态。 在这个例子中,切换星号是一个独立的操作,不会影响父窗口小部件或其他用户界面,因此窗口小部件可以在内部处理它的状态。
在管理状态中了解更多关于窗口小部件和状态的分离以及如何管理状态的信息。
第2步:子类StatefulWidget
FavoriteWidget类管理自己的状态,因此它重写createState()来创建状态对象。 当它想要构建小部件时,框架调用createState()。 在这个例子中,createState()创建_FavoriteWidgetState的一个实例,你将在下一步中实现它。
class FavoriteWidget extends StatefulWidget {
@override
_FavoriteWidgetState createState() => new _FavoriteWidgetState();
}
第3步:子类状态
自定义State类存储可变信息 - 可以在小部件的生命周期内改变的逻辑和内部状态。 当应用第一次启动时,用户界面显示一个稳固的红色星星,表明该湖有“最喜欢”的状态,并有41个“喜欢”。 状态对象将这些信息存储在_isFavorited和_favoriteCount变量中。
状态对象还定义了build方法。 此build方法创建一个包含红色IconButton和Text的行。 该小部件使用IconButton(而不是Icon),因为它有一个onPressed属性,该属性定义了处理水龙头的回调方法。 IconButton也有一个保存图标的Icon属性。
_toggleFavorite()方法在按下IconButton时调用,它调用setState()。 调用setState()是至关重要的,因为这会告诉框架小部件的状态已经改变,并且小部件应该重绘。 _toggleFavorite函数在1)星形图标和数字“41”,以及2)star_border图标和数字“40”之间交换UI。
class _FavoriteWidgetState extends State<FavoriteWidget> {
bool _isFavorited = true;
int _favoriteCount = 41;
void _toggleFavorite() {
setState(() {
// If the lake is currently favorited, unfavorite it.
if (_isFavorited) {
_favoriteCount -= 1;
_isFavorited = false;
// Otherwise, favorite it.
} else {
_favoriteCount += 1;
_isFavorited = true;
}
});
}
@override
Widget build(BuildContext context) {
return new Row(
mainAxisSize: MainAxisSize.min,
children: [
new Container(
padding: new EdgeInsets.all(0.0),
child: new IconButton(
icon: (_isFavorited
? new Icon(Icons.star)
: new Icon(Icons.star_border)),
color: Colors.red[500],
onPressed: _toggleFavorite,
),
),
new SizedBox(
width: 18.0,
child: new Container(
child: new Text('$_favoriteCount'),
),
),
],
);
}
}
提示:将文本放置在SizedBox中并设置其宽度可防止文本在40和41之间变化时出现明显的“跳跃” - 否则会发生这种情况,因为这些值具有不同的宽度。
第4步:将有状态小部件插入小部件树中
将您的自定义状态小部件添加到应用构建方法中的小部件树中。 首先,找到创建图标和文本的代码,并删除它:
// ...
new Icon(
Icons.star,
color: Colors.red[500],
),
new Text('41')
// ...
在相同的位置创建有状态的小部件:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget titleSection = new Container(
// ...
child: new Row(
children: [
new Expanded(
child: new Column(
// ...
),
new FavoriteWidget(),
],
),
);
return new MaterialApp(
// ...
);
}
}
而已! 当您重新加载应用程序时,星形图标现在应该响应点击。
如果您无法运行代码,请在IDE中查找可能的错误。 调试Flutter应用程序可能会有所帮助。 如果仍然无法找到问题,请根据GitHub上的交互式湖区示例检查代码。
如果您仍然有疑问,请参阅获取支持。
本页面的其余部分介绍了可以管理窗口小部件状态的几种方式,并列出了其他可用的交互窗口小部件。
重点是什么?
谁管理有状态小部件的状态? 小部件本身? 父窗口小部件? 都? 另一个对象? 答案是......这取决于依赖高关系。有几种有效的方法可以让你的小部件互动。作为小部件设计师,您根据您期望使用的小部件做出决定。以下是管理状态的最常见方法:
你如何决定使用哪种方法? 以下原则可以帮助您决定:
如果有疑问,首先管理父窗口小部件中的状态。
我们将通过创建三个简单示例来举例说明管理状态的不同方式:TapboxA,TapboxB和TapboxC。 这些例子都是类似的工作 - 每创建一个容器,当点击时,在绿色或灰色框之间切换。 _active布尔值确定颜色:绿色表示激活或者灰色表示不激活。
这些示例使用GestureDetector捕获Container上的活动。
有时,小部件在内部管理其状态是最有意义的。 例如,当ListView的内容超过渲染框时,ListView自动滚动。 大多数使用ListView的开发人员不想管理ListView的滚动行为,因此ListView本身管理其滚动偏移量。
_TapboxAState类:
// TapboxA manages its own state.
//------------------------- TapboxA ----------------------------------
class TapboxA extends StatefulWidget {
TapboxA({Key key}) : super(key: key);
@override
_TapboxAState createState() => new _TapboxAState();
}
class _TapboxAState extends State<TapboxA> {
bool _active = false;
void _handleTap() {
setState(() {
_active = !_active;
});
}
Widget build(BuildContext context) {
return new GestureDetector(
onTap: _handleTap,
child: new Container(
child: new Center(
child: new Text(
_active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: _active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
//------------------------- MyApp ----------------------------------
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Flutter Demo'),
),
body: new Center(
child: new TapboxA(),
),
),
);
}
}
Dart代码:lib/main.dart
对于父窗口小部件来说,管理状态并告诉其子窗口小部件何时更新通常是最有意义的。 例如,IconButton允许您将图标视为可点按的按钮。 IconButton是一个无状态的小部件,因为我们认为父部件需要知道该按钮是否已被轻敲,所以它可以采取适当的行动。
在以下示例中,TapboxB通过回调将其状态导出到其父项。 由于TapboxB不管理任何状态,因此它的子类为无状态部件。
ParentWidgetState类:
TapboxB类:
// ParentWidget manages the state for TapboxB.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => new _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
TapboxB({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return new GestureDetector(
onTap: _handleTap,
child: new Container(
child: new Center(
child: new Text(
active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
Dart代码:lib/main.dart
提示:创建API时,请考虑对代码所依赖的任何参数使用@required注释。 要使用@required,请导入foundation库(该库重新导出Dart的meta.dart库): import ”package:flutter/foundation.dart“;
对于一些小部件来说,混合搭配的方法最有意义。 在这种情况下,有状态小部件管理一些状态,并且父小部件管理状态的其它方面。
在TapboxC示例中,按下时,框的周围会出现一个深绿色的边框。 抬起时,边框消失,框的颜色改变。 TapboxC将其_active状态导出到其父项,但在内部管理_highlight状态。 这个例子有两个状态对象_ParentWidgetState和_TapboxCState。
_ParentWidgetState对象:
_TapboxCState对象:
//---------------------------- ParentWidget ----------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => new _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxC(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//----------------------------- TapboxC ------------------------------
class TapboxC extends StatefulWidget {
TapboxC({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
_TapboxCState createState() => new _TapboxCState();
}
class _TapboxCState extends State<TapboxC> {
bool _highlight = false;
void _handleTapDown(TapDownDetails details) {
setState(() {
_highlight = true;
});
}
void _handleTapUp(TapUpDetails details) {
setState(() {
_highlight = false;
});
}
void _handleTapCancel() {
setState(() {
_highlight = false;
});
}
void _handleTap() {
widget.onChanged(!widget.active);
}
Widget build(BuildContext context) {
// This example adds a green border on tap down.
// On tap up, the square changes to the opposite state.
return new GestureDetector(
onTapDown: _handleTapDown, // Handle the tap events in the order that
onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
onTap: _handleTap,
onTapCancel: _handleTapCancel,
child: new Container(
child: new Center(
child: new Text(widget.active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0, color: Colors.white)),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color:
widget.active ? Colors.lightGreen[700] : Colors.grey[600],
border: _highlight
? new Border.all(
color: Colors.teal[700],
width: 10.0,
)
: null,
),
),
);
}
}
备用实现可能会将高亮状态导出到父级,同时在内部保持活动状态,但如果你问某人使用那个水龙头盒,他们可能会抱怨说这没有什么意义。 开发人员会关心该框是否处于活动状态。开发人员可能不在乎突出显示是如何管理的,并且倾向于轻敲框处理这些细节。
Dart代码:lib/main.dart
Flutter提供各种按钮和类似的交互式小部件。 这些小部件中的大多数实现了Material Design指南,它们定义了一组具有自认UI的组件。
如果你愿意,你可以使用GestureDetector来建立任何自定义小部件的交互性。 您可以在管理状态和Flutter图库中找到GestureDetector的示例。
注意:Flutter还提供了一组名为Cupertino的iOS风格的小部件。
当你需要交互性时,最容易使用预制的小部件之一。 这是一个部分列表:
标准小部件:
材料组件:
将交互添加到您的应用时,以下资源可能会有所帮助。