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,可以根据自己项目的复杂程度进行选择。

相关推荐
leluckys18 小时前
flutter 专题 六十一 支持上拉加载更多的自定义横向滑动表格
flutter
UzumakiHan1 天前
flutter开发音乐APP(简单的音乐播放demo)
flutter
leluckys1 天前
flutter 专题 六十四 在原生项目中集成Flutter
flutter
leluckys1 天前
flutter 专题 一百零四 Flutter环境搭建
flutter
唯鹿1 天前
AI生成Flutter UI代码实践(一)
人工智能·flutter·ui
仙魁XAN1 天前
Flutter 学习之旅 之 flutter 有时候部分手机【TextField】无法唤起【输入法软键盘】的一些简单整理
flutter·unity·华为手机·textfield·键盘唤不起
leluckys1 天前
flutter 专题 五十八 关于Flutter提示Your Xcode project requires migration的错误
flutter
只可远观2 天前
Flutter Dart中的函数参数 默函数的定义 可选参数 箭头函数 匿名函认参数 命名参类数 闭包等
windows·flutter
JarvanMo2 天前
借助FlutterFire CLI实现Flutter与Firebase的多环境配置
前端·flutter
RichardLai882 天前
[Flutter 基础] - Flutter基础组件 - Icon
android·flutter