Flutter InheritedWidget in practice

610 阅读4分钟

前言

本文仅关注InheritedWidget的使用方式, 理论部分可以自行查阅资料。
InheritedWidget用来跨Widget实现数据的共享,避免无穷无尽的构造函数和回调方法,在Widget嵌套越来越深的时候就能派上用场。

本文覆盖两个场景

  1. 使用InheritedWidget实现只读数据,供子子孙孙访问
  2. 怎么刷新InheritedWidget数据并更新ui

数据只读

只读数据比较简单,遵从三步实现。
源码:gitee.com

  1. 新建InheritedWidget子类
  2. 在子Widget中使用InheritedWidget的数据
  3. InheritedWidget设置为子视图的祖先Widget
  1. 新建继承InheritedWidget的子类ReadOnlyInheritedWidget
/// 新建类继承`InheritedWidget`, 将需要使用的数据包装进来
/// 子视图通过`ReadOnlyInheritedWidget.of(context).name`获取共享的数据
class ReadOnlyInheritedWidget extends InheritedWidget {
  /// 封装需要共享的数据
  final String name;
  final String sex;

  ReadOnlyInheritedWidget({this.name, this.sex, Key key, Widget child})
      : super(child: child, key: key);

  /// 不需要更新, 直接返回false
  @override
  bool updateShouldNotify(covariant ReadOnlyInheritedWidget oldWidget) => false;

  /// 使用类方法, 供子子孙孙快速找到本类
  static ReadOnlyInheritedWidget of(BuildContext context) =>
      context.findAncestorWidgetOfExactType<ReadOnlyInheritedWidget>();
}
  1. 子widget通过类方法ReadOnlyInheritedWidget.of(context)使用共享的数据
/// 显示名字
class NameWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(children: [
      Text('name: '),
      Text('${ReadOnlyInheritedWidget.of(context).name}')
    ]);
  }
}

/// 显示性别
class SexWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(children: [
      Text('sex: '),
      Text('${ReadOnlyInheritedWidget.of(context).sex}')
    ]);
  }
}

  1. InheritedWidget设置为需要使用共享数据视图的祖先
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('只读InheritedWidget')),
        body: ReadOnlyInheritedWidget(
            name: 'Bill Gate', sex: 'Male', child: UserInfoWidget()));
  }
}

/// 显示用户信息
class UserInfoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MeanlessWidget();
  }
}

/// 无意义的中间件, 用来说明Inherited不需要构造函数来传递共享参数
class MeanlessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        NameWidget(),
        SexWidget(),
      ],
    );
  }
}

更新数据

更新数据比较复杂,InheritedWidget本身没有state, 所以也没法触发页面更新。
解决方案其实是曲线救国,将InheritedWidget设置为某个StatefullWidget的子Widget,
通过StatefullWidgetsetStates()方法触发build(),从而更新InheritedWidget
既然都包裹在StatefullWidget里,所以我的想法是也没必要让外部知道InheritedWidget的存在
源码:gitee.com
实现步骤:

  1. 新建StatefullWidgetUserInfoWidget
  2. 新建_UserInfoInheritWidget继承InheritedWidget, 将StatefullWidget的state类和共享的数据加为属性
  3. 在子Widget中使用UserInfoWidget.of()方法读取和更新数据
  4. 将子Widget加入到视图中
  1. 新建StatefullWidget
/// `UserInfoWidget`替代`InheritedWidget`来实现数据共享和更新
// ignore: must_be_immutable
class UserInfoWidget extends StatefulWidget {
  /// 作为InheritedWidget.child
  final Widget child;

  /// 待共享和更新的数据
  UserInfo _userInfo;

  UserInfoWidget({UserInfo userInfo, Key key, this.child})
      : assert(userInfo != null),
        _userInfo = userInfo,
        super(key: key);

  @override
  UserInfoWidgetState createState() => UserInfoWidgetState();

  /// 类方法, 供子子孙孙获取到当前Widget对应的state
  /// `[rebuild]==true`表示子类会监听变化并刷新界面
  /// 否则不刷新界面(用于某些不变的属性, 如本例中的`name`)
  static UserInfoWidgetState of(BuildContext context, {bool rebuild = false}) {
    return rebuild
        ? (context
            .dependOnInheritedWidgetOfExactType<_UserInfoInheritWidget>()
            .state)
        : (context
            .findAncestorWidgetOfExactType<_UserInfoInheritWidget>()
            .state);
  }
}

class UserInfoWidgetState extends State<UserInfoWidget> {
  @override
  Widget build(BuildContext context) {
    print('Build UserInfoWidgetState');

    /// UserInfoWidget充当一个容器类, 本身没有视图
    return _UserInfoInheritWidget(widget._userInfo, this);
  }

  /// 更新共享的`count`
  void plusCount() {
    // 错误用法: 会将`_UserInfoInheritWidget`的userInfo的count也更新了, 导致
    // `updateShouldNotify()`会永远返回`true`
    // widget._userInfo.count = widget._userInfo.count + 1;

    // 正确用法:
    widget._userInfo = UserInfo(
        name: widget._userInfo.name, count: widget._userInfo.count + 1);
    setState(() {});
  }

  /// 获取共享名称
  String get name => widget._userInfo.name;

  /// 获取共享的count
  int get count => widget._userInfo.count;
}
  1. 新建InheritedWidget
class _UserInfoInheritWidget extends InheritedWidget {
  final UserInfo userInfo;
  final UserInfoWidgetState state;

  _UserInfoInheritWidget(this.userInfo, this.state, {Key key})
      : super(key: key, child: state.widget.child);

  /// 是否需要通知子类更新
  @override
  bool updateShouldNotify(covariant _UserInfoInheritWidget oldWidget) {
    return userInfo.name != oldWidget.userInfo.name ||
        userInfo.count != oldWidget.userInfo.count;
  }
}
  1. 子Widget使用和更新数据
class ShowInfoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('Build ShowInfoWidget');
    return Text(
        '${UserInfoWidget.of(context).name} ${UserInfoWidget.of(context, rebuild: true).count}');
  }
}

class CountWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('Build _CountWidgetState');
    return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
      IconButton(
          icon: Icon(Icons.add),
          onPressed: () => UserInfoWidget.of(context).plusCount()),
    ]);
  }
}
  1. 加入Widget
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('Build MyHomePage');
    return Scaffold(
      appBar: AppBar(title: Text('Write InheritedWidget')),
      body: Center(
        child: UserInfoWidget(
          userInfo: UserInfo(name: 'bill', count: 0),
          child: Column(
            children: [Meanless1Widget(), Meanless2Widget()],
          ),
        ),
      ),
    );
  }
}

// 无意义的中间节点,用于证明不需要构造函数来传递数据
class Meanless1Widget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('Build Meanless1Widget');
    return Row(children: [ShowInfoWidget()]);
  }
}

// 无意义的中间节点,用于证明不需要构造函数来传递数据
class Meanless2Widget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('Build Meanless2Widget');
    return Row(
      children: [CountWidget()],
    );
  }
}

参考

  1. juejin.cn/post/684490…
  2. www.cnblogs.com/upwgh/p/115…

参考1写的不错,不过我觉得例子不太好,而且文章跟实际github上的例子又有差异
参考2的例子实践意义有点低。