首页 > 其他分享 >【flutter 起步走】flutter共享数据利器,InheritedWidget原理探秘

【flutter 起步走】flutter共享数据利器,InheritedWidget原理探秘

时间:2023-06-22 14:05:03浏览次数:31  
标签:widget InheritedWidget State context MyProvider override 探秘 flutter


知其然,也要知其所以然。最近的搬砖工作中,开发ui页面都是使用flutter,android原生只沦为了后台逻辑处理的后盾。在搬砖过程中,往往只要知道怎么用,便能搭起小房子,而要建的恢弘又大气,还是少不了对于原理的学习。

在接触flutter中,Widget是我们接触最多的类。我们对于各种界面的搭建用的就是各式各样的Widget,有StatefullWidgetStatelessWidget。印象中Widget就是最为ui搭建而存在的,然而在flutter的中,还有一种widget不负责ui控制,但却十分重要,那就是InheritedWidget。 在我们常用的第三方数据共享库中,都有InheritedWidget的影子,比如下面两个。

  • flutter_bloc
  • Provider

还有开发中用到的很多数据,比如Theme.of(context),DefaultTextStyle.of(context)等,都是在父节点配置,子widget全局获取。这都离不开InheritedWidget的特性。 既然如此重要,今天就好好学习下InheritedWidget的大致原理吧。

一,InheritedWidget 介绍

InheritedWidget是 Flutter 中非常重要的一个功能型组件,它提供了一种在 widget 树中从上到下共享数据的方式,比如我们在应用的根 widget 中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget 中来获取该共享的数据!这个特性在一些需要在整个 widget 树中共享数据的场景中非常方便!如Flutter SDK中正是通过 InheritedWidget 来共享应用主题(Theme)和 Locale (当前语言环境)信息的。

InheritedWidget和 React 中的 context 功能类似,和逐级传递数据相比,它们能实现组件跨级传递数据。InheritedWidget的在 widget 树中数据传递方向是从上到下的,这和通知Notification的传递方向正好相反。

二,组件树中的数据共享用法(利用 ValueNotrifier 和 InhertitedWidget 实现简单的状态管理)

  1. 我们首先创建一个MyProvider 继承自InheritedWidget,以存放状态数据,该状态继承自ChangeNotifier,即代码中的model变量。
import 'package:flutter/widgets.dart';

///提供状态存储
class MyProvider<T extends ChangeNotifier> extends InheritedWidget {
  T model;
  MyProvider({Key? key,required Widget child,required this.model}) : super(key: key,child:child);

  @override
  Widget build(BuildContext context) {
    return this.child;
  }

  static MyProvider<T>? of<T extends ChangeNotifier>(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<MyProvider<T>>();
  }

  @override
  bool updateShouldNotify(covariant MyProvider oldWidget) {
    return false;
  }
}
  1. 接下来创建一个Customer,用来实现动态刷新,和局部刷新,原理主要是利用changeNotifier监听器更新数据更新,注册对应监听器刷新Customer的子布局。
///提供状态消费,在状态ValueNotifier更新的时候,自动刷新Customer组件
class Customer<T extends ChangeNotifier> extends StatefulWidget{
  const Customer({required this.builder,});
  final ProviderBuilder<T> builder;

  @override
  State<Customer<T>> createState() => _CustomerState<T>();
}

class _CustomerState<T extends ChangeNotifier> extends State<Customer<T>> {

  late MyProvider? provider;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    provider=MyProvider.of<T>(context);
    if(provider==null) throw "请在祖先节点提供MyProvider<T extends ChangeNotifier>";
    provider!.model.addListener(() {
      setState(()=>{});
    });
  }
  @override
  Widget build(BuildContext context) {
    return widget.builder(context,MyProvider.of<T>(context)!.model);
  }
}
typedef ProviderBuilder<T extends ChangeNotifier> = Widget Function(BuildContext context,T value);
  1. 接着创建一个展示用的widget,包含显示数据的子widget 和 点击刷新数据的 按钮,代码如下:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:free/widget/my_provider.dart';
