Flutter的InheritedWidget

1.概述

场景1:一个界面是由众多组件拼组而成。经常需要将一个组件进行封装,但此时有一个问题,如何让多个组件去共享一些值。一个最直接的方法就是通过构造函数将变量和函数一层层向下传递。如果嵌套的很深,那简直就是噩梦,如果子组件需要更新父组件的数据或者状态,再加个回调?额。。。这个时候我们可以使用Flutter提供的InheritedWidget。

  • 特性:
    InheritedWidget是Flutter中的一个非常重要的功能组件,它能够提供数据在widget树中从上到下进行传递。保证数据在不同子widget中进行共享。
    show code ~~
import 'package:flutter/material.dart';
///数据共享
class SecondDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TestPage(
      child: Scaffold(
        appBar: AppBar(
          title: Text('InheritedWidget Demo'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            WidgetA(),
            WidgetB(),
            WidgetC(),
          ],
        ),
      ),
    );
  }
}
// ignore: must_be_immutable
class InheritedProvide extends InheritedWidget {///数据共享

  InheritedProvide({
    Key key,
    @required Widget child,
    @required this.data,
  }) : super(key: key, child: child);

  final TestPageState data;

  @override
  bool updateShouldNotify(InheritedProvide oldWidget) {
    return true;
  }
}
class TestPage extends StatefulWidget {
  TestPage({
    Key key,
    this.child,
  }) : super(key: key);

  final Widget child;

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

  static TestPageState of(BuildContext context) {
    // ignore: deprecated_member_use
    return (context.inheritFromWidgetOfExactType(InheritedProvide) as InheritedProvide)?.data;
  }
}

class TestPageState extends State<TestPage> {
  int counter = 0;

  void incrementPageCounter() {
    setState(() {
      counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return InheritedProvide(
      data: this,
      child: widget.child,
    );
  }
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
    //如果build中没有依赖InheritedWidget,则此回调不会被调用。
    print("Dependencies change");
  }
}
class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final TestPageState state = TestPage.of(context);
    print('----------WidgetA build------');
    return Center(
      child: Text('点击的次数',),
    );
  }
}
class WidgetB extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    final TestPageState state = TestPage.of(context,);

    print('----------WidgetB build------');

    return  new Padding(
      padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
      child:  new Text('${state.counter}', style: Theme.of(context).textTheme.display1,),
    );
  }
}
class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final TestPageState state = TestPage.of(context);
    print('----------WidgetC build------');
    return RaisedButton(
      onPressed: () {
        state.incrementPageCounter();
      },
      child: Icon(Icons.add),
    );
  }
}

问题:在上面的demo中,存在一个性能问题

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final TestPageState state = TestPage.of(context);
    print('----------WidgetC build------');
    return RaisedButton(
      onPressed: () {
        state.incrementPageCounter();
      },
      child: Icon(Icons.add),
    );
  }
}

这个WidgetC不应该build,因为调用了final TestPageState state = TestPage.of(context);这句代码,也就是说依赖了Widget树上面的InheritedWidget(即InheritedProvider )Widget,所以当点击完按钮后,数据发生变化,会通知TestPage, 而TestPage则会重新构建子树,所以InheritedProvider将会更新,此时依赖它的子孙Widget就会被重新构建。那么如何避免WidgetC不Build呢?

  • 解决方案:
    既然WidgetC重新被build是因为WidgetC和InheritedWidget建立了依赖关系,那么我们只要打破或解除这种依赖关系就可以了。如何打破呢?需要用到inheritFromWidgetOfExactType和ancestorWidgetOfExactType这两个方法。
    代码如下:
import 'package:flutter/material.dart';

//局部刷新
class ThirdDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TestPage(
      child: Scaffold(
        appBar: AppBar(
          title: Text('InheritedWidget Demo'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            WidgetA(),
            WidgetB(),
            WidgetC(),
          ],
        ),
      ),
    );
  }
}
// ignore: must_be_immutable
class InheritedProvider extends InheritedWidget {///数据共享

