Flutter学习三-Flutter基本结构和原理

Flutter有三种运行模式
debug 调试模式
profile 分析模式
release 运行模式

调试模式特点:
原生平台:
启用断言
启用调试
编译针对快速开发和运行周期进行了优化(但不针对执行速度、二进制大小或部署)。

网络应用程序
构建没有缩小
该应用程序使用 dartdevc 编译器进行编译,以便于调试。

release模式
基本上就是调试模式反过来
原生版本

web版本
该应用程序是用 dart2js 编译器编译的,以获得最佳性能。

分析模式
容易想到,分析模式和发布模式很接近,就是附近了分析服务或工具

注意第一次在物理设备上运行时,可能需要一段时间才能加载。

项目结构

pubspec.yaml 是 Flutter 项目的配置文件,类似于 Android 中的 Gradle 配置文件,下面我们就看看 pubspec.yaml 中各个属性的配置。
创建一个新的项目(Flutter Application),pubspec.yaml 位于根目录,如图:

核心

渲染引擎

Flutter原来使用skia,2023.1起 ios使用Impeller引擎,Android也会逐渐过渡到Impeller,用它解决skia的一些瓶颈问题。
Skia 是一款用 C++ 开发的、性能彪悍的 2D 图像绘制引擎,其前身是一个向量绘图软件。目前是Android和
Chrome的官方渲染引擎。Flutter打包这两个平台的时候不用打包Skia了,然后IOS等就需要打包,所以打包体积会大些。
然后因为基于Skia,所以Flutter的绘图API和Android是比较像的。

widget

参考资料 : https://juejin.cn/post/6844903784187953165
在 Flutter 中,几乎所有东西都是小部件,包括对齐、填充和布局。

响应式编程的简单了解:

首先,响应式编程和异步编程有关系,异步编程就是发出一个任务,不用等待结果,而是执行其它代码,然后通过某种方式监听到任务结果并对其进行处理。
响应式编程是使用异步数据流进行编程
异步数据流可以是各种事件的数据组成的列表,比如点击,用户输入,IO数据输入,处理运算结果等,但是它们是异步到来的。
响应式编程提高了代码的抽象层级,我们不必关注这些异步处理的细节,只需要把这个异步数据流当成一个数据列表一样处理,可以map,filter、处理这些数据流。
(几乎) 所有的东西都可以转为一个Stream 。这就是Rx的咒语。

widget是flutter的核心类,作用类似于原生的View,用于构建UI界面。但是其内部原理有所不同。
Flutter Widget 采用现代响应式框架构建, Widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。

Widget 唯一标识 - Key

在 Fultter 中,每一个 Widget 都是被唯一标识的。这个唯一标识在 build/rendering 阶段由框架定义。
该唯一标识对应于可选的 Key 参数。如果省略该参数,Flutter 将会为你生成一个。
在某些情况下,你可能需要强制使用此 key,以便可以通过其 key 访问 widget。
为此,你可以使用以下方法中的任何一个:GlobalKey、LocalKey、UniqueKey 或 ObjectKey。
GlobalKey 确保生成的 key 在整个应用中是唯一的。
强制 Widget 使用唯一标识:
GlobalKey myKey = new GlobalKey();

@override
Widget build(BuildContext context){
return new MyWidget(
key: myKey
);
}
这个就类似于Android中的view id了

Widget树的概念

Widget 以树结构进行组织。
包含其他 Widget 的 Widget 被称为父 Widget(或Widget 容器)。包含在父 Widget 中的 Widget 被称为子 Widget。
flutter中使用小部件树 widget tree来实现布局编写。
让我们用 Flutter 自动生成的基础应用来说明这一点。以下是简化代码,仅有 build 方法:

@override
Widget build(BuildContext){
    
    
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
}

如果我们现在观察这个基本示例,我们将获得以下 Widget 树结构(限制代码中存在的 Widget 列表)
在这里插入图片描述

Context 的概念