import 'package:provider/provider.dart';

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

  @override
  State<TestWidget> createState() => _TestWidgetState();
}
class TestModel extends ChangeNotifier{
  var num=0;

  TestModel() : super();
}
class _TestWidgetState extends State<TestWidget> {
   var model=TestModel();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: MyProvider<TestModel>(
        model:model,
        child: Builder(
          builder: (context) {
            return Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  MyTextWidget(),
                  MaterialButton(
                    color: Colors.blueAccent,
                      elevation: 10,
                      onPressed: () {
                        model.num++;
                        model.notifyListeners();
                      },
                      child: Text("点击加一")),
                ],
              ),
            );
          }
        ),
      ),
    );
  }
}
class MyTextWidget extends StatelessWidget {
  const MyTextWidget ({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Customer<TestModel>(builder:(c,value) => Text("${value.num}"));
  }
}

至此,利用InheritedWidgetChangeNotifier实现的简易状态管理库就实现了。该库实现了跨组件共享数据,跨组件更新数据状态,并更新对应的兄弟组件。 这也是一些第三方开源状态库的基本原理。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-801uLTkK-1656770740524)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/affe1c05fcbe418fbfbb6a166157e10b~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image)]

三,InheritedWidget如何共享数据

先看看我们获取 InheritedWidget实例的dependOnInheritedWidgetOfExactType方法,注释翻译如下:

获取给定类型T的最近小部件,它必须是具体InheritedWidget子类的类型,并将此构建上下文注册到该小部件,以便当该小部件更改时(或引入该类型的新小部件,或小部件消失离开),这个构建上下文被重建,以便它可以从那个小部件获取新值。 这通常从of()静态方法中隐式调用,例如Theme.of 。 不应从小部件构造函数或State.initState方法调用此方法,因为如果继承的值发生更改,这些方法将不会再次被调用。为了确保小部件在继承值更改时正确更新自身,只能从构建方法、布局和绘制回调或从State.didChangeDependencies调用(直接或间接)。 不应从State.dispose调用此方法,因为此时元素树不再稳定。要从该方法引用祖先,请将对祖先的引用保存在State.didChangeDependencies中。从State.deactivate中使用此方法是安全的,每当从树中删除小部件时都会调用该方法。 也可以从交互事件处理程序(例如手势回调)或计时器调用此方法,以获取一次值,如果该值不会被缓存并稍后重用。 调用此方法是 O(1),具有较小的常数因子,但会导致更频繁地重建小部件。 一旦小部件通过调用此方法注册对特定类型的依赖关系,它将被重建,并且State.didChangeDependencies将被调用,每当与该小部件相关的更改发生时,直到下一次移动小部件或其祖先之一(例如例如,因为添加或删除了祖先)。 aspect参数仅在T是支持部分更新的InheritedWidget子类时使用,例如InheritedModel 。它指定此上下文所依赖的继承小部件的“方面。 T? dependOnInheritedWidgetOfExactType({ Object? aspect });

  1. 我们找到了BuildContext的该方法的具体实现,实际上是在Element中,其实现了BuildContext
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
  if (ancestor != null) {
    return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}

可以看到主要是从 **_inheritedWidgets** 中找到对应TypeInheritedElement

  1. 我们再去看InheritedElement源码,主要关注下_updateInheritance方法
@override
void _updateInheritance() {
  assert(_lifecycleState == _ElementLifecycle.active);
  final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
  if (incomingWidgets != null)
    _inheritedWidgets = HashMap<Type, InheritedElement>.of(incomingWidgets);
  else
    _inheritedWidgets = HashMap<Type, InheritedElement>();
  _inheritedWidgets![widget.runtimeType] = this;
}

小结:可以看到_updateInheritance方法中,其向_inheritedWidgets中添加了自己。而**_inheritedWidgetsElement**树中会被子Element继承,所以子树就能轻松获取到该InheritedElement实例了。 而_updateInheritance方法,是在Element mountactive方法中被调用的,当**InheritedElement**被挂载到element树中或者被激活后执行,就能被子element获取到了。 至此就能大概了解**InheritedWidget** 如何共享数据了,主要是利用InhritedElement作为中转持有。


