前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第130期:flutter的状态组件和状态管理

第130期:flutter的状态组件和状态管理

作者头像
terrence386
发布2023-02-25 17:05:24
1.5K0
发布2023-02-25 17:05:24
举报

我们在看电影的时候,往往只关注某个主演的角色,其实那些小角色的表演,远远比主演角色的表演要丰富~

场景

怎样才能在我们的flutter应用中对用户输入做出响应?比如我们有个图标,我们想让它支持点击事件,或者在状态改变的时候换一个不同的图标。

其实我们可以创建一个有状态的组件来控制或管理那些需要变化的组件。

状态组件 VS 无状态组件

这两个概念在react中我们非常熟悉,状态组件内部定义的有自己的属性,可以用来控制不同状态下展示不同的界面。无状态组件则只负责展示界面,没有其他的多余功能。

在flutter中无状态组件有很多,比如:Icon, IconButton, and Text。他们继承StatelessWidget类。

状态组件stateful widget则是动态的:例如,它可以响应用户交互触发的事件或接收数据时更改其外观。Checkbox, Radio, Slider, InkWell, Form, and TextField其实都是状态组件,他们继承了StateulWidget类。

回想一下web端的开发,其实大同小异。

组件的状态存储在state对象中,将控件的状态与其外观分开。状态由可以更改的值组成,例如滑块的当前值或是否选中复选框。当小部件的状态发生变化时,状态对象调用setState(),告诉框架重新绘制小部件。

创建状态组件

需要注意的是:

代码语言:javascript
复制
/**
1.  状态组件件由两个类实现:StatefulWidget的子类和State的子类。
2.  state类包含组件的可变状态和组件的build()方法。
3.  当组件状态发生变化时,state对象调用setstate方法,通知框架重新绘制组件。
**/   

创建一个自定义的状态组件需要创建两个类:

代码语言:javascript
复制
/**
1.  StatefulWidget类 用来定义组件。
2.  包含组件状态的State类, 用来定义组件的build方法。
**/   

先继承StatefulWidget类,定义组件:

代码语言:javascript
复制
class FavoriteWidget extends StatefulWidget {
  const FavoriteWidget({super.key});

  @override
  State<FavoriteWidget> createState() => _FavoriteWidgetState();
}

再扩展State类定义状态:

代码语言:javascript
复制
class _FavoriteWidgetState extends State<FavoriteWidget> {
  bool _isFavorited = true;
  int _favoriteCount = 41;

  // ···
}

然后实现build()方法实现组件展示:

代码语言:javascript
复制
class _FavoriteWidgetState extends State<FavoriteWidget> {
  // ···
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Container(
          padding: const EdgeInsets.all(0),
          child: IconButton(
            padding: const EdgeInsets.all(0),
            alignment: Alignment.centerRight,
            icon: (_isFavorited
                ? const Icon(Icons.star)
                : const Icon(Icons.star_border)),
            color: Colors.red[500],
            onPressed: _toggleFavorite,
          ),
        ),
        SizedBox(
          width: 18,
          child: SizedBox(
            child: Text('$_favoriteCount'),
          ),
        ),
      ],
    );
  }
}

然后实现 _toggleFavorite方法用来更新状态:

代码语言:javascript
复制
void _toggleFavorite() {
  setState(() {
    if (_isFavorited) {
      _favoriteCount -= 1;
      _isFavorited = false;
    } else {
      _favoriteCount += 1;
      _isFavorited = true;
    }
  });
}

这样就简单的实现了一个状态组件。

状态管理

需要注意的内容:

代码语言:javascript
复制
/**
1.  管理状态有不同的方法。
2.  作为组件的开发者,我们可以选择具体使用哪种方法。
3.  如果我们不确定怎么管理状态,就把状态放到父组件中进行管理。
**/   

到底是谁在负责状态的管理呢?组件本身?父组件?或者有个更高级的组件?其实是根据情况而定的。

根据实际情况进行状态管理是一种最有效的方法,以下是管理状态的最常见方法:

  • 组件自身控制自己的状态
  • 父组件控制子组件的状态
  • 混合状态控制

我们该怎么选择呢?建议如下:

代码语言:javascript
复制
/**
1.  如果所讨论的状态是用户数据,例如复选框的选中或未选中模式,或者滑块的位置,那么状态最好由父组件管理。
2.  如果所讨论的状态是美学的,例如动画,那么状态最好由组件自身管理。
**/ 

组件管理自己的状态

有时候,组件在内部管理自己状态比较好。例如,当ListView的内容超过渲染框时,它会自动滚动。大多数使用ListView的开发人员不想管理ListView的滚动行为,所以就让ListView本身管理其滚动偏移量。

举个例子,看下面的代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

