跳转至

状态管理

iOS控件有响应,按钮按下去有状态。

flutter增量渲染,没有必要拥有状态。

复杂的设计,一个页面需要改变的东西多。数据的保留。所以需要有状态的widget。底层中渲染逻辑和数据逻辑分开管理。保留的是数据逻辑,不保留的是渲染逻辑,UI每时每刻都在创建新的,但是数据一直保留。

一般整个页面是StatelessWidget。需要setState(() {});的,需要生命周期dispose()释放资源的,initState()异步请求数据的,就用StatefulWidget

//外部可能会传递数据
class StatefulWidgetDemo extends StatefulWidget {
  const StatefulWidgetDemo({Key? key}) : super(key: key);

  @override
  State<StatefulWidgetDemo> createState() => _StatefulWidgetDemoState();
}
//状态管理者
class _StatefulWidgetDemoState extends State<StatefulWidgetDemo> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

StatefulWidget

  • 继承StatefulWidget,用于对外提供接口。

  • 实现createState方法,管理数据 state状态。

state

定义state类:class _state类名 extends State<哪个StatefulWidget 操纵的哪个>

继承State用来管理状态。

  • 实现build方法,访问数据,数据在state里面。
  • 通过setState设置/改变数据,会重新走一遍build方法,重新渲染整个UI,旧的舍弃掉了,new新的widget
  • 推荐:定义const修饰的widget就不会new。父widget能设置const就设置const,不能设置就设置子widget为const。组件拆分的细一些。

    • 例如:cell非常复杂的时候:把cell的一部分改为有状态的,而不是把整个cell改为有状态的(为了性能考虑)。
    • 不推荐:在build方法外面new一个widget,build里面直接用,就不会重新new。context也用不了build方法中的BuildContext。

StatefulBuilder

StatefulBuilder 用于创建一个局部的可变状态,不需要为了仅仅更新一小部分 UI 而创建一个完整的 StatefulWidget

用处:当在 StatelessWidget 或者对话框中有一些可变状态时。

这里是一个简单的 StatefulBuilder 使用示例,它在一个对话框中使用,允许用户增加一个计数器的值而不需要关闭对话框:

import 'package:flutter/material.dart';

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        showDialog(
          context: context,
          builder: (BuildContext context) {
            int counter = 0; // 初始计数器值

            return AlertDialog(
              title: Text('StatefulBuilder Demo'),
              content: StatefulBuilder(
                builder: (BuildContext context, StateSetter setState) {
                  return Column(
                    mainAxisSize: MainAxisSize.min,
                    children: <Widget>[
                      Text('You have pushed the button this many times:'),
                      Text(
                        '$counter',
                        style: Theme.of(context).textTheme.headline4,
                      ),
                    ],
                  );
                },
              ),
              actions: <Widget>[
                TextButton(
                  child: Text('Increment'),
                  onPressed: () {
                    //counter++;
                    //// 这里我们告诉 StatefulBuilder 需要重建它的状态
                    //(context as Element).markNeedsBuild();
                    setState(() {
                      counter++;
                    });
                  },
                ),
              ],
            );
          },
        );
      },
      child: Text('Show Dialog'),
    );
  }
}

在这个例子中,在 MyHomePage 类中创建了一个按钮,当按下时会显示一个对话框。对话框使用 StatefulBuilder 来创建一个可变的计数器。当用户点击 "Increment" 按钮时,计数器的值会增加,并且通过调用 setState 函数,告诉 StatefulBuilder 需要重建,以便更新显示的计数器值。

注意:上述代码中的 (context as Element).markNeedsBuild(); 是一种强制更新 UI 的方法,推荐的做法是在 StatefulBuilderbuilder 方法中调用 setState 函数来更新状态,告诉 StatefulBuilder 需要重建其子树,从而更新计数器的显示值。

访问和修改外部状态

状态提升,把自己写的组件里面的属性提升到父组件,由父组件传进来值。

如果是别人写的组件就不好实现这种方式。

class Foo extends StatelessWidget {
  //外面传值,子组件访问外面的值
  final int count;
  //修改外部的值
  //利用回传函数
  // final void Function() onPressed;
  final VoidCallback onPressed;
  //回传函数带参数
  final void Function(int) add;
  const Foo({
    Key? key,
    required this.count,
    required this.onPressed,
    required this.add,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      color: Colors.red.withOpacity(0.5),
      margin: const EdgeInsets.all(16),
      child: Column(
        children: [
          const Center(
            child: FlutterLogo(
              size: 50,
            ),
          ),
          Text('$count'),
          ElevatedButton(
            //修改外部的值
            //利用回传函数
            onPressed: () => onPressed(),
            child: const Text("点击按钮,修改count的值"),
          ),
        ],
      ),
    );
  }
}

外部组件控制内部小组件

