那么通过实现RenderObject的方式和上面介绍的通过CustomPaint和Canvas自绘的方式有什么区别?
Canvas 是真正负责绘制的“画布”;CustomPainter 是开发者编写绘制逻辑的地方;CustomPaint 是连接 Widget 层和 Canvas 的桥梁。
那么通过实现RenderObject的方式和上面介绍的通过CustomPaint和Canvas自绘的方式有什么区别?
Canvas 是真正负责绘制的“画布”;CustomPainter 是开发者编写绘制逻辑的地方;CustomPaint 是连接 Widget 层和 Canvas 的桥梁。
9.2.1 动画基本结构
整个 Flutter 动画的执行过程可以概括为:
Ticker 随屏幕刷新驱动动画。
AnimationController 根据时间产生动画进度(通常为 0~1)。
CurvedAnimation(可选)对动画进度进行曲线映射,使动画具有加速、减速、弹性等效果。
Tween 将映射后的进度转换为实际业务数据(如位置、透明度、颜色、大小等)。
Animation 保存当前计算结果,并通过 addListener() 通知界面刷新。
Widget 在 build() 中读取 animation.value 更新 UI,连续绘制每一帧,从而形成流畅动画。
一句话概括它们的关系:
Ticker 提供每帧时钟 → AnimationController 控制动画进度 → Curve 调整进度变化规律 → Tween 将进度映射为具体属性值 → Animation 保存当前值并通知 UI 刷新,从而实现动画效果。
Flutter 中显式动画的典型使用流程可以概括为:
创建 AnimationController:负责控制动画的时长、播放和停止。
(可选)创建 CurvedAnimation:为动画添加加速、减速等曲线效果。
创建 Tween 并调用 animate():将 0~1 的动画进度映射到实际需要的数值或对象。
监听动画变化:通过 addListener() 调用 setState(),让界面在每一帧重建。
启动动画:调用 forward()、reverse()、repeat() 等方法。 在 build() 中使用 animation.value 更新 Widget 属性,最终呈现连续的动画效果。
9.1.1 动画基本原理
整个 Flutter 动画的执行过程可以概括为:
Ticker 随屏幕刷新驱动动画。
AnimationController 根据时间产生动画进度(通常为 0~1)。
CurvedAnimation(可选)对动画进度进行曲线映射,使动画具有加速、减速、弹性等效果。
Tween 将映射后的进度转换为实际业务数据(如位置、透明度、颜色、大小等)。
Animation 保存当前计算结果,并通过 addListener() 通知界面刷新。
Widget 在 build() 中读取 animation.value 更新 UI,连续绘制每一帧,从而形成流畅动画。
一句话概括它们的关系:
Ticker 提供每帧时钟 → AnimationController 控制动画进度 → Curve 调整进度变化规律 → Tween 将进度映射为具体属性值 → Animation 保存当前值并通知 UI 刷新,从而实现动画效果。
数据发生变化怎么通知? 谁来重新构建InheritedProvider?
InheritedWidget 只能通知变化,它不能产生变化。它只负责保存数据,通知依赖者。所以需要在数据变化时通知InheritedWidget 数据变化了也就是创建一个新的InheritedWidget(data不同)此时就可以rebuild了。
第一,数据变化后如何通知 Flutter(例如 ChangeNotifier.notifyListeners());
第二,由谁负责重新创建 InheritedWidget(通常是一个 StatefulWidget,通过 setState() 重新 build 出新的 InheritedProvider)。
定制 RenderObject
RenderObject派生出了两个类一个是RenderBox还有一个是RenderSliver
Sliver 的布局协议如下:
Sliver 不是列表里的一个 item。
一个 Sliver 更像是一段可滚动区域的布局管理器,它里面可以包含很多 item。 一个Viewport可以管理多个sliver
也是 Scrollable 、 Viewport 和 Sliver 所占用的空间
Scrollable 不直接包含 Viewport它通过 builder 创建viewport。
Viewport 有一个:List<Widget> slivers。Viewport 管理 Sliver。
父子关系为:Sliver 父组件为 Viewport,Viewport的 父组件为 Scrollable
Scrollable:负责滚动过程(手势、offset、动画)
Viewport:负责显示范围(当前应该看哪里)
Sliver:负责内容布局(有哪些东西、大小多少、创建哪些子项)
用户滑动:
手指
|
↓
Scrollable
|
| 产生 offset
↓
Viewport
|
| 根据 offset 请求内容
↓
Sliver
|
| 创建并布局需要显示的child
↓
屏幕显示
注意重新构建 Viewport 并不是一个昂贵的操作,因为 Viewport 本身也是 Widget,只是配置信息,Viewport 变化时对应的 RenderViewport 会更新信息,并不会随着 Widget 进行重新构建
viewportBuilder 在滚动时重新生成 Viewport Widget,但 Widget 只是轻量的配置对象,Flutter 会复用已有的 Element 和 RenderViewport,只更新 RenderViewport 的 offset 等参数,因此不会重新创建整个视图树,所以这个操作开销很小。
在线性布局中,有两个定义对齐方式的枚举类MainAxisAlignment和CrossAxisAlignment,分别代表主轴对齐和纵轴对齐。
mainAxisAlignment = 东西放在哪里
verticalDirection = 上下方向是从哪边开始排
textDirection = 左右方向是从哪边开始排
Form的子孙元素必须是FormField类型
Form 只能注册它下面的 FormField 类型的后代作为表单字段,form可以管理这些FormField
继承StatefulWidget不便。
如果 build 放在 StatefulWidget 中,那么继承 StatefulWidget 的子类为了实现 build,需要访问父类的 State,而父类 State 属于内部实现,不应该暴露。这样会破坏封装,并增加无意义的状态传递机制。
5. 小结
H5 + 原生(Hybrid) 的核心优势在于开发效率高、动态更新方便(无需重新发布 App,就可以修改应用中的部分内容。)、成本低,但由于依赖 WebView,在性能和用户体验方面相对较弱,更适合内容展示类应用。 JavaScript 开发 + 原生渲染(React Native、Weex) 的核心优势在于使用原生控件渲染、性能接近原生、用户体验更好,同时仍能保持一套 JavaScript 代码跨平台的开发模式,更适合交互复杂、对性能要求较高的移动应用。
思考题:Diff操作和DOM批量更新难道不应该是浏览器的职责吗?第三方框架中去做合不合适?
真实 DOM(Real DOM)由浏览器维护,是页面真正显示的节点。 Virtual DOM 由 React 维护,是 React 用来描述 UI 的 JavaScript 对象。 React 通过比较新旧 Virtual DOM(Diff),计算出最小修改,再调用浏览器提供的 DOM API 去更新真实 DOM,最终由浏览器完成布局、绘制和显示。
Diff 操作不应该由浏览器完成,因为浏览器不了解应用状态和组件树,只能看到最终的 DOM 操作;而框架掌握了完整的状态变化过程,因此能够计算出最小的更新范围。