flutter获取数据,flutter 传值

flutter 状态管理 InheritedWidget 原理分析

最近公司做技术分享写的文章的demo

创新互联公司坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都网站设计、做网站、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的准格尔网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!

Flutter中的InheritedWidget状态管理

1.InheritedWidget是什么?

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

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

2.源码分析

InheritedWidget

先来看下InheritedWidget的源码:

abstract class InheritedWidget extends ProxyWidget {   const InheritedWidget({ Key key, Widget child }): super(key: key, child: child);  @override  InheritedElement createElement() =InheritedElement(this);  @protected  bool updateShouldNotify(covariant InheritedWidget oldWidget);}

它继承自ProxyWidget:

abstract class ProxyWidget extends Widget {   const ProxyWidget({ Key key, @required this.child }) : super(key: key);  final Widget child;}

可以看出Widget内除了实现了createElement方法外没有其他操作了,它的实现关键一定就是InheritedElement了。

InheritedElement 来看下InheritedElement源码

class InheritedElement extends ProxyElement {   InheritedElement(InheritedWidget widget) : super(widget);  @override  InheritedWidget get widget = super.widget;  // 这个Set记录了所有依赖的Elementfinal MapElement, Object _dependents = HashMapElement, Object();

//该方法会在Element mount和activate方法中调用,_inheritedWidgets为基类Element中的成员,用于提高Widget查找父节点中的InheritedWidget的效率,它使用HashMap缓存了该节点的父节点中所有相关的InheritedElement,因此查找的时间复杂度为o(1)   @override  void _updateInheritance() {final MapType, InheritedElement incomingWidgets = _parent?._inheritedWidgets;if (incomingWidgets != null)      _inheritedWidgets = HashMapType, InheritedElement.from(incomingWidgets);    else      _inheritedWidgets = HashMapType, InheritedElement();    _inheritedWidgets[widget.runtimeType] = this;  }

//该方法在父类ProxyElement中调用,看名字就知道是通知依赖方该进行更新了,这里首先会调用重写的updateShouldNotify方法是否需要进行更新,然后遍历_dependents列表并调用didChangeDependencies方法,该方法内会调用mardNeedsBuild,于是在下一帧绘制流程中,对应的Widget就会进行rebuild,界面也就进行了更新   @override  void notifyClients(InheritedWidget oldWidget) {    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));for (Element dependent in _dependents.keys) {      notifyDependent(oldWidget, dependent);    }  }

其中_updateInheritance方法在基类Element中的实现如下:

void _updateInheritance() {

_inheritedWidgets = _parent?._inheritedWidgets;

}

总结来说就是Element在mount的过程中,如果不是InheritedElement,就简单的将缓存指向父节点的缓存,如果是InheritedElement,就创建一个缓存的副本,然后将自身添加到该副本中,这样做会有两个值得注意的点:

InheritedElement的父节点们是无法查找到自己的,即InheritedWidget的数据只能由父节点向子节点传递,反之不能。

如果某节点的父节点有不止一个同一类型的InheritedWidget,调用inheritFromWidgetOfExactType获取到的是离自身最近的该类型的InheritedWidget。

看到这里似乎还有一个问题没有解决,依赖它的Widget是在何时被添加到_dependents这个列表中的呢?

回忆一下从InheritedWidget中取数据的过程,对于InheritedWidget有一个通用的约定就是添加static的of方法,该方法中通过inheritFromWidgetOfExactType找到parent中对应类型的的InheritedWidget的实例并返回,与此同时,也将自己注册到了依赖列表中,该方法的实现位于Element类,实现如下:

@overrideT dependOnInheritedWidgetOfExactType

// 这里通过上述mount过程中建立的HashMap缓存找到对应类型的InheritedElement final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];if (ancestor != null) {    assert(ancestor is InheritedElement);return dependOnInheritedElement(ancestor, aspect: aspect);  }  _hadUnsatisfiedDependencies = true;  return null;}

@overrideInheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {  assert(ancestor != null);

// 这个列表记录了当前Element依赖的所有InheritedElement,用于在当前Element deactivate时,将自己从InheritedElement的_dependents列表中移除,避免不必要的更新操作   _dependencies ??= HashSetInheritedElement();  _dependencies.add(ancestor);  ancestor.updateDependencies(this, aspect);return ancestor.widget;}

3.如何使用InheritedWidget

1)、创建一个类继承自Inheritedwidget

class InheritedContext extends InheritedWidget{  final InheritedTestModel inheritedTestModel;  InheritedContext({    Key key,    @required this.inheritedTestModel,    @required Widget child}): super(key: key, child: child);static InheritedContext  of (BuildContext context) {    return context.dependOnInheritedWidgetOfExactTypeInheritedContext();  }  @override  bool updateShouldNotify(InheritedContext oldWidget) {    return inheritedTestModel != oldWidget.inheritedTestModel;  }}

2)、InheritedTestModel类为数据容器(这里定义了一个Listint数据源)