需要用到控制器。

double是值传递,对象是指针传递。值传递里外不同步。创建一个类,value是double类型的值。

内部组件构造函数参数有一个controller,外部组件controller成员传进来。类似TextField

1、extends一个ChangeNotifier

class AccountDetailListController with ChangeNotifier {
  List _list = [];

  //读数据
  List get list => _list;

    //写数据
  set list(List newList) {
    _list = newList;
    notifyListeners(); //告诉监听者 刷新build
  }

}

2、每当调用了上面的notifyListeners();,如果有监听。则会调用builder。渲染范围尽量缩小,以节约性能。

child: ListenableBuilder(
  listenable: widget.controller, //监听,
  builder: (BuildContext context, Widget? child) {
    return ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return DiscoverCell(
          title: _options[index],
          imageName: defaultImageName,
          onTapCallBack: () {},
        );
      },
      itemCount: _options.length,
    );
  },
),

里面也是状态提升。controller提到了父widget。

状态提升

提到父组件,父级组件的变量通过传参给子组件。

子组件修改父组件通过回传函数。

内部状态复杂的组件,内部状态封装控制器,通过状态提升,把控制器的实例化交给父级组件,通过传参和回调函数来控制控制器,即复杂组件的内部状态。

//新增账套成功之后,当前组件调用`callBack()`,父组件去实现callBack。
class AddAccount extends StatefulWidget {
  final void Function() callBack;

  const AddAccount({Key? key, required this.callBack}) : super(key: key);

  @override
  State<AddAccount> createState() => _AddAccountState();
}

通过一层一层的传参的问题:使用InheritedWidget继承式组件。

InheritedWidget

InheritedWidget继承式组件发生了更新才通知其它依赖的子组件更新。

适用于传的组件树比较深,组件比较多的情况。

在比较大的项目中做状态管理。数据共享。

import 'package:flutter/material.dart';

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    /// 放到树的根 才能共享数据
    return const MyColor(
      color: Colors.yellow,
      child: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    super.key,
    required this.title,
  });

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  Color _color = Colors.blue;

  @override
  Widget build(BuildContext context) {
    return MyColor(
      color: _color,
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text('You have pushed the button this many times:',),
              const Foo(),
              Text(
                '$_counter',
                style: Theme.of(context).textTheme.headlineMedium,
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              _color = Colors.black;
            });
          },
          child: const Icon(Icons.add),
        ), // This trailing comma makes auto-formatting nicer for build methods.
      ),
    );
  }
}

class Foo extends StatelessWidget {
  const Foo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: MyColor.of(context).color,
      // color: MyColor.maybeOf(context)?.color,
    );
  }
}

//class Foo extends StatefulWidget {
//   final int count;
//
//   const Foo({super.key, required this.count});
//
//   @override
//   _FooState createState() => _FooState();
// }
//
// class _FooState extends State<Foo> {
//   @override
//   void didChangeDependencies() {
//     super.didChangeDependencies();
//   }
//
//   @override
//   Widget build(BuildContext context) {
//      return Container(
//        width: 100,
//        height: 100,
//        color: MyColor.of(context).color,
//        // color: MyColor.maybeOf(context)?.color,
//      );
//   }
// }

/// 继承式组件
class MyColor extends InheritedWidget {
  final Color color;
  const MyColor({
    super.key,
    required super.child,
    required this.color,
  });

  static MyColor of(BuildContext context) {
    ///注入依赖
    ///在当前context开始,在组件树上找最近的某一种类型的InheritedWidget
    final myColor = context.dependOnInheritedWidgetOfExactType<MyColor>()!;

    ///获取依赖,只获得一次。并没有注入依赖,添加监听
    // final myColor = context.getInheritedWidgetOfExactType<MyColor>();
    return myColor;
  }

  static MyColor? maybeOf(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyColor>();
  }

  //该回调决定当前color发生变化时,是否通知子组件依赖color的Widget(使用了MyColor.of的组件)
  @override
  bool updateShouldNotify(covariant MyColor oldWidget) {
    ///当前组件变化时,子组件是否需要重绘。
    // TODO: implement updateShouldNotify
    print('object: ${oldWidget.color} -> $color');
    return oldWidget.color != color;
  }
}

Provider

pubspec.yaml引入状态管理的框架provider

管理页面数据,也可以使用数据共享,数据共享是单向传递。

Provider更加方便,各个页面共享数据。

传递数据,通讯。

1、ChangeNotifier

创建一个需要共享的状态类,这个类需要继承 ChangeNotifier

notifyListeners(); //告诉监听者 刷新build

import 'package:flutter/material.dart';

//定义一个数据模型 通过混入ChangeNotifier管理监听者(通知模式)
class AccountDetailListController with ChangeNotifier {
  List<String> _list = [];

  //读数据
  List<String> get list => _list;

