Flutter InheritedWidget 详解

186 阅读4分钟

InheritedWidget简介

用于在 widget 树中高效地向下传递数据,允许子组件获取最近Widget树中的InheritedWidget的数据

  1. 数据共享:在 widget 树中向下共享数据
  2. 高效更新:当数据变化时,只重建依赖该数据的子 widget
  3. 自动依赖管理:子 widget 自动"注册"为依赖项(dependOn)

示例

组件结构

子组件TextShowWidget 通过dependOnInheritedWidgetOfExactType()NameInheritedWidget关联,当InputNameWidget组件TextFiled输入变化的时,触发NameInheritedWidget变化,当updateShouldNotify返回true,从而通知和NameInheritedWidget关联的组件TextShowWidget变化

image.png

代码示例

TestInheritedWidget/AppChildInheritedWidget

在Flutter中Widget是不可变的,InheritedWidget也是一个Widget,所有要更新widget还是得构造一个StatfulWidget,通过setState()来更新, 在 AppChildInheritedWidget中当name发生变化,重新rebuild,来更新NameInheritedWidget数据, 但是此时的InputShowWidget是通过构造方法传入的,不会改变

class TestInheritedWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("TestInheritedWidget111");
    return Scaffold(
        appBar: AppBar(
          title: Text(
            "Inherited",
            style: TextStyle(fontSize: 28, color: Colors.white),
          ),
          backgroundColor: Color(0xFFE51BE5),
        ),
        body: AppChildInheritedWidget(child: InputShowWidget()));
  }
}

class AppChildInheritedWidget extends StatefulWidget {
  final Widget child;

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

  @override
  State<AppChildInheritedWidget> createState() => _AppChildInheritedWidgetState();
}

class _AppChildInheritedWidgetState extends State<AppChildInheritedWidget> {
  String? name;

  @override
  Widget build(BuildContext context) {
    return NameInheritedWidget(
      child: widget.child,
      name: name,
      onNameChange: (value) {
        setState(() {
          name = value;
        });
      },
    );
  }
}

NameInheritedWidget

dependOnInheritedWidgetOfExactType和 InheritedWidget 产生依赖关系,主动获取数据,当InheritedWidget数据变化时,关联的组件也会自动更新(build),

getInheritedWidgetOfExactType 单纯的获取InheritedWidget的数据,不产生关联,被动获取,

updateShouldNotify比较新旧widget数据是否一致,true触发更新

class NameInheritedWidget extends InheritedWidget {
  final String? name;
  void Function(String? name) onNameChange;
  NameInheritedWidget({required super.child, this.name, required this.onNameChange});

  static String? of(BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType<NameInheritedWidget>()!
        .name;
  }
  
  static String? maybe(BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType<NameInheritedWidget>()
        ?.name;
  }

  static NameInheritedWidget? getNameInheritedWidget(BuildContext context) {
    return context.getInheritedWidgetOfExactType<NameInheritedWidget>();
  }

  @override
  bool updateShouldNotify(covariant NameInheritedWidget oldWidget) {
    return oldWidget.name != this.name;
  }
}

InputShowWidget


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

  @override
  Widget build(BuildContext context) {
    print("InputShowWidget222");
    return Column(
      children: [
        Expanded(flex: 2, child: InputNameWidget()),
        Expanded(flex: 1, child: TextShowWidget())
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    print("TextShowWidget");
    var name = NameInheritedWidget.maybe(context) ?? "未知输入";
    return Padding(
      padding: const EdgeInsets.all(20.0),
      child: Center(
          child: Text(
        name,
        style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.w600,
            color: Colors.deepOrangeAccent),
      )),
    );
  }
}

InputNameWidget/TextShowWidget


class InputNameWidget extends StatefulWidget {
  const InputNameWidget({super.key});

  @override
  State<InputNameWidget> createState() => _InputNameWidgetState();
}