// TapboxA manages its own state.

//------------------------- TapboxA ----------------------------------

class TapboxA extends StatefulWidget {
  const TapboxA({super.key});

  @override
  State<TapboxA> createState() => _TapboxAState();
}

class _TapboxAState extends State<TapboxA> {
  bool _active = false;

  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
        child: Center(
          child: Text(
            _active ? 'Active' : 'Inactive',
            style: const TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
      ),
    );
  }
}

//------------------------- MyApp ----------------------------------

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Demo'),
        ),
        body: const Center(
          child: TapboxA(),
        ),
      ),
    );
  }
}
  • 组件TapboxA管理自己的状态_active
  • 状态_active用来控制组件的颜色
  • _handleTap方法调用setState来更新组件的展示

父组件管理状态

通常情况下,父组件管理状态并通知其子组件何时更新是最有意义的。例如,IconButton可以让图标看作是可点击的按钮。IconButton是一个无状态的小部件,因为我们可以让父组件知道按钮是否被点击,以便采取适当的操作。

看下面的代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

// ParentWidget manages the state for TapboxB.

//------------------------ ParentWidget --------------------------------

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      child: TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
  const TapboxB({
    super.key,
    this.active = false,
    required this.onChanged,
  });

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
        child: Center(
          child: Text(
            active ? 'Active' : 'Inactive',
            style: const TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
      ),
    );
  }
}
  • 父组件定义了状态_active用来控制组件TapboxB的展示
  • 父组件定义了_handleTapboxChanged,当组件TapboxB被点击的时候会更新_active
  • 子组件TapboxB接受active属性,同时定义了onChanged属性方法,当点击子组件TapboxB时,会触发父组件的_handleTapboxChanged方法,通知父组件,从而实现组件的更新。

混合状态管理

对于其他的一些组件件,混合使用混合状态管理最有意义。在这个场景中,状态组件管理自己的一些状态,而父组件管理状态的其他方面。

看下面的代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

//---------------------------- ParentWidget ----------------------------

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      child: TapboxC(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//----------------------------- TapboxC ------------------------------

class TapboxC extends StatefulWidget {
  const TapboxC({
    super.key,
    this.active = false,
    required this.onChanged,
  });

  final bool active;
  final ValueChanged<bool> onChanged;

  @override
  State<TapboxC> createState() => _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);
  }

  @override
  Widget build(BuildContext context) {
    // This example adds a green border on tap down.
    // On tap up, the square changes to the opposite state.
    return GestureDetector(
      onTapDown: _handleTapDown, // Handle the tap events in the order that
      onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
      onTap: _handleTap,
      onTapCancel: _handleTapCancel,
      child: Container(
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
          border: _highlight
              ? Border.all(
                  color: Colors.teal[700]!,
                  width: 10.0,
                )
              : null,
        ),
        child: Center(
          child: Text(widget.active ? 'Active' : 'Inactive',
              style: const TextStyle(fontSize: 32.0, color: Colors.white)),
        ),
      ),
    );
  }
}

在这个示例中,按下时,框周围会出现一个深绿色边框。松手时,边框消失,框的颜色改变。组件TapboxC将其活动状态导出到其父组件,但在自身内部管理其高亮状态。此示例有两个State对象,_ParentWidgetState_TapboxCState

_ParentWidgetState对象:

  • 管理活动状态_active
  • 实现了_handleTapboxChanged()方法,即在轻敲框时调用的方法。
  • 调用setState()以在轻敲发生且_active状态更改时更新UI。

_TapboxCState对象:

  • 管理自身状态_highlight
  • GestureDetector组件监听onTapDownonTapUp事件。onTapDown时,它会添加高亮显示(实现为深绿色边框)。onTapUp时,它会删除高亮显示。
  • onTapDownonTapUp调用setState()方法更新UI,并且_higlight状态发生变化。
  • _handleTap时,将状态传递到付组件中,通知父组件进行更新。

最后

在组件的状态管理中,我们使用的最多的交互场景大抵是表单相关的内容,相关的组件有:

  • Form
  • FormField
  • Checkbox
  • DropdowmButton
  • TextButton
  • FloatingActionButton
  • IconButton
  • Radio
  • ElevateButton
  • Slider
  • Switch
  • TextField
  • ...

和web开发使用的场景差不多~

我们在进行组件的封装时,本质上是在开发一个自定义的状态组件~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-12-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JavaScript高级程序设计 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 我们在看电影的时候,往往只关注某个主演的角色,其实那些小角色的表演,远远比主演角色的表演要丰富~
  • 场景
  • 状态组件 VS 无状态组件
  • 创建状态组件
  • 状态管理
  • 组件管理自己的状态
  • 父组件管理状态
  • 混合状态管理
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档