另外一个重要的概念是 Context。
Context 仅仅是已创建的所有 Widget 树结构中某个 Widget 的所占区域的引用。
如果我们现在尝试在上图中说明 Context 的概念,我们得到(依旧是一个非常简化的视图)每种颜色代表一个 context(除了 MyApp,它是不同的):

无状态Stateless Widget

这些可视化组件除了它们自身的配置信息外不依赖于任何其他信息,该信息在其直接父节点构建时提供。
换句话说,这些 Widget 一旦创建就不关心任何变化。
这样的 Widget 称为 Stateless Widget。
当所描述的用户界面部分不依赖于对象中的配置信息以外的任何东西时,StatelessWidgets 非常有用。

Stateless Widget 的生命周期是相当简单的:
初始化
通过 build() 渲染

有状态widget Stateful Widget

Widget 所持有的数据集在其生命周期内可能会发生变化,这样的数据被称为 State。

如果我们使用有状态widget,当widget的状态发生变化时,调用setState方法,widget会重新构建UIFlutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改。Flutter 不会改变旧的实例 b,而是构造新的 Widget 实例。框架使用 RenderObjects 在后台完成传统 UI 对象的许多职责(比如维护布局的状态)。

注意:Flutter 的小部件是轻量级的,部分原因在于它们的不变性。因为它们不是视图本身,也不直接绘制任何东西,而是对 UI 及其语义的描述,渲染的时候这些描述被“膨胀”成了实际的视图对象。

Stateful Widget 由 2 部分组成

第一部分 widget**

class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({
Key key,
this.color,
}): super(key: key);

final Color color;

@override
_MyStatefulWidgetState createState() => new _MyStatefulWidgetState();

}
第一部分 “MyStatefulWidget” 通常是 Widget 的 public 部分。当你需要将其添加到 widget 树时,可以实例化它。该部分在 Widget 生命周期内不会发生变化,但可能接受与其相关的 State 实例化时使用的参数。
请注意,在 Widget 第一部分定义的任何变量通常在其生命周期内不会发生变化。

第二部分 Widget State

class MyStatefulWidgetState extends State {

@override
Widget build(BuildContext context){

}
}
MyStatefulWidgetState” 管理 Widget 生命周期中的变化,并强制每次应用修改时重建该 Widget 实例。名称开头的 ‘’ 字符使得该类对 .dart 文件是私有的。
如果你需要在 .dart 文件之外引用此类,请不要使用 ‘
’ 前缀。
_MyStatefulWidgetState 类可以通过使用 widget.{变量名称} 来访问被存储在 MyStatefulWidget 中的任何变量。在该示例中为:widget.color。
应该是这样的,刷新页面时,会重新创建widget对象,但是state对象不会重新创建。

Widget State的周期和内部函数

initState()

一旦 State 对象被创建,initState() 方法是第一个(构造函数之后)被调用的方法。当你需要执行额外的初始化时,该方法将会被重写。常见的初始化与动画、控制器等相关。如果重写该方法,你应该首先调用 super.initState()。
更加复杂耗时的初始化也可以放在这里?

build()

build(BuildContext context) 方法在 didChangeDependencies()(及 didUpdateWidget)之后被调用。

这是你构建你的 widget(可能还有任何子树)的地方。

每次 State 对象更新(或当 InheritedWidget 需要通知“已注册” widget)时都会调用该方法!!

setState 更新状态

通过setState传递一个类似于run的函数进入,框架就会run这个函数,然后调用build函数构建widget,刷新界面

didChangeDependencies()

didChangeDependencies() 方法是第二个被调用的方法。
在这一阶段,由于 context 是可用的,所以你可以使用它。
如果你的 Widget 链接到了一个 InheritedWidget 并且/或者你需要初始化一些 listeners(基于 context),通常会重写该方法。
请注意,如果你的 widget 链接到了一个 InheritedWidget,在每次重建该 Widget 时都会调用该方法。
如果你重写该方法,你应该首先调用 super.didChangeDependencies()。