class _InputNameWidgetState extends State<InputNameWidget> {
  TextEditingController _textEditingController = TextEditingController(text: "爱好");
  
  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    _textEditingController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print("InputNameWidget333");
    return LayoutBuilder(builder: (context, constraints) {
      return SingleChildScrollView(
        child: ConstrainedBox(
          constraints: BoxConstraints(minHeight: constraints.maxHeight),
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 20),
            child: Column(
              spacing: 20,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                TextField(
                  style: TextStyle(color: Colors.pink),
                  controller: _textEditingController,
                  onChanged: (value){
                    print(value);
                    NameInheritedWidget.getNameInheritedWidget(context)
                        ?.onNameChange(value);
                  },
                  decoration: InputDecoration(
                      focusColor: Colors.red,
                      hintText: "请输入",
                      hintStyle: TextStyle(color: Colors.blueAccent),
                      border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(12),
                          borderSide: BorderSide.none),
                      focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(12),
                          borderSide: BorderSide(color: Colors.black)),
                      filled: true,
                      fillColor: Color(0xE5BBBBC4)),
                ),
                SizedBox(height: 20,),
                FilledButton(
                    style: ButtonStyle(
                        backgroundColor: WidgetStatePropertyAll(Colors.black),
                        padding: WidgetStatePropertyAll(
                            EdgeInsets.symmetric(vertical: 18)),
                        shape: WidgetStatePropertyAll(RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(12)))),
                    onPressed: () {
                      NameInheritedWidget.getNameInheritedWidget(context)
                          ?.onNameChange(_textEditingController.text);
                    },
                    child: Text("点击"))
              ],
            ),
          ),
        ),
      );
    });
  }
}

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

  @override
  Widget build(BuildContext context) {
    print("TextShowWidget");
    var name = NameInheritedWidget.maybe(context) ?? "未知输入";
    return Padding(
      padding: const EdgeInsets.all(20.0),
      child: Center(
          child: Text(
        name,
        style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.w600,
            color: Colors.deepOrangeAccent),
      )),
    );
  }
}

日志

首次进入所有Widget的build方法全部执行

1750261878025.png

TextField发生变化,触发底部文字变化

1750261982693.png

dependOnInheritedWidgetOfExactType探索如何关联

PersistentHashMap<Type, InheritedElement>? _inheritedElements; 存储InheritedElement类型的Map集合

@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement? ancestor = _inheritedElements?[T];
  if (ancestor != null) {
    return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}

在mount和 activate 赋值

void _updateInheritance() {
  assert(_lifecycleState == _ElementLifecycle.active);
  _inheritedElements = _parent?._inheritedElements;
}

总结

子组件 _dependencies 存储所关联的InheritedElement, 通过ancestor.updateDependencies(this, aspect);把子组件和 InheritedElement 关联,把子组件存储在InheritedElement的 Map1 (key值) , 当InheritedElement更新updated,通过updateShouldNotify 判断是否更新super.updated(oldWidget),如需更新调用 notifyClients,遍历上述Map1的keys,执行 dependent.didChangeDependencies(); 这时的dependent就是上述子组件,执行子组件的markNeedsBuild,执行owner!.scheduleBuildFor(this),把子组件加入_dirtyElements.add(element);_dirtyElements集合中,并执行scheduleRebuild?.call(); WidgetsBinding中执行ensureVisualUpdate,执行scheduleFrame请求新的帧,等待VSync信号,执行onDrawFrame,执行 rootElement.buildScope._flushDirtyElements()_dirtyElements排序遍历 _dirtyElements,执行element.rebuild, 执行element.performRebuild 执行widget.build,执行element.updateChild挂载到element上,到此流程结束。

dependOnInheritedElement

@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, {Object? aspect}) {
  _dependencies ??= HashSet<InheritedElement>();
  _dependencies!.add(ancestor);
  ancestor.updateDependencies(this, aspect);
  return ancestor.widget as InheritedWidget;
}
@protected
void updateDependencies(Element dependent, Object? aspect) {
  setDependencies(dependent, null);
}
@protected
void setDependencies(Element dependent, Object? value) {
  _dependents[dependent] = value;
}
void updated(InheritedWidget oldWidget) {
  if ((widget as InheritedWidget).updateShouldNotify(oldWidget)) {
    super.updated(oldWidget);
  }
}
@protected
void updated(covariant ProxyWidget oldWidget) {
  notifyClients(oldWidget);
}
 @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      assert(() {
        // check that it really is our descendant
        Element? ancestor = dependent._parent;
        while (ancestor != this && ancestor != null) {
          ancestor = ancestor._parent;
        }
        return ancestor == this;
      }());
      // check that it really depends on us
      assert(dependent._dependencies!.contains(this));
      notifyDependent(oldWidget, dependent);
    }
  }
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
  dependent.didChangeDependencies();
}

子组件的 didChangeDependencies/markNeedsBuild

