美文网首页
Flutter学习 - 状态管理篇

Flutter学习 - 状态管理篇

作者: handyTOOL | 来源:发表于2023-10-25 16:26 被阅读0次

    前言

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

    案例

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

    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

    class _SegmentHeaderState extends State<SegmentHeader> {
      var selectedIndex = -1;
    
      @override
      Widget build(BuildContext context) {
        ...
    

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

    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改变了

    class SegmentHeader extends StatefulWidget {
      final Function(int, Color)? tabSelectedChanged;
    
      const SegmentHeader({super.key, this.tabSelectedChanged});
    
      @override
      State<StatefulWidget> createState() => _SegmentHeaderState();
    }
    
    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整合到页面中,并控制页面的颜色

    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来改造上面的案例。首先添加依赖

    dependencies:
      provider: ^6.0.0
    

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

    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

    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来生产模型实例

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

    相关文章

      网友评论

          本文标题:Flutter学习 - 状态管理篇

          本文链接:https://www.haomeiwen.com/subject/olieidtx.html