Flutter 添加交互
创建一个有状态的widget
- 要创建一个自定义有状态widget,需创建两个类:StatefulWidget和State
- 状态对象包含widget的状态和build() 方法。
- 当widget的状态改变时,状态对象调用setState(),告诉框架重绘widget
在本节中,您将创建一个自定义有状态的widget。 您将使用一个自定义有状态widget来替换两个无状态widget - 红色实心星形图标和其旁边的数字计数 - 该widget用两个子widget管理一行:IconButton和Text。
实现一个自定义的有状态widget需要创建两个类:
- 定义一个widget类,继承自StatefulWidget.
- 包含该widget状态并定义该widget build()方法的类,它继承自State.
本节展示如何为Lakes应用程序构建一个名为FavoriteWidget的StatefulWidget。第一步是选择如何管理FavoriteWidget的状态。
Step 1: 决定哪个对象管理widget的状态
Widget的状态可以通过多种方式进行管理,但在我们的示例中,widget本身(FavoriteWidget)将管理自己的状态。 在这个例子中,切换星形图标是一个独立的操作,不会影响父窗口widget或其他用户界面,因此该widget可以在内部处理它自己的状态。
Step 2: 创建StatefulWidget子类
FavoriteWidget类管理自己的状态,因此它重写createState()来创建状态对象。 框架会在构建widget时调用createState()。在这个例子中,createState()创建_FavoriteWidgetState的实例,您将在下一步中实现该实例。
class FavoriteWidget extends StatefulWidget {
@override
_FavoriteWidgetState createState() => new _FavoriteWidgetState();
}
注意: 以下划线(_)开头的成员或类是私有的。有关更多信息,请参阅Dart语言参考中的库和可见性部分 。
Step 3: 创建State子类
自定义State类存储可变信息 - 可以在widget的生命周期内改变逻辑和内部状态。 当应用第一次启动时,用户界面显示一个红色实心的星星形图标,表明该湖已经被收藏,并有41个“喜欢”。状态对象存储这些信息在_isFavorited和_favoriteCount变量。
状态对象也定义了build方法。此build方法创建一个包含红色IconButton和Text的行。 该widget使用IconButton(而不是Icon), 因为它具有一个onPressed属性,该属性定义了处理点击的回调方法。IconButton也有一个icon的属性,持有Icon。
按下IconButton时会调用_toggleFavorite()方法,然后它会调用setState()。 调用setState()是至关重要的,因为这告诉框架,widget的状态已经改变,应该重绘。 _toggleFavorite在: 1)实心的星形图标和数字“41” 和 2)虚心的星形图标和数字“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'),
),
),
],
);
}
}
提示: 当文本在40和41之间变化时,将文本放在SizedBox中并设置其宽度可防止出现明显的“跳跃” ,因为这些值具有不同的宽度。
Step 4: 将有stateful widget插入widget树中
将您自定义stateful widget在build方法中添加到widget树中。首先,找到创建图标和文本的代码,并删除它:
// ...
new Icon(
Icons.star,
color: Colors.red[500],
),
new Text('41')
// ...
在相同的位置创建stateful widget:
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(
// ...
);
}
}
管理状态
- 有多种方法可以管理状态.
- 选择使用何种管理方法
- 如果不是很清楚时, 那就在父widget中管理状态吧.
谁管理着stateful widget的状态?widget本身?父widget?都会?另一个对象?答案是……这取决于实际情况。 有几种有效的方法可以给你的widget添加互动。作为小部件设计师。以下是管理状态的最常见的方法:
- widget管理自己的state
- 父widget管理 widget状态
- 混搭管理(父widget和widget自身都管理状态))
如何决定使用哪种管理方法?以下原则可以帮助您决定:
- 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父widget管理
- 如果所讨论的状态是有关界面外观效果的,例如动画,那么状态最好由widget本身来管理.
如果有疑问,首选是在父widget中管理状态
我们将通过创建三个简单示例来举例说明管理状态的不同方式:TapboxA、TapboxB和TapboxC。 这些例子功能是相似的 - 每创建一个容器,当点击时,在绿色或灰色框之间切换。 _active确定颜色:绿色为true,灰色为false。
widget管理自己的状态
有时,widget在内部管理其状态是最好的。例如, 当ListView的内容超过渲染框时, ListView自动滚动。大多数使用ListView的开发人员不想管理ListView的滚动行为,因此ListView本身管理其滚动偏移量。
_TapboxAState 类:
- 管理TapboxA的状态.
- 定义_active:确定盒子的当前颜色的布尔值.
- 定义_handleTap()函数,该函数在点击该盒子时更新_active,并调用setState()更新UI.
- 实现widget的所有交互式行为.
// TapboxA 管理自身状态.
//------------------------- 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(),
),
),
);
}
}
父widget管理widget的state
对于父widget来说,管理状态并告诉其子widget何时更新通常是最有意义的。 例如,IconButton允许您将图标视为可点按的按钮。 IconButton是一个无状态的小部件,因为我们认为父widget需要知道该按钮是否被点击来采取相应的处理。
在以下示例中,TapboxB通过回调将其状态导出到其父项。由于TapboxB不管理任何状态,因此它的父类为StatelessWidget。
ParentWidgetState 类:
- 为TapboxB 管理_active状态.
- 实现_handleTapboxChanged(),当盒子被点击时调用的方法.
- 当状态改变时,调用setState()更新UI.
TapboxB 类:
- 继承StatelessWidget类,因为所有状态都由其父widget处理.
- 当检测到点击时,它会通知父widget.
// ParentWidget 为 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],
),
),
);
}
}
提示: 在创建API时,请考虑使用@required为代码所依赖的任何参数使用注解。
'package: flutter/foundation.dart';
混合管理
对于一些widget来说,混搭管理的方法最有意义的。在这种情况下,有状态widget管理一些状态,并且父widget管理其他状态。
在TapboxC示例中,点击时,盒子的周围会出现一个深绿色的边框。点击时,边框消失,盒子的颜色改变。 TapboxC将其_active状态导出到其父widget中,但在内部管理其_highlight状态。这个例子有两个状态对象_ParentWidgetState和_TapboxCState。
_ParentWidgetState 对象:
- 管理_active 状态.
- 实现 _handleTapboxChanged(), 当盒子被点击时调用.
- 当点击盒子并且_active状态改变时调用setState()更新UI
_TapboxCState 对象:
- 管理_highlight state.
- GestureDetector监听所有tap事件。当用户点下时,它添加高亮(深绿色边框);当用户释放时,会移除高亮。
- 当按下、抬起、或者取消点击时更新_highlight状态,调用setState()更新UI。
- 当点击时,将状态的改变传递给父widget.
//---------------------------- 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,
),
),
);
}
}
另一种实现可能会将高亮状态导出到父widget,同时保持_active状态为内部,但如果您要求某人使用该TapBox,他们可能会抱怨说没有多大意义。 开发人员只会关心该框是否处于活动状态。开发人员可能不在乎高亮显示是如何管理的,并且倾向于让TapBox处理这些细节。
其他交互式widgets
Flutter提供各种按钮和类似的交互式widget。这些widget中的大多数实现了Material Design 指南, 它们定义了一组具有质感的UI组件。
如果你愿意,你可以使用GestureDetector来给任何自定义widget添加交互性。 您可以在管理状态和Flutter Gallery中找到GestureDetector的示例。
注意: Futter还提供了一组名为Cupertino的iOS风格的小部件 。
When you need interactivity, it’s easiest to use one of the prefabricated widgets. Here’s a partial list: 当你需要交互性时,最容易的是使用预制的widget。这是预置widget部分列表:
更多建议: