如何理解Flutter的三棵树
Widget树
由开发者直接绘制的由Widget组成的配置树。一旦创建就是不可变的,只会频繁地重建,并且由重建决定Element和RenderObject的创建和更新
Element树
是Widget的实例化,负责管理Widget和RenderObject,处理UI的更新和生命周期
RenderObject树
由RenderObject组成的树,负责处理实际的布局和渲染
如何理解Flutter中的context
Context是Flutter中Widget的核心,是BuildContext的实例,代表了当前Widget在Widget树中的位置,
- 确定Widget在Widget树中的位置
- 访问资源和服务
- 构建Widget
- 查找祖先Widget
- 判断Widget的生命周期
解释 Stateful Widget Lifecycle ?
- 创建阶段:
createState->initState->didChangeDependencies->build。 - 更新阶段:
didUpdateWidget->build。 - 销毁阶段:
deactivate->dispose。
createState()
当Widget被插入到Widget树中时被调用,会创建一个与之关联的state对象
initState()
在state对象被创建后,并且build方法被调用之前,用于进行初始化状态等一次性动作
didChangeDependencies()
- 在initState之后立即调用
- 当State对象依赖的InheritedWidget发生变化时调用
用于处理依赖InheritedWidget的数据或者依赖于上下文的操作
build()
每当State对象需要重新构建UI的时候调用,会构建并且返回描述UI的widget树
didUpdateWidget()
当父组件重建并且传入一个新的widget时,但State对象保持不变,主要用于响应Widget的更新,可能会根据新的Widget更新状态
setState()
根据新的State对象重新构建UI,触发build方法重新构建UI
deactivate()
当State对象从树中移除的时候调用,用于清理资源和保存状态
dispose()
当State对象从树中永久移除时,用于释放资源
InheritedWidget是什么
是一个特殊的Widget,用于在Widget树中传递数据。允许任意子Widget访问他祖先Widget中的数据,并且当InheritedWidget的数据发生改变时,依赖于这个数据的子Widget会自动创建,从而实现状态管理和数据共享
class MyTest extends InheritedWidget{
final int data;
const MyTest(this.data, {super.key, required super.child});
// of方法 通过context从最近的context中获取MyTest实例
// 如有就返回
// 如果没有就返回null
static MyTest? of(BuildContext context){
return context.dependOnInheritedWidgetOfExactType<MyTest>();
}
// 通过数据判断是否应该通知子Widget
@override
bool updateShouldNotify(covariant MyTest oldWidget) {
return oldWidget.data!=data;
}
为什么build方法要在State上而不是在stf上
-
将状态管理与UI构建分开:
- Widget是不可变的,所以每次stf重建时都会重新创建一个实例,如果build方法放在stf里,那么每次重建都会重新调用build,造成性能浪费
- State是持久的,stf重建时不会被销毁,而是会被保留。在build方法里能使用这些状态重构UI,如果是局部更新,会根据diff算法更新部分,优化绘制性能
-
State有自己的生命周期,开发者可以适当时机处理操作,如果build方法在stf中,那就无法协同操作了。
-
数据与UI分离的开发思维
Flutter中有几种key
主要用于决定Widget是否会被刷新
LocalKey
用于在局部Widget树中标识某一个Widget
-
ValueKey
使用基础数据作为标志的key,一般用在列表多一点
-
ObjectKey
使用对象作为标志的key,一般用于需要用对象类型作为标志的场景
-
UniqueKey
保证了key的唯一性,一般用于需要确保该对象唯一的情况下
GlobalKey
全局唯一的key,可以在整个Widget树中唯一标记一个Widget,可以通过该key获取到对应Widget的State对象
GetX
Get.put和 Get.find
通过_insert方法进行插入到缓存的一个map中
通过find方法从map中获取到对应的实例
Obs实现原理
包装成rx类,内部持有对应的数据以及提供了监听和更新的机制
- Obx控件中包含了RxNotifier对象,在initState回调里进行监听,当数据有变化时会调用_updateTree方法进行更新
- Obs封装了一个可观察的对象,当值发生变化时,会通过update方法通知所有依赖它的组件或监听器
GetBuilder实现原理
GetBuilder 是 GetX 中基于 GetXController 和 StatefulWidget 的状态管理方式。 它通过 update() 方法手动触发 UI 更新,适用于需要精确控制更新的场景。 与 Obs 相比,GetBuilder 更加轻量,适合简单的状态管理需求。
GetBuilder** 通过观察者模式实现状态更新**:它监听Controller的状态变化,当Controller调用update()时,通知GetBuilder刷新 UI。- 基于
StatefulWidget和 ****setState:GetBuilder内部使用StatefulWidget,通过setState触发局部 UI 更新,确保高效渲染。 - 局部更新,性能优化:
GetBuilder只更新其包裹的 UI 部分,避免全局刷新,提升应用性能。
Flutter渲染流程
- 在Dart framework中构建Widget树。
- 调用rendering库,将Widget树转换为Element树。
- 再将Element树转换为RenderObject树,进行布局和绘制。
- 通过Skia引擎,将RenderObject树转换为GPU命令,并提交给GPU执行。
- GPU执行这些命令,最终将渲染结果显示在屏幕上。
Flutter屏幕适配
原生自己适配
通过MediaQuery获取屏幕的宽高,自己去做适配
final mediaQueryData = MediaQuery.of(context);
final screenWidth = mediaQueryData.size.width;
final screenHeight = mediaQueryData.size.height;
使用第三方库
flutter_screenutil: ^5.9.3
在根入口包裹App,给定设计稿尺寸
return ScreenUtilInit(
designSize: const Size(750, 1334),
builder: (context, child) {
return GetMaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
});
后续使用 .w以及.h拓展,在对应的num拓展里进行了屏幕适配
double get w => ScreenUtil().setWidth(this);
double setWidth(num width) => width * scaleWidth;
double get scaleWidth => !_enableScaleWH() ? 1 : screenWidth / _uiSize.width;
与原生通信的三种渠道
BasicMessageChannel
基础数据传递,可双向通信
MethodChannel
用于传递方法调用,可以有返回值,适用于一次性的通信,可双向通信
EventChannel
用于数据流通信,支持原生侧主动向flutter发送数据流
String StringBuilder StringBuff有什么区别
| 特性 | String | StringBuilder | StringBuff |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全性 | 安全 | 不安全 | 安全 |
| 性能 | 低 | 高 | 有同步开销 |
extends和implements和Mixins的区别
| 特性 | extends | implements | Mixins |
| 作用 | 创建一个类继承另一个类的属性和方法 | 实现一个接口,强制类实现接口中定义的所有方法和属性 | 将代码片段注入到类中,实现代码复用 |
| 多继承 | 不支持,只能继承一个父类 | 支持,可以实现多个接口 | 支持,可以混入多个 Mixin |
| 使用场景 | 用于扩展已有类的功能 | 用于实现接口,强制类遵循特定规范 | 用于将代码片段注入到多个类中,实现代码复用 |
setState的作用
调用 setState() 会将当前组件标记为“脏”(Dirty),表示需要重新构建,Flutter 框架会在下一帧渲染时,调用 build() 方法重新构建组件,Flutter 使用高效的 Diff 算法,只更新发生变化的部分,而不是整个 UI
diff算法
- 构建新的 Widget 树
调用 build() 方法生成新的 Widget 树。
- 比较新旧 Widget 树
从根节点开始,递归比较新旧 Widget 树的每个节点。
比较的依据:
类型:如果 Widget 的类型不同,则移除旧 Widget 并创建新 Widget。
Key:如果 Widget 的 Key 不同,则移除旧 Widget 并创建新 Widget。
属性:如果 Widget 的类型和 Key 相同,但属性不同,则更新对应的 RenderObject。
- 更新 Element 树
根据比较结果,更新 Element 树:
如果 Widget 相同,则复用对应的 Element。
如果 Widget 不同,则移除旧的 Element 并创建新的 Element。
- 更新 RenderObject 树
根据 Element 树的变化,更新 RenderObject 树:
更新布局、绘制和合成等操作。
如何获取控件的大小和位置?
- 使用Key拿到上下文取得findRenderObject拿内容的尺寸数据;
- 使用context取得findRenderObject拿内容的尺寸数据;