189 8069 5689

flutter树,flutter 树

Flutter自定义绘制组件

Flutter中自定义组件一般有两种方式:

让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:空间域名、虚拟空间、营销软件、网站建设、海州网站维护、网站推广。

CustomPaint继承自SingleChildRenderObjectWidget,即它可以在通过嵌套引入到widget树中,并且可以有一个child子widget。它的构造方法如下:

painter和foregroundPainter需要接收CustomPainter对象,是CustomPaint核心。CustomPainter是进行UI绘制的核心类,绘制时, CustomPaint 首先在画布上调用 painter绘制 , 然后再绘制它的 child Widget, child 绘制完成后再调用 foregroundPainter 进行绘制。

size属性标识绘制区域大小,但当CustomPaint有child,该属性将会忽略,而使用child的大小为绘制区域大小。

isComplex和willChange用于控制绘制层缓存处理的,这里暂不讨论。

可实现CustomPainter子类进行UI绘制

实现paint方法进行真正的绘制,canvas是画布对象,size是绘制区域,是从CustomPaint中size属性传递得到的。绘制过程与Android原生开发十分类似,连API都十分相像,这点对熟悉Android原生开发者真是太友好了。

Paint对象是画笔对象,就是绘图工具,我们可以设置画笔的颜色、粗细、是否抗锯齿、笔触形状以及作画风格等,通过这些属性我们可以很方便的来定制自己的UI效果,在绘制的过程中可以定义多个画笔,以便实现多种风格图形的集合。

根据需求选择合适的画笔属性,完成你的绘制。

Canvas是绘制的画布,它包含了很多绘制方法,可以绘制出各种形状的图形。需要注意的是,画布是应用所有控件都在使用的, 所以通过这个画布其实是可以绘制充满屏幕的内容的,每次绘制都应该限制在本控件的区域(Size)内, 以免绘制覆盖到其他组件。

下面介绍下Canvas的绘制方法:

PointMode是个枚举

p1、p2为线段两个端点

Rect定义矩形的大小位置,有多种构造方式:

RRect描述圆角矩形,他通过Rect和Radius来构造

画圆比较简单,c表示圆心位置,radius是半径。

椭圆使用外接矩形确定大小位置,rect就是外接矩形。

绘制弧形,先确定弧形对应的椭圆,同样地用外接矩形rect确定椭圆,然后根据起始点和结束点角度来确定那一段弧度,startAngle,sweepAngle分别代表起始和结束点角度,角度用弧度表示法。

useCenter表示是否连接闭合形状,userCenter = false表示不闭合,即画一段弧线,userCenter = true表示闭合,即绘制一个扇形。

绘制路径,关键在于构建路径Path,可以直接new Path对象,然后通过path方法可以连接出图形,path关键方法如下:

还有其他方法,有兴趣可以查看API。

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 页面的生命周期(转)

转自

在 Flutter 中,有两类常用的 Widget:

在开发过程中,我们经常需要继承它们两来实现自己的 Widget。

一个 StatelessWidget 是不能被改变的,比如: Icon 、 Text 等。

如果你的控件一旦显示,就不需要再做任何的变更,那么你应该使用 StatelessWidget 。

实现一个自己的 StatelessWidget 很简单。

当你看到下面这个例子?时,你就知道它有多简单了。

看,只要在 build() 中返回你的视图就可以了。

一个 StatefulWidget 是有状态的,可变的。

它可以改变自己的外观,以响应用户的操作或者数据的变化。

比如: CheckBox 、 Switch ..

我们之所以能够改变一个 StatefulWidget ,是因为它有一个设置状态的函数:

调用这个函数后,就会触发 StatefulWidget 的视图树重建。

因此,当我们需要一个可交互的,即能根据用户操作或数据变化而改变视图的 Widget 时,那就得用上 StatelessWidget 了。

现在,来创建一个自定义的 StatefulWidget:

从上面的例子中可以看到, StatefulWidget 会要求提供一个含有视图树的 State 。

既然 State 能够控制一个视图的状态,那它肯定会有一系列的生命周期。

上图就是 State 的生命周期图。

Flutter初步探索(二)使用Tabs

在Material的设计准则里面,tabs是一个常用的模块。Flutter里面包含了 material library 创建tab布局的简便方法

为了使tab起作用,我们需要保持选中的tab和相关内容同步。这就是 TabController 的职责。

我们可以手动创建 TabController ,也可以使用 DefaultTabController 小部件。使用 DefaultTabController 是最简单的选项,因为它将为我们创建一个 TabController ,并使它可用于所有子类Widget。

现在我们已经有个一个 TabController ,我们可以 TabBar Widget去使用创建我们的tab。在这个示例中,我们将会在一个 AppBar 下.创建一个包含3个 Tab Widgets 的 TabBar 。

默认情况下, TabBar 在Widget树中查找最近的 DefaultTabController 。如果是手动创建的 TabController ,则需要将其传递到“TabBar”。