  //写数据
  set list(List<String> newList) {
    _list = newList;
    notifyListeners(); //告诉监听者 刷新build
  }

  void hhh() {
    _list = ["fdsjklfj"];
    notifyListeners();
  }
}

2、widget树的比较高的层面使用 ChangeNotifierProvider 来提供这个状态

有一个create: (BuildContext context){}方法需要传上面的ChangeNotifier

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Account extends StatelessWidget {
  const Account({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) => AccountDetailListController(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primaryColor: Colors.yellow,
          primarySwatch: Colors.grey, //主题色 影响整个app
        ),
        home: const RootPage(),
      ),
    );
  }
}

取数据Provider.of<T>(context)

在任何widget的build方法中

var li =
  Provider.of<AccountDetailListController>(context, listen: false).list;
print("llllll:$li");

第二个页面

性能优化:最小颗粒度的渲染刷新UI。使用Consumer

Consumer包括:

  • key
  • builder
  • child

notifyListeners()发出通知的时候,下面的bulider会重新走一遍,刷新组件。

Consumer<AccountDetailListController>(
  builder: (context, value, child) {
    return Container();
  },
)
//第二个页面
class ProviderTwo extends StatelessWidget {
  const ProviderTwo({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    // //取数据 -- 不再统一的取数据
    // final _counter = Provider.of<CountModel>(context);
    return Scaffold(
      appBar: AppBar(
        title: const Text('ProviderTwo'),
      ),
      body: Center(
        //哪里用数据,就在哪里取数据
        child: Consumer<CountModel>(
          builder: (context, CountModel counter, child) =>
              Text('第二个页面count:${counter.counter}'),
        ),
      ),
      floatingActionButton: Consumer(
        child: const MyIcon(), //通过child隔离开,数据变的时候,child不需要重新渲染
        builder: (context, CountModel counter, child) => FloatingActionButton(
          onPressed: counter.increment,
          child: child,
        ),
      ),
    );
  }
}

//自定义icon
class MyIcon extends StatelessWidget {
  const MyIcon({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    print('MyIcon build');
    return const Icon(Icons.add);
  }
}

案例二

context.watch<LogoModel>();添加监听。

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) => LogoModel(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        home: const MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Logo(),
            ControlPanel(),
          ],
        ),
      ),
    );
  }
}

class Logo extends StatelessWidget {
  const Logo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    ///添加监听
    final model = context.watch<LogoModel>();

    return Card(
      child: Transform.flip(
        flipX: model.flipX,
        flipY: model.flipY,
        child: FlutterLogo(
          size: model.size,
        ),
      ),
    );
  }
}

class ControlPanel extends StatefulWidget {
  const ControlPanel({Key? key}) : super(key: key);

  @override
  State<ControlPanel> createState() => _ControlPanelState();
}

class _ControlPanelState extends State<ControlPanel> {
  @override
  Widget build(BuildContext context) {
    final model = context.watch<LogoModel>();
    return Card(
      margin: const EdgeInsets.all(32),
      child: Column(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text('Flip X: '),
              Switch(
                value: model.flipX,
                onChanged: (bool value) {
                  model.flipX = value;
                },
              ),
              const Text('Flip Y: '),
              Switch(
                value: model.flipY,
                onChanged: (bool value) {
                  model.flipY = value;
                },
              ),
            ],
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text('Size: '),
              Slider(
                min: 50,
                max: 300,
                value: model.size,
                onChanged: (value) {
                  model.size = value;
                },
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class LogoModel extends ChangeNotifier {
  bool _flipX = false;
  bool get flipX => _flipX;
  set flipX(value) {
    _flipX = value;
    notifyListeners();
  }

  bool _flipY = false;
  bool get flipY => _flipY;
  set flipY(value) {
    _flipY = value;
    notifyListeners();
  }

  double _size = 100.0;
  double get size => _size;
  set size(value) {
    _size = value;
    notifyListeners();
  }
}

利用泛型自己实现Provider

///利用范型做通用封装Provider
///多套一层StatefulWidget,State的build方法return一个InheritedWidget
///create方法:创建extends ChangeNotifier的model
class ChangeNotifierProvider<T extends Listenable> extends StatefulWidget {
  final T Function() create;
  final Widget child;

  const ChangeNotifierProvider(
      {super.key, required this.create, required this.child});

  @override
  State<ChangeNotifierProvider> createState() =>
      _ChangeNotifierProviderState<T>();

  static T of<T>(BuildContext context, {required bool listen}) {
    ///是否监听
    if (listen) {
      return context
          .dependOnInheritedWidgetOfExactType<_InheritedWidget<T>>()!
          .model;
    } else {
      return context
          .getInheritedWidgetOfExactType<_InheritedWidget<T>>()!
          .model;
    }
  }
}

class _ChangeNotifierProviderState<T extends Listenable>
    extends State<ChangeNotifierProvider<T>> {
  late T model;

  @override
  void initState() {
    super.initState();
    model = widget.create();
  }

  @override
  Widget build(BuildContext context) {
    ///借助`ListenableBuilder`监听model,每当model改变时,组件进行重绘。
    return ListenableBuilder(
      listenable: model,
      builder: (BuildContext context, Widget? child) {
        return _InheritedWidget(model: model, child: widget.child);
      },
    );
  }
}

//InheritedWidget
class _InheritedWidget<T> extends InheritedWidget {
  final T model;

  const _InheritedWidget({
    super.key,
    required this.model,
    required super.child,
  });

  @override
  bool updateShouldNotify(covariant _InheritedWidget oldWidget) {
    // TODO: implement updateShouldNotify
    return true;
  }
}

///扩展
extension Consumer on BuildContext {
  T watch<T>() => ChangeNotifierProvider.of(this, listen: true);

  T read<T>() => ChangeNotifierProvider.of(this, listen: false);
}

使用自己Provider示例

  1. 创建LogoModel extends ChangeNotifier
  2. 创建LogoModelProvider extends InheritedWidget
  3. 在组件树最上层组件插入LogoModelProvider,负责向整个组件树下面传递model。
  4. 子组件
  5. 使用final model = context.dependOnInheritedWidgetOfExactType<LogoModelProvider>()!.model;查找model。这个方法可以在LogoModelProvider封装一个of方法,直接调用of方法。
  6. 借助ListenableBuilder监听model,每当model改变时,组件进行重绘。
import 'package:flutter/material.dart';

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: () => LogoModel(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        home: const MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Logo(),
            ControlPanel(),
          ],
        ),
      ),
    );
  }
}