dispose()

dispose() 方法在 widget 被废弃时被调用。

如果你需要执行一些清理操作(比如:listeners),则重写该方法,并在此之后立即调用 super.dispose()。

第二部分:如何访问 State

访问直接子 Widget

有时,父 widget 可能需要访问其直接子节点的 State 才能执行特定任务。
在这种情况下,要访问这些直接子节点的 State,你需要了解它们。
呼叫某人的最简单方法是通过名字。在 Flutter 中,每个 Widget 都有一个唯一的标识,由框架在 build/rendering 时确定。如前所示,你可以使用 key 参数为 Widget 强制指定一个标识。

GlobalKey myWidgetStateKey = new GlobalKey();

@override
Widget build(BuildContext context){
return new MyStatefulWidget(
key: myWidgetStateKey,
color: Colors.blue,
);
}
复制代码一经确定,父 Widget 可以通过以下形式访问其子节点的 State:
myWidgetStateKey.currentState

基本使用

小部件的主要工作是提供一个 build ()方法,该方法描述如何按照其他较低级别的小部件来显示小部件。

保证 build 方法无副作用

build应该是类似于Android ondraw 不能在里面做消耗内存的事,创建对象等
Build 无副作用也通常被人叫做,build 保持 pure,二者是同一个意思。

通常我们经常会看到,为了获取顶层数据我们会在 build 方法中调用 XXX.of(context) 方法。你必须非常小心,你的 build 函数不应该产生任何副作用,包括新的对象(Widget 以外),请求网络,或作出一个映射视图以外的操作等。

这是因为,你的根本无法控制什么时候你的 build 函数将会被调用。(我可以说随时)每当你的 build 函数被调用,那么都会产生一个副作用。这将会发生非常恐怖的事情。

视图渲染构建原理

Flutter中,widget保存的是视图的状态,配置信息配,它永远是immutable的。那真正显示在屏幕上的视图是什么呢?Element !Element会构成视图树。
当要把这个widget装进视图树的时候,首先会去createElement,并将当前widget传给Element。

先看无状态视图构建方式

StatelessElement定义
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);

@override
StatelessWidget get widget => super.widget;

@override
Widget build() => widget.build(this);

@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}

StatelessElement保留了widget的引用,并且将会调用build方法,而这个build方法真正调用的则是widget的build方法,并将this,也就是该StatelessElement对象传入。

实际上是Element类实现了BuildContext。

所以我们可以把BuildContext看做真正视图的基础类。

对于StatefulWidget

首先同样也是调用StatefulWidget的 createElement方法,并根据这个widget生成StatefulElement对象,并保留widget引用。
将这个StatefulElement挂载到Element树上。
根据widget的 createState 方法创建State。
StatefulElement对象调用state的build方法,并将element自身作为BuildContext传入。

flutter构建页面的一些特点

使用flutter构建页面,相对Android灵活得多,它完全基于代码构建,而不是类似Android那样需要加载xml,再构建页面。还有它是单页面应用,不需要AC跳转等的支持等原因。
这使得flutter的页面可以用代码轻易的进行改变,只需要一个bool设置,然后执行不同的代码块,页面就完全换了。如果在Android中这个要复杂得多,要重新加载xml,然后再装填数据,等等,切换成本很高。
由此,我们在设计flutter页面的时候,一些思想也需要转过来,就是
1、可以在一个页面下通过变量轻易的控制展示不同的外观,而不像Android那样收到局限,Android的设计思想中页面是固定的,flutter中可以大胆的,勇敢的去换,只要不是频繁的切换影响性能。
2、可以很容易的进行页面复用,在Android中这是很麻烦的,需要专门写一个自动View,但是flutter直接丢个组件类过去就可以了,可以把一个页面、View组件,丢掉任意其它位置轻易的进行复用。

猜你喜欢

转载自blog.csdn.net/hn_lgc/article/details/126113105