class InheritedTestModel{ final List _list;  InheritedTestModel(this._list);  List getList(){    return _list;  }}

class ArrayListData{  static List  _list ;static List  getListData (){     _list  = new List();     _list .add(1);     _list .add(2);     _list .add(3);     _list .add(4);return  _list ;  }}

3)、定义一个Widget 使用 InheritedContext类的数据 InheritedTestModel 

class ListDemo extends StatefulWidget{  @override  State createState() {    return new ListDemoState();  }}class ListDemoState extends StateListDemo{List _list;  InheritedTestModel _inheritedTestModel;  Timer _timer;  Duration oneSec = const Duration(seconds: 1);  @override  void initState() {    _list = ArrayListData. getListData ();    _inheritedTestModel = new InheritedTestModel(_list);    _timer = Timer.periodic(oneSec, (timer) {      _doTimer();    });  }  void _doTimer() {    for(int i = 0; i  _list.length; i++){      _list[i] = _list[i]+ 1;    }  setState(() {    _inheritedTestModel = new InheritedTestModel(_list);  });  }Widget _buildBody() {    return Container(child: ListDemo2(),    );  }  @override  Widget build(BuildContext context) {    return InheritedContext(inheritedTestModel: _inheritedTestModel,      child: Scaffold(appBar: AppBar(title: Text("ListDemo"),        actions: Widget[            IconButton(icon: Icon(Icons. add ),            )        ],),        body: _buildBody(),      ),    );  }  @override  void dispose() {    super.dispose();if (_timer != null) {      _timer.cancel();    }  }}

4)、在ListDemo中通过Timer更新InheritedTestModel 中的数据,然后再下一个Widget中获取更新的数据作为展示

class ListDemo2 extends StatefulWidget{  @override  State createState() {    return new ListDemoState2();  }}class ListDemoState2 extends StateListDemo2{InheritedTestModel _inheritedTestModel;  Widget _buildListItem(BuildContext context,int index) {    return  Container(height: 50,        width: 100,        alignment: Alignment. center ,        child: Text(_inheritedTestModel.getList()[index].toString()),    );  }Widget _buildBody() {    _inheritedTestModel = InheritedContext. of (context).inheritedTestModel;return Container(child: ListView.builder(itemBuilder:(context, index)=_buildListItem(context,index),itemCount: _inheritedTestModel.getList().length,),    );  }  @override  Widget build(BuildContext context) {    return  _buildBody();  }}

这样就可以在父widget中更新数据,子View不需任何操作直接从数据容器InheritedTestModel 中获取到更新后的新数据

这是一个数据共享的简单的例子,基本的流程,大致就是A去更新B的数据,A和B有一个共同的父类,实现数据的共享

4.上面说了原理和基本的使用,但是在实际项目当中,我当然不建议这样来使用,Google 已经为我们封装好了功能更加强大的插件Provider,其内部原理就是基于InheritedWidget来实现的,我们理解了基本原理,可以更好的在项目中运用Provider

Flutter之Image Widget(六)

Image(图片组件)是显示图像的组件,一个显示图片的widget,支持图像格式:JPEG,PNG,GIF,动画GIF,WebP,动画WebP,BMP和WBM