  InheritedProvider({
    Key key,
    @required Widget child,
    @required this.data,
  }) : super(key: key, child: child);

  final TestPageState data;

  @override
  bool updateShouldNotify(InheritedProvider oldWidget) {
    return true;
  }
}
class TestPage extends StatefulWidget {
  TestPage({
    Key key,
    this.child,
  }) : super(key: key);

  final Widget child;

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

  static TestPageState of(BuildContext context, {bool rebuild = true}) {
    if (rebuild) {
      // ignore: deprecated_member_use
      return (context.inheritFromWidgetOfExactType(InheritedProvider) as InheritedProvider)?.data;
    }
    // ignore: deprecated_member_use
    return (context.ancestorWidgetOfExactType(InheritedProvider) as InheritedProvider)?.data;
  }
}

class TestPageState extends State<TestPage> {
  int counter = 0;

  void incrementPageCounter() {
    setState(() {
      counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return InheritedProvider(
      data: this,
      child: widget.child,
    );
  }

}
class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final TestPageState state = TestPage.of(context);
    print('----------WidgetA build------');
    return Center(
      child: Text('点击的次数',),
    );
  }
}
class WidgetB extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    final TestPageState state = TestPage.of(context,);

    print('----------WidgetB build------');

    return  new Padding(
      padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
      child:  new Text('${state.counter}', style: Theme.of(context).textTheme.display1,),
    );
  }
}
class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final TestPageState state = TestPage.of(context, rebuild: false);
    print('----------WidgetC build------');
    return RaisedButton(
      onPressed: () {
        state.incrementPageCounter();
      },
      child: Icon(Icons.add),
    );
  }
}

再点击会发现“----------WidgetC build------”这句话不打印了。说明WidgetC不build了。

  • 总结:调用inheritFromWidgetOfExactType() 和 ancestorWidgetOfExactType()的区别就是前者会注册依赖关系,而后者不会,所以在调用inheritFromWidgetOfExactType()时,InheritedWidget和依赖它的子孙组件关系便完成了注册,之后当InheritedWidget发生变化时,就会更新依赖它的子孙组件,也就是会调这些子孙组件的build()方法。而当调用的是 ancestorWidgetOfExactType()时,由于没有注册依赖关系,所以之后当InheritedWidget发生变化时,就不会更新相应的子孙Widget。

2. ValueListenableBuilder

InheritedWidget 提供一种在 widget 树中从上到下共享数据的方式,但是也有很多场景数据流向并非从上到下,比如从下到上或者横向等。为了解决这个问题,Flutter 提供了一个 ValueListenableBuilder 组件,它的功能是监听一个数据源,如果数据源发生变化,则会重新执行其 builder。

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

///直接调用SetState
class FourthDemo extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _FourthDemoState();
  }
}

class _FourthDemoState extends State<FourthDemo>{
  int counter = 0;

  void incrementCounter() {
//    setState(() {
      counterNotifier.value = counter++;
//    });
  }
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("Dependencies change");
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('FirstDemo'),),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          WidgetA(),
          WidgetB(counter),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
class WidgetA extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    print('----------WidgetA build------');
    return Center(
      child: Text('You have pushed the button this many times:',),
    );
  }
}
ValueNotifier<int> counterNotifier = ValueNotifier<int>(0);
class WidgetB extends StatelessWidget {
  final int counter;
  WidgetB(this.counter);
  @override
  Widget build(BuildContext context) {
    print('----------WidgetB build------');
    return ValueListenableBuilder<int>(
        valueListenable: counterNotifier,
        builder: (BuildContext context,int counter,Widget child){
          print('----------ValueListenableBuilder build------');
          return Text('$counter', style: Theme.of(context).textTheme.display1,);
        });
  }
}
  • 总结
  1. 和数据流向无关,可以实现任意流向的数据共享。
  2. 实践中,ValueListenableBuilder 的拆分粒度应该尽可能细,可以提高性能。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容