@mustCallSuper
void didChangeDependencies() {
  assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
  assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
  markNeedsBuild();
}
void markNeedsBuild() {
  if (dirty) {
    return;
  }
  _dirty = true;
  owner!.scheduleBuildFor(this);
}
void scheduleBuildFor(Element element) {
 
  final BuildScope buildScope = element.buildScope;
  
  if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
    _scheduledFlushDirtyElements = true;
    onBuildScheduled!();
  }
  buildScope._scheduleBuildFor(element);

}
void _scheduleBuildFor(Element element) {
  if (!element._inDirtyList) {
    _dirtyElements.add(element);
    element._inDirtyList = true;
  }
  if (!_buildScheduled && !_building) {
    _buildScheduled = true;
    scheduleRebuild?.call();
  }
  if (_dirtyElementsNeedsResorting != null) {
    _dirtyElementsNeedsResorting = true;
  }
}
mixin WidgetsBinding
    on
        BindingBase,
        ServicesBinding,
        SchedulerBinding,
        GestureBinding,
        RendererBinding,
        SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
   
    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
    platformDispatcher.onLocaleChanged = handleLocaleChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    SystemChannels.backGesture.setMethodCallHandler(_handleBackGestureInvocation);
    assert(() {
      FlutterErrorDetails.propertiesTransformers.add(debugTransformDebugCreator);
      return true;
    }());
    platformMenuDelegate = DefaultPlatformMenuDelegate();
  }
void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame();
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks:
      return;
  }
}
@protected
void ensureFrameCallbacksRegistered() {
  platformDispatcher.onBeginFrame ??= _handleBeginFrame;
  platformDispatcher.onDrawFrame ??= _handleDrawFrame;
}
if (rootElement != null) {
  buildOwner!.buildScope(rootElement!);
}
void buildScope(Element context, [VoidCallback? callback]) {
  final BuildScope buildScope = context.buildScope;
  if (callback == null && buildScope._dirtyElements.isEmpty) {
    return;
  }
  
  try {
    _scheduledFlushDirtyElements = true;
    buildScope._building = true;
    if (callback != null) {
      assert(_debugStateLocked);
      Element? debugPreviousBuildTarget;
      try {
        callback();
      } finally {
        assert(() {
          assert(_debugCurrentBuildTarget == context);
          _debugCurrentBuildTarget = debugPreviousBuildTarget;
          _debugElementWasRebuilt(context);
          return true;
        }());
      }
    }
    buildScope._flushDirtyElements(debugBuildRoot: context);
  } finally {
    buildScope._building = false;
    _scheduledFlushDirtyElements = false;
    if (!kReleaseMode) {
      FlutterTimeline.finishSync();
    }
  
  }
  assert(_debugStateLockLevel >= 0);
}

buildScope._flushDirtyElements(debugBuildRoot: context);

_tryRebuild(element);

@pragma('vm:notify-debugger-on-exception')
void _flushDirtyElements({required Element debugBuildRoot}) {
  assert(_dirtyElementsNeedsResorting == null, '_flushDirtyElements must be non-reentrant');
  //排序
  _dirtyElements.sort(Element._sort);
  _dirtyElementsNeedsResorting = false;
  try {
    for (int index = 0; index < _dirtyElements.length; index = _dirtyElementIndexAfter(index)) {
      final Element element = _dirtyElements[index];
      if (identical(element.buildScope, this)) {
        
        _tryRebuild(element);
      }
    }
    assert(() {
      final Iterable<Element> missedElements = _dirtyElements.where(
        (Element element) =>
            element.debugIsActive && element.dirty && identical(element.buildScope, this),
      );
      
  } finally {
    for (final Element element in _dirtyElements) {
      if (identical(element.buildScope, this)) {
        element._inDirtyList = false;
      }
    }
    _dirtyElements.clear();
    _dirtyElementsNeedsResorting = null;
    _buildScheduled = false;
  }
}

element.rebuild();

void _tryRebuild(Element element) {
  assert(element._inDirtyList);
  assert(identical(element.buildScope, this));
  final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(element.widget);
  if (isTimelineTracked) {
    Map<String, String>? debugTimelineArguments;
    assert(() {
      if (kDebugMode && debugEnhanceBuildTimelineArguments) {
        debugTimelineArguments = element.widget.toDiagnosticsNode().toTimelineArguments();
      }
      return true;
    }());
    FlutterTimeline.startSync('${element.widget.runtimeType}', arguments: debugTimelineArguments);
  }
  try {
    element.rebuild();
  } catch (e, stack) {
   
  }
 
}

performRebuild

void rebuild({bool force = false}) {
 
   performRebuild();
 
}

执行 build,updateChild()挂载到element上

void performRebuild() {
  Widget? built;
  try {
    assert(() {
      _debugDoingBuild = true;
      return true;
    }());
    built = build();
  } catch (e, stack) {
   
  } finally {
    // We delay marking the element as clean until after calling build() so
    // that attempts to markNeedsBuild() during build() will be ignored.
    super.performRebuild(); // clears the "dirty" flag
  }
  try {
    _child = updateChild(_child, built, slot);
    assert(_child != null);
  } catch (e, stack) {
  }
}