构造方法

Image: 从ImageProvider获取数据

Image.network: 加载网络图片。

Image.asset: 加载本地图片文件。

new Image.file: 加载本地图片文件(File文件)图片。

new Image.memory: 加载Uint8List资源图片(byte数组)图片。

常用属性

方式一:CircleAvatar

CircleAvatar可以实现圆形头像,也可以添加一个子Widget:

在图片上加一个文本

方式二:ClipOval

ClipOval也可以实现圆角头像,而且通常是在只有头像时使用

方式三:Container+BoxDecoration

方法一:ClipRRect

方法二:Container + BoxDecoration

补充知识点

Icon字体图标和图片图标的区别 ?

Colors.red 是一个MaterialColor对象,为什么可以使用[](Colors.red[10])来设置颜色 ?

MaterialColor 继承于ColorSwatch,ColorSwatch中有[] 运算符重载;

Flutter中InheritedWidget的使用

在Tree中从上往下高效传递数据的基类widget , 定义为:abstract class InheritedWidget extends ProxyWidget

Flutter的响应式开发与React类似,数据都是自顶向下的。

假设有祖先组点A,中间经过结点B, C,然后到结点D,D需要从A中获取数据f,那按照自顶向下数据流转,f需要依次传递给B及C,最后才到C。这样开发极为不灵活,成本也比较高。所有Flutter需要有跨结点(只能是祖先后代节点,不能跨兄弟节点)高效传递数据的方案。

大体意思如下:

InheritedWidget 是在树中高效向下传递信息的基类部件;

调用[BuildContext.inheritFromWidgetOfExactType]方法可以从 BuildContext 中获取到最近的 InheritedWidget 类型的实例;

在 InheritedWidget 类型的控件被引用,也就是调用过 inheritFromWidgetOfExactType 方法后,当 InheritedWidget 自身状态改变时,会导致引用了 InheritedWidget 类型的子控件重构(rebuild)。

这里随便定义一个人 Person 类。

创建一个类继承 InheritedWidget,并实现 updateShouldNotify 方法。

之前说到调用[BuildContext.inheritFromWidgetOfExactType]方法可以从 BuildContext 中获取到最近的 InheritedWidget 类型的实例,所以此处定义一个静态的 of 方法,通过传入的 context 获取到最近的 InheriedDataWidget 实例。

1.定义数据模型

这里随便定义一个 Person 类。

2.自定义 InheritedWidget 控件类

创建一个类继承 InheritedWidget,并实现 updateShouldNotify 方法。

之前说到调用[BuildContext.inheritFromWidgetOfExactType]方法可以从 BuildContext 中获取到最近的 InheritedWidget 类型的实例,所以此处定义一个静态的 of 方法,通过传入的 context 获取到最近的 InheriedDataWidget 实例。

3.InheriedDataWidget 的使用

InheriedDataWidget 使用起来也很简单,它本身也是一个控件,只要在任意一个页面的子控件调用其构造方法就行,这里我们定义一个形如的 Widget 树。

