Flutter学习 - 状态管理篇

前言

对于UI而言,最基础的就是展示数据,刷新数据,Flutter提供了一套状态管理机制来做这些事情

案例

通过一个案例来解释Flutter的状态管理机制是如何运作的,假设我们需要实现一个SegmentHeader,通过点击不同的按钮改变页面的背景色。 第一步创建一个继承自StatefulWidgetSegmentHeader类,同时创建_SegmentHeaderState

scala 复制代码
class SegmentHeader extends StatefulWidget {
  const SegmentHeader({super.key});

  @override
  State<StatefulWidget> createState() => _SegmentHeaderState();
}

class _SegmentHeaderState extends State<SegmentHeader> {
  @override
  Widget build(BuildContext context) {
    ...
  }
}

Flutter中Widget会随着每次更新重新创建,但是State会保留,所以状态字段都会放在State类中,目前需要保存的状态就是用户点击了那个Segment

scala 复制代码
class _SegmentHeaderState extends State<SegmentHeader> {
  var selectedIndex = -1;

  @override
  Widget build(BuildContext context) {
    ...

_SegmentHeaderState中定义一个selectedIndex表示用户选择了哪个SegmentTab,接下来增加几个tab,并响应点击事件

less 复制代码
class _SegmentHeaderState extends State<SegmentHeader> {
  var selectedIndex = -1;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(child: buildItem(0, "Red", Colors.red)),
        Expanded(child: buildItem(1, "Blue", Colors.blue)),
        Expanded(child: buildItem(2, "Cyan", Colors.cyan)),
        Expanded(child: buildItem(3, "Purple", Colors.purple)),
      ],
    );
  }

  Widget buildItem(int index, String title, Color color) {
    return GestureDetector(
      onTap: () {
        setState(() {
          selectedIndex = index;
        });
      },
      child: Container(
        margin: const EdgeInsets.all(5),
        height: 40,
        alignment: Alignment.center,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(5),
            color: selectedIndex == index ? Colors.amber : Colors.white),
        child: Text(
          title,
          style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
        ),
      ),
    );
  }
}

在tab的点击事件中,通过setState修改selectedIndex的值,并且通过判断selectedIndex的值是否与当前tab的index相等来高亮选中的tab。最后再增加一个回调,告知上层选中的tab改变了

scala 复制代码
class SegmentHeader extends StatefulWidget {
  final Function(int, Color)? tabSelectedChanged;

  const SegmentHeader({super.key, this.tabSelectedChanged});

  @override
  State<StatefulWidget> createState() => _SegmentHeaderState();
}
scala 复制代码
class _SegmentHeaderState extends State<SegmentHeader> {
  var selectedIndex = -1;

...

  Widget buildItem(int index, String title, Color color) {
    return GestureDetector(
      onTap: () {
        setState(() {
          selectedIndex = index;
        });
        if (widget.tabSelectedChanged != null) {
          widget.tabSelectedChanged!.call(index, color);
        }
      },
      child: ...
    );
  }
}

将写好的SegmentHeader整合到页面中,并控制页面的颜色

scala 复制代码
class LearnStatefulWidget extends StatefulWidget {
  const LearnStatefulWidget({super.key});
  @override
  State<StatefulWidget> createState() => _LearnStatefulWidgetState();
}

class _LearnStatefulWidgetState extends State<LearnStatefulWidget> {
  Color bgColor = Colors.white;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("基本State管理"),
      ),
      body: Column(
        children: [
          SegmentHeader(
            tabSelectedChanged: (idx, color) {
              setState(() {
                bgColor = color;
              });
            },
          ),
          Expanded(
              child: Container(
            color: bgColor,
          ))
        ],
      ),
    );
  }
}

通过SegmentHeader的回调控制bgColor,从而达到改变颜色的目的

Provider库

