Flutter进阶—创建有状态控件
2017-05-12 14:30
351 查看
Flutter进阶—构建布局实例展示了如何创建以下布局。
当应用程序首次启动时,这颗实心星标是红色的,表明这个景点曾经被收藏过。实心星标旁边的人数表明,有66人喜欢这个景点。现在需要完成一个任务,点击该实心星标删除其收藏的状态,用空心星标取代实心星标并减少收藏人数。点击再次收藏景点,画一颗实心星标,增加收藏人数。
要完成此操作,需要创建一个包含Icon(图标)和Text(文本)的自定义控件,Icon(图标)和Text(文本)本身就是控件。因为点击Icon(图标)会改变这两个控件的状态,所以自定义控件应该同时管理两者。
stateless(无状态)控件没有内部状态来管理。Icon、IconButton和Text是无状态控件的示例,它是StatelessWidget的子类。
stateful(有状态)控件是动态的。用户可以与有状态控件(比如通过键入内容或移动滑块)进行交互,或者随着时间的推移而变化(比如数据Feed会导致UI更新)。Checkbox、Radio、Slider、InkWell、Form和TextField是有状态小部件的示例,它们是StatefulWidget的子类。
实现自定义的有状态控件需要创建两个类
StatefulWidget的子类定义控件。
包含该控件的状态并定义控件的build()方法的State的子类。
状态对象还定义了build(),build()创建一行包含红色IconButton和Text。该控件使用IconButton,而不是Icon,因为它具有一个onPressed属性,该属性定义了用于处理点击的回调方法。IconButton也有一个icon 属性保存图标。
_toggleFavorite()方法,当按下IconButton时调用,调用setState()。调用setState()是至关重要的,因为它告诉框架控件的状态已经改变,控件应该重新绘制。
在同一位置,创建有状态控件:
如果要管理的状态是用户数据,例如复选框的勾选、未选中的模式,或者滑块的位置,则状态最好由父控件管理。
如果要管理的状态是美观效果,例如动画,则状态最好由控件本身管理。
我们将通过创建三个简单的示例来演示管理状态的不同方式:TapboxA、TapboxB和TapboxC。这些例子都是类似的,每个都创建一个容器,当点击时,在一个绿色或灰色框之间切换。布尔值_active确定颜色,绿色为活动、灰色为非活动。
_TapboxAState类:
管理TapboxA的状态。
定义确定容器当前颜色的布尔值_active。
定义_handleTap()函数,当该容器被点击时,该函数会更新_active,并调用setState()函数来更新用户界面。
实现控件的所有交互式行为。
在以下示例中,TapboxB通过回调将其状态导出到其父级。因为TapboxB不管理任何状态,它会对StatelessWidget进行子类化。
ParentWidgetState类:
管理TapboxB的_active状态。
实现_handleTapboxChanged(),当容器被点击时调用的方法。
当状态改变时,调用setState()来更新用户界面。
TapboxB类:
扩展StatelessWidget,因为所有状态都由其父进程处理。
当检测到点击时,它会通知父级。
在TapboxC示例中,点击按钮,点击框的周围会出现一个深绿色的边框。不点按钮,边框消失。 TapboxC将其_active状态导出到其父级,但在内部管理其_highlight状态。此示例有两个状态对象 _ParentWidgetState和_TapboxCState。
_ParentWidgetState对象
管理_active状态。
实现_handleTapboxChanged(),当点击框被点击时调用的方法。
调用setState()以在点击发生时更新用户界面,并且_active状态更改。
_TapboxCState对象
管理_highlight状态。
GestureDetector侦听所有点击事件。随着用户点击,它增加了高亮,即深绿色边框。当用户释放点击时,它会删除高亮。
调用setState()通过轻按、点按或点击取消来更新用户界面,而且_highlight状态更改。
在点击事件上,将状态更改传递给父控件,以使用widget属性采取适当的操作。
实际我们也可以将高亮状态导出到父级,同时保持内部活动状态,但是如果您要求别人使用该点击框,别人可能会抱怨说它没有任何意义。开发人员关心该点击框是否处于活动状态,但开发人员可不在乎如何高亮显示,并且更喜欢点击框自己处理这些细节。
当应用程序首次启动时,这颗实心星标是红色的,表明这个景点曾经被收藏过。实心星标旁边的人数表明,有66人喜欢这个景点。现在需要完成一个任务,点击该实心星标删除其收藏的状态,用空心星标取代实心星标并减少收藏人数。点击再次收藏景点,画一颗实心星标,增加收藏人数。
要完成此操作,需要创建一个包含Icon(图标)和Text(文本)的自定义控件,Icon(图标)和Text(文本)本身就是控件。因为点击Icon(图标)会改变这两个控件的状态,所以自定义控件应该同时管理两者。
有状态和无状态的控件
有状态和无状态的控件stateless(无状态)控件没有内部状态来管理。Icon、IconButton和Text是无状态控件的示例,它是StatelessWidget的子类。
stateful(有状态)控件是动态的。用户可以与有状态控件(比如通过键入内容或移动滑块)进行交互,或者随着时间的推移而变化(比如数据Feed会导致UI更新)。Checkbox、Radio、Slider、InkWell、Form和TextField是有状态小部件的示例,它们是StatefulWidget的子类。
创建有状态控件
创建一个自定义的有状态控件,管理具有IconButton和Text两个子控件的行,用来替换两个无状态控件,实心红色星标和数字计数。实现自定义的有状态控件需要创建两个类
StatefulWidget的子类定义控件。
包含该控件的状态并定义控件的build()方法的State的子类。
第一步:决定哪个对象管理控件的状态
控件的状态可以通过多种方式进行管理,但在下面的示例中,控件本身,FavoriteWidget将管理自己的状态。在这个例子中,切换星标是一个独立的动作,不会影响父控件或用户界面的其余部分,所以该控件可以在内部处理其状态。第二步:子类StatefulWidget
FavoriteWidget类管理自己的状态,所以它覆盖createState()来创建State对象,框架在构建控件时调用createState()。在这个例子中,createState()创建一个_FavoriteWidgetState的实例。class FavoriteWidget extends StatefulWidget { @override _FavoriteWidgetState createState() => new _FavoriteWidgetState(); }
第三步:子类State
自定义State类存储可变信息(可在控件的整个生命周期内更改的逻辑和内部状态),当应用程序首次启动时,用户界面将显示一个实心的红色星标,表示该景点有“收藏”状态,并有66“收藏”。状态对象将此信息存储在_isFavorited和_favoriteCount变量中。状态对象还定义了build(),build()创建一行包含红色IconButton和Text。该控件使用IconButton,而不是Icon,因为它具有一个onPressed属性,该属性定义了用于处理点击的回调方法。IconButton也有一个icon 属性保存图标。
_toggleFavorite()方法,当按下IconButton时调用,调用setState()。调用setState()是至关重要的,因为它告诉框架控件的状态已经改变,控件应该重新绘制。
class _FavoriteWidgetState extends State<FavoriteWidget> { bool _isFavorited = true; int _favoriteCount = 66; void _toggleFavorite() { setState(() { // 如果景点目前被收藏 if(_isFavorited) { _favoriteCount -= 1; _isFavorited = false; // 景点未被收藏 } 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'), ) ) ] ); } }
第四步:将有状态控件插入控件树
在应用程序的构建方法中将您的自定义有状态控件添加到用户界面。首先找到创建图标和文本的代码,然后将其删除:// ... new Icon( Icons.star, color: Colors.red[500], ), new Text('66') // ...
在同一位置,创建有状态控件:
class _MyHomePageState extends State<MyHomePage> { // ... @override Widget build(BuildContext context) { Widget titleSection = new Container( // ... child: new Row(children: [ new Expanded( child: new Column( // ... ), new FavoriteWidget(), ] ) ); // ... }
管理状态
通过以下原则可以帮助您决定如何管理状态如果要管理的状态是用户数据,例如复选框的勾选、未选中的模式,或者滑块的位置,则状态最好由父控件管理。
如果要管理的状态是美观效果,例如动画,则状态最好由控件本身管理。
我们将通过创建三个简单的示例来演示管理状态的不同方式:TapboxA、TapboxB和TapboxC。这些例子都是类似的,每个都创建一个容器,当点击时,在一个绿色或灰色框之间切换。布尔值_active确定颜色,绿色为活动、灰色为非活动。
控件管理自己的状态
有时,这个控件最有意义的是在内部管理它的状态。例如,当ListView的内容超过渲染框时,ListView会自动滚动。大多数使用ListView的开发人员不想管理ListView的滚动行为,因此ListView本身管理其滚动偏移。_TapboxAState类:
管理TapboxA的状态。
定义确定容器当前颜色的布尔值_active。
定义_handleTap()函数,当该容器被点击时,该函数会更新_active,并调用setState()函数来更新用户界面。
实现控件的所有交互式行为。
//------------------------- 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 ? '有效的' : '无效的', 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(), ) ) ); } }
父控件管理控件的状态
通常情况下,父控件最大的意义的是管理状态,并在更新时告诉其子控件。例如,IconButton允许您将图标作为可点击按钮。IconButton是一个无状态的控件,因为我们决定父控件需要知道按钮是否被点击,所以可以采取适当的措施。在以下示例中,TapboxB通过回调将其状态导出到其父级。因为TapboxB不管理任何状态,它会对StatelessWidget进行子类化。
ParentWidgetState类:
管理TapboxB的_active状态。
实现_handleTapboxChanged(),当容器被点击时调用的方法。
当状态改变时,调用setState()来更新用户界面。
TapboxB类:
扩展StatelessWidget,因为所有状态都由其父进程处理。
当检测到点击时,它会通知父级。
//------------------------ 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, // import 'package:flutter/foundation.dart'; @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 ? '有效的' : '无效的', 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], ) ) ); } }
混合管理状态的方法
对于一些控件,混合管理状态是很有必要的。在这种情况下,状态控件管理某些状态,父控件管理状态的其他方面。在TapboxC示例中,点击按钮,点击框的周围会出现一个深绿色的边框。不点按钮,边框消失。 TapboxC将其_active状态导出到其父级,但在内部管理其_highlight状态。此示例有两个状态对象 _ParentWidgetState和_TapboxCState。
_ParentWidgetState对象
管理_active状态。
实现_handleTapboxChanged(),当点击框被点击时调用的方法。
调用setState()以在点击发生时更新用户界面,并且_active状态更改。
_TapboxCState对象
管理_highlight状态。
GestureDetector侦听所有点击事件。随着用户点击,它增加了高亮,即深绿色边框。当用户释放点击时,它会删除高亮。
调用setState()通过轻按、点按或点击取消来更新用户界面,而且_highlight状态更改。
在点击事件上,将状态更改传递给父控件,以使用widget属性采取适当的操作。
//---------------------------- ParentWidget ---------------------------- 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, // import 'package:flutter/foundation.dart'; @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) { return new GestureDetector( onTapDown: _handleTapDown, onTapUp: _handleTapUp, onTap: _handleTap, onTapCancel: _handleTapCancel, child: new Container( child: new Center( child: new Text( widget.active ? '有效的' : '无效的', 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, ) ) ); } }
实际我们也可以将高亮状态导出到父级,同时保持内部活动状态,但是如果您要求别人使用该点击框,别人可能会抱怨说它没有任何意义。开发人员关心该点击框是否处于活动状态,但开发人员可不在乎如何高亮显示,并且更喜欢点击框自己处理这些细节。
相关文章推荐
- Flex如何创建状态States并掌握几个常用控件用法
- wince控件之自绘按钮(支持多种状态的图像加载、可通过图像创建不规程按钮)
- 动态创建ASP.NET控件并保持其状态分析及实现
- asp.net 动态创建TextBox控件 如何加载状态信息
- 五种情况下会刷新控件状态(刷新所有子FWinControls的显示)——从DFM读取数据时、新增加子控件时、重新创建当前控件的句柄时、设置父控件时、显示状态被改变时
- Flex如何创建状态States并掌握几个常用控件用法
- 在代码中动态创建控件无法保存状态的问题
- Flutter进阶—布局一个控件
- 黑马程序员——iOS开发进阶教程——用代码创建控件
- 动态创建的控件不能保存视图状态
- asp.net 动态创建TextBox控件 如何加载状态信息
- Flutter进阶—通用布局控件
- 思归的“动态控件的状态问题”的分析
- 使用 ASP+ DataGrid 控件来创建主视图/详细资料视图
- 一个分页跟动态创建列的控件
- 只在必要时保存服务器控件视图状态
- 创建一个ActiveX 控件
- 创建用于ASP.NET的分页程序控件
- 利用 ASP.NET 2.0 创建自定义 Web 控件
- 利用 ASP.NET 2.0 创建自定义 Web 控件