WidgetA 是一个 StatefulWidget 类型的控件,可以调用 setState 刷新,如果是继承人 Stateless 类型的控件,那我们也可以通过 Stream 或者其他方式刷新数据,感兴趣的请看[什么是 Stream? Dart

WidgetA1_1 类

WidgetA1_2 类

WidgetA1_3 类

当我们点击 floatingActionButton 的时候,WidgetA1, WidgetA1_1, WidgetA1_2 的控件都会更新 Person 的信息,而且每点 floatingActionButton 一次, 当我们点击 floatingActionButton 的时候,WidgetA1, WidgetA1_1, WidgetA1_2 的控件都会更新 Person 的信息,而且每点 floatingActionButton 一次,都会输出:

如果我们试图在和 WidgetA 的同一层级的兄弟节点去访问 InheriedDataWidget 的 Person 数据,是不行的,因为父节点中并没有插入 InheriedDataWidget。

把 WidgetB 和 WidgetA 保持同一节点

这也体现了 Inheried(遗传) 这一单词的特性,遗传只存在于父子。兄弟不存在遗传的关系。

这种数据共享的方式在某些场景还是很有用的,就比如说全局主题,字体大小,字体颜色的变更,只要在 App 根层级共享出这些配置数据,然后在触发数据改变之后,所有引用到这些共享数据的地方都会刷新,这换主题,字体是不是就很轻松,事实上 Theme.of(context).primaryColor 之流就是这么干的。

以上就是有关InheritedWidget的使用。

自己也是从事Android开发5年有余了;整理了一些Android开发技术核心笔记和面经题纲,有关更多Android开发进阶技术资料、面经题纲、核心技术笔记; 想要进阶自己、拿高薪的同学请私信我回复“核心笔记”或“面试”领取!

Flutter 之 文件操作(二十九)

Dart的 IO 库包含了文件读写的相关类,它属于 Dart 语法标准的一部分,所以通过 Dart IO 库,无论是 Dart VM 下的脚本还是 Flutter,都是通过 Dart IO 库来操作文件的,不过和 Dart VM 相比,Flutter 有一个重要差异是文件系统路径不同,这是因为Dart VM 是运行在 PC 或服务器操作系统下,而 Flutter 是运行在移动操作系统中,他们的文件系统会有一些差异。

Android 和 iOS 的应用存储目录不同, PathProvider 插件提供了一种平台透明的方式来访问设备文件系统上的常用位置。该类当前支持访问两个文件系统位置:

File代表一个整体的文件,他有三个构造函数,分别是:

文件读取本身有两种形式,一种是文本,一种是二进制。

2.2.1 读取文本内容

如果是文本文件,File提供了readAsString、readAsLines、readAsStringSync、readAsLinesSync方法,读取文本内容

readAsString 一次性读取所有文本

readAsLines 一行行的读取文本

结果返回的是一个List,list中表示文件每行的内容

readAsStringSync、readAsLinesSync同步读取文本

2.2.2 读取二进制内容

如果文件是二进制,那么可以使用readAsBytes或者同步的方法readAsBytesSync:

dart中表示二进制有一个专门的类型叫做Uint8List,他实际上表示的是一个int的List。

上面提到的读取方式,都是一次性读取整个文件,缺点就是如果文件太大的话,可能造成内存空间的压力。

所以File为我们提供了另外一种读取文件的方法,流的形式来读取文件.

示例

dart提供了open和openSync两个方法来进行随机文件读写:

写入和文件读取一样,可以一次性写入或者获得一个写入句柄,然后再写入。

一次性写入的方法有四种,分别对应字符串和二进制

句柄形式可以调用openWrite方法,返回一个IOSink对象,然后通过这个对象进行写入:

默认情况下写入是会覆盖整个文件的,但是可以通过下面的方式来更改写入模式:

虽然dart中所有的异常都是运行时异常,但是和java一样,要想手动处理文件读写中的异常,则可以使用try,catch:

我们还是以计数器为例,实现在应用退出重启后可以恢复点击次数。 这里,我们使用文件来保存数据:

1.引入PathProvider插件;在pubspec.yaml文件中添加如下声明:

执行 flutter pub get

2.实现如下

参考:

flutter provider的理解

provider 是flutter 中的状态管理 开源库;

存储的数据对象 必须extends ChangeNotifier;下层widget 通过 Provider.of(context) 函数 获取model对象 ,并且可以建立依赖关系;当数据对象发生变化时,依赖的widget 会重新build,像不像InheritedWidget Provider 没错 下层widget就是 封装了InheritedWidget

主要 通过 Provider.ofT(context) 函数,来获取;

推荐使用 Provider.of而不是 Consumer,因为 listen默认为true,也就是说 默认 依赖于 持有数据model的widget 对应的element;

数据类 可继承的 ChangeNotifier,本身和privider框架 没有关系;

ChangeNotifier 是 flutter框架 提供的工具类, 用来实现一对多的订阅通知功能。


标题名称:flutter获取数据,flutter 传值
本文路径:http://scyanting.com/article/dsgsosh.html