既然我们有了选项卡,那么我们就需要在选择选项卡时显示相关的内容。因此,我们将使用 TabBarView Widget.

备注: 顺序很重要,必须与 TabBar 中的选项卡的顺序相对应!

1. Flutter初步探索(二)使用Tabs

1. Working with Tabs

Flutter基础篇——常用Widget

对于初学flutter的朋友来说,要知道,flutter的UI万物皆Widget。

flutter所写的页面的结构可以被看成套娃,一层套一层,一层套一层,一层套一层。。。。。。

Flutter Widget采用现代响应式框架构建,这是从 React 中获得的灵感,中心思想是用widget构建你的UI。 Widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时,widget会重新构建UI,Flutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改。

Text : 该 widget 可让创建一个带格式的文本。

Row 、 Column : 这些具有弹性空间的布局类Widget可让您在水平( Row )和垂直( Column )方向上创建灵活的布局。

Stack :取代线性布局 (和Android中的LinearLayout相似),Stack允许子 widget 堆叠, 你可以使用 Positioned 来定位他们相对于 Stack 的上下左右四条边的位置。

Container : Container 可让您创建矩形视觉元素。 您可以为 Container 装饰一个 BoxDecoration , 如 background、一个边框、或者一个阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container 可以使用矩阵在三维空间中对其进行变换。

具体的演示见我另外的博客

有一部分Widget都有一个 child 属性,用于容纳唯一的子Widget。

例如:Container、Center、Padding、Align等Widget。

还有一部分Widget允许存在多个子Widget,用 children 作为属性。

例如:Row、Column、Stack等Widget。

在StatefulWidget调用createState之后,框架将新的状态插入树种,然后调用状态对象的initState。子类化State可以重写initState,以完成仅需要一次执行的工作。当然在initState的实现中需要调用super.initState

当一个状态对象不再需要时,框架调用状态对象的dispose。也可以通过覆盖dispose方法来执行清理工作。

OVER~

Widget总结

Flutter中Widget,State和BuildContext的概念是每个Flutter开发人员需要完全理解的最重要概念之一。这里先讲解一下Widget以及Widget。三者之间的关系会在最后一篇总结一下。

Widget类在Flutter中是非常重要的,继承自Widget类的有PreferredSizeWidget、ProxyWidget、RenderObjectWidget、StatefulWidget、StatelessWidget。我们日常使用的绝大部分widget都是继承自Widget类,查看Widget类源码,内部实现非常简单,构造函数如下:

在flutter中构建APP是由widget树构建起来的,所以这个key的作用是用来控制在widget树中替换widget的时候使用的。其中Key类是Widget、Element以及SemanticsNode的唯一标识符,继承自Key的还有LocalKey以及GlobalKey。详细可以去framework.dart文件查看相关源码及说明。

在Flutter中,我们平时自定义的widget,一般都是继承自StatefulWidget或StatelessWidget(并不是只有这两种),这两种widget也是目前最常用的两种。如果一个控件自身状态不会去改变,创建了就直接显示,不会有色值、大小或者其他属性的变化,这种widget一般都是继承自StatelessWidget,常见的有Container、ScrollView等。如果一个控件需要动态的去改变或者相应一些状态,例如点击态、色值、内容区域等,那么一般都是继承自StatefulWidget,常见的有CheckBox、AppBar、TabBar等。两者的差别在于是否有状态。

对于StatelessWidget,build方法会在如下三种情况下调用:

我们在创建State的时候可以看到和StatefulWidget相似的build方法,也就是说我们也可以获得一个BuildContext,在使用StatefulWidget.createState创建它们之前以及在调用initState之前,框架将State对象与BuildContext关联起来,该关联是永久的:State对象永远不会改变它的BuildContext(但是BuildContext本身可以在控件树中移动)。后面讲解一下这个BuildContext对象在整个程序中什么角色

State的作用有两点:

State的生命周期有四种状态:

完整生命周期如下:

当控件的配置被更改时会调用State.didUpdateWidget方法,此时框架会重新绘制控件。你也可以使用State.setState方法在状态发生变化时通知框架,告诉框架该对象的内部状态已经改变,框架接到通知后也会重新绘制控件。

State中比较重要的一个方法是setState,当修改状态时,widget会被更新。比方说点击CheckBox,会出现选中和非选中状态之间的切换,就是通过修改状态来达到的。查看setState源码,在一些异常的情况下将会抛出异常:

markNeedsBuild内部,则是通过标记element为diry,在下一帧的时候重建(rebuild)。可以看出setState并不是立即生效,它只是将widget进行了标记,真正的rebuild操作,则是等到下一帧的时候才会去进行。

StatefulWidget的两个主要类别:

在我的小部件的生命周期中,我是否需要考虑一个将要更改的变量,何时更改,将强制重建小部件?

如果问题的答案是肯定的,那么您需要一个有状态的小部件,否则,您需要一个无状态小部件。

比如:


当前题目:flutter树,flutter 树
浏览路径:http://cdxtjz.cn/article/dsgopeo.html

其他资讯