class Logo extends StatelessWidget {
  const Logo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    ///添加监听
    // final model = ChangeNotifierProvider.of<LogoModel>(context);
    final model = context.watch<LogoModel>();

    return Card(
      child: Transform.flip(
        flipX: model.flipX,
        flipY: model.flipY,
        child: FlutterLogo(
          size: model.size,
        ),
      ),
    );
  }
}

class ControlPanel extends StatelessWidget {
  const ControlPanel({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // final model = ChangeNotifierProvider.of<LogoModel>(context);
    final model = context.watch<LogoModel>();
    ///监听
    return Card(
      margin: const EdgeInsets.all(32),
      child: Column(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text('Flip X: '),
              Switch(
                value: model.flipX,
                onChanged: (bool value) {
                  model.flipX = value;
                },
              ),
              const Text('Flip Y: '),
              Switch(
                value: model.flipY,
                onChanged: (bool value) {
                  model.flipY = value;
                },
              ),
            ],
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text('Size: '),
              Slider(
                min: 50,
                max: 300,
                value: model.size,
                onChanged: (value) {
                  model.size = value;
                },
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class LogoModel extends ChangeNotifier {
  bool _flipX = false;

  bool get flipX => _flipX;

  set flipX(value) {
    _flipX = value;
    notifyListeners();
  }

  bool _flipY = false;

  bool get flipY => _flipY;

  set flipY(value) {
    _flipY = value;
    notifyListeners();
  }

  double _size = 100.0;

  double get size => _size;

  set size(value) {
    _size = value;
    notifyListeners();
  }
}

model是同一个

final model = context.watch<AccountDetailListController>();,获取的model是同一个。

保持小部件状态

每次切换进入页面都会走initState(),如果不想刷新数据的话,需要混入。

Mixins混入

目的:给一个类增加功能,是多继承。

保持部件状态有一个前提:在widget树中,不会被销毁。

AutomaticKeepAliveClientMixin

AutomaticKeepAliveClientMixin是Flutter框架中的一个mixin,它是用来帮助管理页面状态的。

在Flutter中,当有一个widget(例如列表中的一个项目)离开视图时,它的状态可能会被销毁,以释放内存。但是有时候可能希望保持这个widget的状态,即使它离开了视图,它的状态也会被保持,当它再次出现时,它可以恢复到以前的状态。这就是AutomaticKeepAliveClientMixin的用途。

  1. 使用with混入AutomaticKeepAliveClientMixin
  2. 重写wantKeepAlive方法,使其返回true。这将告诉Flutter你希望保持这个widget的状态。
  3. 需要在build方法中调用super.build(context),以确保AutomaticKeepAliveClientMixin可以做它需要做的事情。

这个mixin特别适用于那些加载数据或者有用户交互的widget,例如滚动列表或者输入表单,因为这些widget的状态恢复可能会耗费更多的资源。

简单示例:

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);  // Do not remove this line, it's needed for AutomaticKeepAliveClientMixin.
    return Container();  // Replace with your own widget.
  }
}