Flutter进阶—创建有状态控件

119 篇文章 9 订阅
82 篇文章 1417 订阅

Flutter进阶—构建布局实例展示了如何创建以下布局。

这里写图片描述

当应用程序首次启动时,这颗实心星标是红色的,表明这个景点曾经被收藏过。实心星标旁边的人数表明,有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,
          )
      )
    );
  }
}

实际我们也可以将高亮状态导出到父级,同时保持内部活动状态,但是如果您要求别人使用该点击框,别人可能会抱怨说它没有任何意义。开发人员关心该点击框是否处于活动状态,但开发人员可不在乎如何高亮显示,并且更喜欢点击框自己处理这些细节。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何小有

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值