在上面的案例中,我们使用系统的setState进行状态管理,但是有个问题,子Widget向父Widget的数据传递只能依靠回调,这样的话层级多了就很容易陷入回调地狱,为了更好的管理状态,我们可以使用三方状态管理库,比如Flutter官方推荐的Provider,接下来我们通过Provider来改造上面的案例。首先添加依赖

yaml 复制代码
dependencies:
  provider: ^6.0.0

接下来定义一个数据模型来存储状态,目前这个页面需要存储的就是当前选择的索引和背景颜色

scala 复制代码
class SegmentPageModel extends ChangeNotifier {
  int selectedIndex = -1;
  Color bgColor = Colors.white;

  void setSelectedIndex(int val) {
    selectedIndex = val;
    notifyListeners();
  }

  void setBgColor(Color val) {
    bgColor = val;
    notifyListeners();
  }
}

这个模型继承了ChangeNotifier,当数据改变时,调用notifyListeners来通知外部,接下来重写SegmentHeader,由于使用了Provider,我们可以将SegmentHeader调整成为StatelessWidget

less 复制代码
class SegmentHeader extends StatelessWidget {
  const SegmentHeader({super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(child: buildItem(0, "Red", Colors.red)),
        Expanded(child: buildItem(1, "Blue", Colors.blue)),
        Expanded(child: buildItem(2, "Cyan", Colors.cyan)),
        Expanded(child: buildItem(3, "Purple", Colors.purple)),
      ],
    );
  }

  Widget buildItem(int index, String title, Color color) {
    return Consumer<SegmentPageModel>(builder: (context, value, child) {
      return GestureDetector(
        onTap: () {
          value.setSelectedIndex(index);
          value.setBgColor(color);
        },
        child: Container(
          margin: const EdgeInsets.all(5),
          height: 40,
          alignment: Alignment.center,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(5),
              color:
                  value.selectedIndex == index ? Colors.amber : Colors.white),
          child: Text(
            title,
            style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
          ),
        ),
      );
    });
  }
}

这里一个重大改变就是需要动态改变的Widget被Consumer包裹起来,顾名思义,Consumer就是消费者的意思,当模型发送改变通知,Consumer就会重新build。在onTap回调里,我们调用setSelectedIndexsetBgColor触发模型的数据更新。现在有了消费者,模型实例还缺少生产的地方,需要通过ChangeNotifierProvider Widget来生产模型实例

less 复制代码
class LearnProvider extends StatelessWidget {
  const LearnProvider({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Provider管理"),
        ),
        body: ChangeNotifierProvider(
          create: (context) => SegmentPageModel(),
          child: Column(
            children: [
              const SegmentHeader(),
              Expanded(
                  child: Consumer<SegmentPageModel>(
                builder: (context, value, child) => Container(
                  color: value.bgColor,
                ),
              ))
            ],
          ),
        ));
  }
}

ChangeNotifierProvider的create中,返回模型实例即可,ChangeNotifierProviderConsumer子Widget都会接收到同一个模型实例。

总结

本篇博客介绍了基础的状态管理方式以及三方状态管理库Provider,当然还有很多其他状态管理库,比如redux,Bloc,可以根据自己项目的复杂程度进行选择。

相关推荐
行者962 小时前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨2 小时前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨2 小时前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨3 小时前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨3 小时前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者964 小时前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
前端不太难4 小时前
Flutter / RN / iOS,在长期维护下的性能差异本质
flutter·ios
小雨下雨的雨4 小时前
Flutter 框架跨平台鸿蒙开发 —— Padding 控件之空间呼吸艺术
flutter·ui·华为·harmonyos·鸿蒙系统
行者965 小时前
Flutter到OpenHarmony:横竖屏自适应布局深度实践
flutter·harmonyos·鸿蒙
小雨下雨的雨5 小时前
Flutter 框架跨平台鸿蒙开发 —— Align 控件之精准定位美学
flutter·ui·华为·harmonyos·鸿蒙