flutter中InheritedWidget分析

256 阅读4分钟

介绍

InheritedWidget 组件就是Flutter 中的一个功能组件,它可以实现Flutter 组件之间的数据共享,他的数据传递方向在Widget树传递是从上到下的 inheritedWidget 不继承自StatefulWidget,而是 InheritedWidget -> ProxyWidget -> Widget 这样的继承关系。简单来说,InheritedWidget 的作用是向它的子 Widget 有效地传播和分享数据,当 InheritedWidget 作为一个Parent Widget时,它下面的Widget tree的所有Widget都可以去和 InheritedWidget 发生数据传递和交互。当数据发生改变时,一部分控件需要 rebuild,另外的控件不需要 rebuild 的时候,可以使用 InheritedWidget

使用

直接撸代码吧


/// des:  InheritedWidget是Flutter中非常重要的一个功能型组件,它提供了一种数据在widget树中从上到下传递、共享的方式

/// des:  InheritedWidget是Flutter中非常重要的一个功能型组件,它提供了一种数据在widget树中从上到下传递、共享的方式
import 'package:flutter/material.dart';


class ShareDataWidget extends InheritedWidget  {


  final int data; //需要在子树中共享的数据,保存点击次数

  ShareDataWidget( {@required this.data,Widget child})
      :super(child:child);


  // 子树中的widget通过该方法获取ShareDataWidget,从而获取共享数据
  static ShareDataWidget of(BuildContext context){
    //该方法用于在Widget树上获取离当前widget最近的一个父级InheritFromWidget
    //使用 ancestorInheritedElementForWidgetOfExactType 方法当数据变化则不会调用 子widget 的didChangeDependencies 方法


     return context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget;
//从BuildContext的Widget树上获取最近指定的类型的Widget,指定的Widget必须是InheritedWidget的子类,并且指定的Widgt挂载到了该BuildContext上,
// 这个Widget改变时,该BuildContext会rebuilt,这样获取数据的子组件就会重新build,所以它就可以从该InheritedWidget中获取到最新的数
// 我们的InheritedWidget是挂载到BuildContext,它的子组件自然也在该BuildContext树上,当InheritedWidget发生变化时,
// BuildContext会rebuilt,从而子组件就就能重新build获取最新的数据。
     //final widget = context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
   // return widget;
  }


  //继承 InheritedWidget 实现的方法 返回值 决定当data发生变化时,是否通知子树中依赖data的Widget 更新数据
  @override
  bool updateShouldNotify(ShareDataWidget oldWidget) {
    //如果返回true,则子树中依赖(build函数中有调用)本widget的子widget的`state.didChangeDependencies`会被调用
    return oldWidget.data != data;
  }
}

import 'package:flutter/material.dart';

import 'my_InheritedWidget.dart';

class TestShareDataWidget extends StatefulWidget {
  @override
  _TestShareDataWidgetState createState() => _TestShareDataWidgetState();
}

class _TestShareDataWidgetState extends State<TestShareDataWidget> {


  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //上层 widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
    //如果build中没有依赖InheritedWidget,则此回调不会被调用。
    print("didChangeDependencies");
  }

  @override
  Widget build(BuildContext context) {
    //显示 ShareDataWidget 数据变化,如果build中没有依赖InheritedWidget,则此回调不会被调用。
    return Text(ShareDataWidget.of(context).data.toString());

  }
}

/// des:  创建一个按钮,每点击一次,就将ShareDataWidget的值自增
import 'package:flutter/material.dart';
import 'package:flutterinheritedwidget/test_shareDataWidget.dart';

import 'my_InheritedWidget.dart';

class InheritedWidgetTest extends StatefulWidget {
  @override
  _InheritedWidgetTestState createState() => _InheritedWidgetTestState();
}

class _InheritedWidgetTestState extends State<InheritedWidgetTest> {

  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ShareDataWidget(
        data: count, //共享数据 data
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
                padding: const EdgeInsets.only(bottom: 20.0),
                child: TestShareDataWidget()//子widget中依赖ShareDataWidget
            ),
            RaisedButton(
              child: Text("计数增加"),
              onPressed: (){
                setState(() {
                  ++ count;
                });
              },
            )
          ],
        ),
      ),
    );
  }
}

说明

InheritedWidget是怎么去通知子Widget刷新数据的呢?

@override
  Widget build(BuildContext context) {
    //显示 ShareDataWidget 数据变化,如果build中没有依赖InheritedWidget,则此回调不会被调用。
    return Text(ShareDataWidget.of(context).data.toString());
  }

会调用

//在这调用
static ShareDataWidget of(BuildContext context){
  //该方法用于在Widget树上获取离当前widget最近的一个父级InheritFromWidget
  //使用 ancestorInheritedElementForWidgetOfExactType 方法当数据变化则不会调用 子widget 的didChangeDependencies 方法

  //  return context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget;

  final widget = context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  return widget;
}

这个方法的作用:

  • 获取ShareDataWidget里面的数据
  • 会将调用该方法的Widget加入订阅者行列,当数据发生改变的时候,会通知这些Widget刷新数据,也就是rebuild

但是这个方法需要改造。实现不同widget的定向数据分享和传播

  static ShareDataWidget of(BuildContext context, {bool rebuild = true}){
  //该方法用于在Widget树上获取离当前widget最近的一个父级InheritFromWidget
  //使用 ancestorInheritedElementForWidgetOfExactType 方法当数据变化则不会调用 子widget 的didChangeDependencies 方法
 //  return context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget;

//  final widget = context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
return (rebuild ? context.inheritFromWidgetOfExactType(ShareDataWidget) 
                  : context.ancestorInheritedElementForWidgetOfExactType(ShareDataWidget)
}
}

这样不同的weiget就可以用不同的订阅

原理

InheritedWidget中可以存放数据data,它的子组件使用了InheritedWidget中的data,那么这个子组件就依赖于该InheritedWidget,所以子组件是否依赖InheritedWidget主要在于子组件是否使用了父组件InheritedWidget中的数据。形成依赖后,当InheritedWidget中的数据data改变时,就会通知到依赖它的子组件。我们知道在StatefulWidget的State对象中有一个didChangeDependencies回调,看名字就知道,它会在依赖的组件发生变化时被调用。另外我们知道 initState -> didChangeDependencies -> build,所以这个时候就会调用build函数进行页面重构。当然,如果子组件没有使用到父组件InheritedWidget中的数据时,也就没有产生依赖关系,InheritedWidget中的data变化时也就不会通知该子组件。