标签:widget,InheritedWidget,State,context,MyProvider,override,探秘,flutter
From: https://blog.51cto.com/u_16163453/6534777

相关文章

  • Flutter 学习 之 权限管理 permission_handler 9.2.0
    官方文档地址permission_handler控制台打印提示信息的时候可能会有两种(我遇到的)Noandroidspecificpermissionsneededfor:9可能表示你当前申请的权限你的系统不支持就是备注里nothing的Noandroidspecificpermissionsneededfor:[]9可能表示你没在AndroidM......
  • Android - Jetpack ViewModel源码探秘
    ViewModel使用场景当横竖屏切换时,希望数据不丢失,可以用ViewModel当成存储媒介;可作为Activity&Fragment通讯的媒介;ViewModel的创建//Activity中构建MyViewModelViewModelProvider(this).get(MyViewModel::class.java)//ViewModelProviders类中publicViewModelProvider(@NonNu......
  • Flutter性能优化实践
    作者:王猛猛前言Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的,可以用一套代码同时构建Android和iOS应用,性能可以达到原生应用一......
  • Flutter — 文本为什么可以被编辑?如何自定义编辑的行为?
    通过阅读本文,您将了解到知道在Flutter中关于文本的整体逻辑;可编辑文本包含哪些内容;如何自定义可编辑行为;如何优雅的实现文本表单。前言:在上一篇文章中,我们讲解了Flutter文本的组成部分和Flutter文本渲染到屏幕上的逻辑。文本的输出我们已经分析完成了,那么文本的输入又是怎么样的......
  • 使用flutter_background_service创建后台服务
    介绍flutter_background_service,它是一个在Flutter应用中创建和管理后台服务的库,并提供了一种简单的方式来执行长时间运行的任务。使用方法下面是关于flutter_background_service的使用方法的详细介绍:1、创建服务使用flutter_background_service库,你可以创建一个后台服......
  • 2023年十大最受欢迎的Flutter开源应用程序
    原文出处:https://juejin.cn/post/7245170503798538296在移动应用开发领域,Flutter以其跨平台能力和漂亮的用户界面获得了巨大的人气。随着其开发者社区的不断壮大,Flutter生态系统已经见证了众多开源应用程序的诞生。这些开源应用不仅展示了Flutter的多功能性,而且还为开发者提供......
  • Flutter在字节跳动的现状与工程实践
    Flutter是当前跨平台技术中最火的一项,在提供极好的用户体验的同时,还能解决多端一致性问题,并有效降低人力成本。字节跳动希望把Flutter打造成下一代研发体系,支撑众多App的各种使用场景,为此,团队在Flutter上大力投入,覆盖了引擎技术、平台服务、开发框架等多个维度。Flutter在......
  • Flutter - 加载网络图片的几种方式
    对很多移动应用来说,加载网络图片是很常见的基本功能。Android中常用Glide等图片库。Flutter提供了Image组件来展示不同类型的图片。加载网络图片有几种方式:Image.networkFadeInImage.memoryNetwork使用cached_network_image中的CachedNetworkImage使用Image.network加载图片根据UR......
  • 2023年Flutter开发前景如何,能找到工作吗?
    引言Flutter自诞生之日起,从来都稳坐风口浪尖,关注与争议一直伴随其身。学习一门技术的时候大家最关心的就是发展前景怎么样,学习Flutter的朋友也不例外,那就让我们一起来看看2023年Flutter开发前景到底怎么样吧。Flutter开发前景从上图的数据可以看出,虽然Flutter开发岗位的招聘在减......
  • Flutter 绘制探索 | 操作坐标系范围
    前言在视频【Flutter绘制指南|第二集·坐标系】中,实现了画板区域内的单位坐标系。今天来拓展一下,让坐标系支持变换,比如坐标系的平移和缩放,从而让坐标系的功能更加完备。本文要实现的效果如下,可以通过下方的七个按钮操作坐标系的范围,这样可以查看在当前定义域内的函数曲线,也......