RenderObject

每个Element都对应一个RenderObject,我们可以通过Element.findRenderObject()来获取。并且我们也说过RenderObject的主要职责是Layout和绘制,所有的RenderObject会组成一棵渲染树Render Tree

  • RenderObject就是渲染树中的一个对象,它拥有一个parent和一个parentData 插槽(slot),所谓插槽,就是指预留的一个接口或位置,这个接口和位置是由其它对象来接入或占据的,这个接口或位置在软件中通常用预留变量来表示,而parentData正是一个预留变量,它正是由parent 来赋值的,parent通常会通过子RenderObject的parentData存储一些和子元素相关的数据,如在Stack布局中,RenderStack就会将子元素的偏移数据存储在子元素的parentData中(具体可以查看Positioned实现)。
  • RenderObject类本身实现了一套基础的layout和绘制协议,但是并没有定义子节点模型(如一个节点可以有几个子节点,没有子节点?一个?两个?或者更多?)。 它也没有定义坐标系统(如子节点定位是在笛卡尔坐标中还是极坐标?)和具体的布局协议(是通过宽高还是通过constraint和size?,或者是否由父节点在子节点布局之前或之后设置子节点的大小和位置等)。
  • Flutter提供了一个RenderBox类,它继承自RenderObject,布局坐标系统采用笛卡尔坐标系,这和Android和iOS原生坐标系是一致的,都是屏幕的top、left是原点,然后分宽高两个轴,大多数情况下,我们直接使用RenderBox就可以了,除非遇到要自定义布局模型或坐标系统的情况。

RenderObejct继承关系

通过 Android Studio 的 Hierarchy 功能可以直观地对类继承关系进行查看:
RenderObject、GlobalKey - 图1

  • RenderBox A render object in a 2D Cartesian coordinate system.
    采用2D笛卡尔坐标系中的渲染对象。它实现了一个内在的尺寸调整协议,它允许您在没有完全铺设的情况下测量一个子级,以这样的方式,如果该子级改变了尺寸,父级将再次布置(考虑到子级的新尺寸)。若对坐标系统没有限制,可直接继承它来实现自定义RenderObject。
  • RenderView
    The root of the render tree.渲染对象树的根。它有单独的子级,它必须是一个RenderBox。因此,如果你想在渲染树中有一个自定义的RenderObject子类,你有两种选择:你可能需要替换RenderView本身,或者你需要一个RenderBox作为它的子类。
  • RenderAbstractViewport
    An interface for render objects that are bigger on the inside.内部较大的渲染对象的界面。某些渲染对象(如RenderViewport)显示其内容的一部分,可以通过ViewportOffset进行控制。这个接口允许框架识别这些呈现对象并与它们交互,而不需要了解所有不同类型的视图。主要处理滑动相关控件的展示。
  • RenderSliver
    Base class for the render objects that implement scroll effects in viewports.在视图中实现滚动效果的渲染对象的基类。Sliver有细片、薄片之意,在Flutter中,Sliver通常指可滚动组件子元素(就像一个个薄片一样)。只有当Sliver出现在视口中时才会去构建它,这种模型也称为“基于Sliver的延迟构建模型”。
    RenderViewport有一组子Sliver。每个Sliver(字面意思是视图内容的一部分)依次排列,覆盖过程中的视图(每个Sliver每次都被放置,包括那些由于“滚动”或超出了视图端口的末端而没有区段的Sliver。)。而RenderSliver则控制着Sliver的绘制渲染。

此外RenderObjec还有两个常用的mixin:

  • RenderObjectWithChildMixin 用于为只有 1 个 child 的 RenderObject 提供 child 管理模型。
  • ContainerRenderObjectMixin 用于为有多个 child 的 RenderObject 提供 child 管理模型。

基本上每个 RenderBox 都混入了他们,省去了自己管理 child 的代码。

Widget、Element及RenderObject关系

  • Widget实际上就是Element的配置数据,Widget树实际上是一个配置树,而真正的UI渲染树是由Element构成。Widget只是描述显示元素的一个配置数据,真正代表屏幕上显示元素的类是Element。
  • 一个Widget对象可以对应多个Element对象。(相同的widget可以同时存在)
  • UI树由一个个独立的Element节点构成。组件最终的Layout、渲染都是通过RenderObejct来完成的,从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObejct并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。
  • 我们可以认为Flutter的UI系统包含三棵树:Widget树、Element树、渲染树。他们的依赖关系是:Element树根据Widget树生成,而渲染树又依赖于Element树。
    RenderObject、GlobalKey - 图2

**

renderObj属性

  1. import "package:flutter/material.dart";
  2. class TestPage extends StatelessWidget {
  3. GlobalKey _Aaa = GlobalKey();
  4. @override
  5. Widget build(BuildContext context) {
  6. return Column(
  7. children: [
  8. Container(height: 200, color: Colors.red),
  9. Container(key: _Aaa, height: 200, color: Colors.green),
  10. Container(
  11. child: RaisedButton(
  12. child: Text('click me'),
  13. onPressed: () {
  14. print(_Aaa.currentWidget); //获取元素Widget
  15. var element = _Aaa.currentContext; //获取元素
  16. var renderObj = element.findRenderObject(); //获取元素 renderObject
  17. print(element);
  18. print(element.size); //获取元素大小
  19. print(renderObj);
  20. print(renderObj.semanticBounds); //Rect.fromLTRB(0.0, 0.0, 411.4, 200.0)
  21. print(renderObj.semanticBounds.left); //0.0
  22. print(renderObj.semanticBounds.top); //0
  23. print(renderObj.semanticBounds.right); //411.42857142857144
  24. print(renderObj.semanticBounds.bottom); //200
  25. print(renderObj.semanticBounds.bottomCenter); //Offset(205.7, 200.0)
  26. print(renderObj.semanticBounds.size); //Size(411.4, 200.0)
  27. print(renderObj.semanticBounds.width); //411.42857142857144
  28. print(renderObj.semanticBounds.height); //200.0
  29. print(renderObj.semanticBounds.longestSide); //411.42857142857144
  30. print(renderObj.semanticBounds.shortestSide); //200.0
  31. },
  32. ),
  33. ),
  34. ],
  35. );
  36. }
  37. }


获取元素大小

非ScrollView

可以通过BuildContext的size方法获取到大小,也可以通过renderObject的paintBounds和semanticBounds获取大小。

  1. RenderObject renderObject = key.currentContext.findRenderObject();
  2. renderObject.semanticBounds.size;
  3. renderObject.paintBounds.size;
  4. key.currentContext.size;

含有ScrollView

注意ScrollView的元素如果不在渲染树中,GlobalKey.currentContext是null。
结论:即使在ScrollView中,也一样。跟非ScrollView一样。

含有Sliver系列的固定头部等元素

SliverList等Sliver系列的Widget,不能直接使用上述方法获得大小,必须用内部的容器间接获取。
**

总结

1 、可以使用GlobalKey找到对应的元素的BuildContext对象
2 、通过BuildContext对象的size属性可以获取大小,Sliver系列Widget除外
3 、可以通过findRender方法获取到渲染对象,然后使用paintBounds获取到大小。

GlobalKey

GlobalKey是Flutter提供的一种在整个APP中引用element的机制。

概念参考前端react和vue中key值的含义,如果列表key不更改,则即便数据又修改视图也没有更改。

  • ValueKey:以一个值为key。
  • ObjectKey:以一个对象为key。
  • UniqueKey:生成唯一的随机数作为key。
  • PageStorageKey:专用于存储页面滚动位置的key。
  • GlobalKey每个globalkey都是一个在整个应用内唯一的key。globalkey相对而言是比较昂贵的,如果你并不需要globalkey的某些特性,那么可以考虑使用Key、ValueKey、ObjectKey或UniqueKey。

如果一个widget设置了GlobalKey,那么我们便可以通过globalKey.currentWidget获得该widget对象、globalKey.currentElement来获得widget对应的element对象,如果当前widget是StatefulWidget,则可以通过globalKey.currentState来获得该widget对应的state对象。

GlobalKey用途

  • 允许widget在应用程序中的任何位置更改其parent而不丢失其状态。应用场景:在两个不同的屏幕上显示相同的widget,并保持状态相同。
  • globalkey唯一定义了某个element,它使你能够访问与element相关联的其他对象,例如buildContext、state等。应用场景:跨widget访问状态

父组件调用子组件方法

父组件

  1. import 'package:flutter/material.dart';
  2. import 'package:kjt_bsp/screen/orderEntry/test.dart';
  3. class ParentScreen extends StatefulWidget {
  4. @override
  5. _ParentScreenState createState() => _ParentScreenState();
  6. }
  7. class _ParentScreenState extends State<ParentScreen> {
  8. @override
  9. Widget build(BuildContext context) {
  10. return Column(
  11. children: <Widget>[
  12. ChildScreen(
  13. key: childKey
  14. ),
  15. RaisedButton(
  16. onPressed: (){
  17. childKey.currentState.childFunction();
  18. },
  19. child: Text('点击我调用子组件方法'),
  20. )
  21. ],
  22. );
  23. }
  24. }

子组件

  1. import 'package:flutter/material.dart';
  2. GlobalKey<_ChildScreenState> childKey = GlobalKey();
  3. class ChildScreen extends StatefulWidget {
  4. ChildScreen({Key key}) : super(key: key);
  5. @override
  6. _ChildScreenState createState() => _ChildScreenState();
  7. }
  8. class _ChildScreenState extends State<ChildScreen> {
  9. @override
  10. Widget build(BuildContext context) {
  11. return Container();
  12. }
  13. childFunction(){
  14. print('this is a childFunction');
  15. }
  16. }

全局 context

有时,我们要在网络工具类做弹框处理或页面跳转,比如没有登录时,要跳转到登录页面,但是网络工具类不是 Widget,没有 Context, 所以做不了跳转,这时,我们就要一个全局 Context.

示例

application.dart

  1. class Application {
  2. //定义全局 navigatorKey
  3. static GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
  4. }

main.dart

  1. void main() {
  2. runApp(MyApp());
  3. }
  4. class MyApp extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return MaterialApp(
  8. navigatorKey: Application.navigatorKey,
  9. );
  10. }
  11. }

使用

  1. // 获取全局的context
  2. BuildContext myContext = Application.navigatorKey.currentState.overlay.context;
  3. Routes.pop(myContext, {'id': 342});
  4. //或者
  5. var aa = Application.navigatorKey.currentState;
  6. Routes.pop(aa, {'id': 342});

库 get_it

https://pub.dev/packages/get_it

安装依赖
  1. dependencies:
  2. get_it: ^5.0.1

导入
  1. import 'package:get_it/get_it.dart';

官网的介绍:

Simple direct Service Locator that allows to decouple the interface from a concrete implementation and to access the concrete implementation from everywhere in your App

关于什么是Service Locator:

If you are not familiar with the concept of Service Locators, its a way to decouple the interface (abstract base class) from a concrete implementation and at the same time allows to access the concrete implementation from everywhere in your App over the interface. I can only highly recommend to read this classic article by from Martin Fowler Inversion of Control Containers and the Dependency Injection pattern

所以Service Locator可以将接口(抽象基类)与具体实现分离,同时允许通过接口从App中的任何位置访问具体实现。

对于依赖注入不是很了解的同学可以参考:

为什么要使用 get_it

我们也可以通过其他的方式在app中的任意位置获取到要访问的对象,但是:

  • 如果使用Singleton,则无法轻松地将实现切换到另一个(例如用于单元测试的模拟版本)
  • 用于依赖项注入的IoC容器提供了类似的功能,但代价是启动时间慢且可读性差,因为您不知道神奇注入的对象来自何处。 由于大多数IoC库都依赖反射,因此它们不能与Flutter一起使用。

使用场景

  • 访问诸如REST API客户端,数据库之类的服务对象,以便可以轻松模拟它们
  • 从Flutter视图访问View / AppModels / Manager
  • 由于接口和实现是分离的,因此您还可以在不同的实现中注册Flutter Views,并在启动时确定要使用的视图,例如 取决于屏幕分辨率

使用注意事项

按照官方解释:始终使用相同的样式将项目文件作为相对路径或我建议的包导入。 不要混用它们,因为虽然两种导入方式都引用相同的文件,但目前Dart将以不同方式导入的类型视为两种不同的类型。

比如我们在lib根目录下新建了一个Service Locator,我们要在不同的文件中使用,可以使用以下两种方式导入,官方的意思是应该一直使用同一种方式导入。

那么如果真的混合使用,会有什么影响吗?我试过在不同的文件中使用不同的导入方式混用,但没发现异常,且locator对象本来就是一个单例的,
知道的人可以告诉一下我,?

  1. import '../../locator.dart';
  2. import 'package:provider_architecture/locator.dart';

如何使用

lib/routes/navigate_service.dart’

  1. import 'package:app1/routes/routes.dart';
  2. import 'package:flutter/material.dart';
  3. class NavigateService {
  4. final GlobalKey<NavigatorState> key = new GlobalKey<NavigatorState>();
  5. NavigatorState get navigator => key.currentState;
  6. BuildContext get navigatorContext => key.currentState.overlay.context;
  7. }

lib/routes/locator.dart

  1. import 'package:get_it/get_it.dart';
  2. import 'package:app1/routes/navigate_service.dart';
  3. //获取GetIt对象
  4. GetIt locator = GetIt.instance;
  5. //注册
  6. Future<void> setupLocator({bool test = false}) async {
  7. // Services 需要什么就注册什么
  8. // locator.registerSingleton<AppModel>(AppModel());
  9. // NavigateService 就是一个工具,导航工具
  10. // 工具一:导航服务,注册(放入工具箱)
  11. //locator.registerSingleton<NavigateService>(NavigateService());
  12. locator.registerLazySingleton<NavigateService>(() => NavigateService());
  13. }

main.dart

  1. import 'package:app1/routes/locator.dart';
  2. import 'package:app1/routes/navigate_service.dart';
  3. void main() {
  4. setupLocator(); //注册get_it
  5. runApp(MyApp());
  6. }
  7. class MyApp extends StatelessWidget {
  8. @override
  9. Widget build(BuildContext context) {
  10. return MaterialApp(
  11. // navigatorKey: Application.navigatorKey,
  12. navigatorKey: locator<NavigateService>().key,
  13. );
  14. }
  15. }
  16. // var naContext = Application.navigatorKey.currentState.overlay.context;
  17. var naContext = locator<NavigateService>().navigator;
  18. Routes.pop(naContext, {'id': 342});

4、注册的种类

  • Factory工厂模式

    1. void registerFactory<T>(FactoryFunc<T> func)
    2. locator.registerLazySingleton(() => Api());
    3. locator.registerFactory(() => HomeModel());

    传入参数是一个函数func,这个func函数要返回一个实现了类型T的对象,每次调用获取对象的方式时,都会返回一个新对象

  • 单例模式&懒加载单例

单例注册时,传入一个范型T的对象或其子类的对象,如果创建这个单例时是耗时的,那么可以不在app启动的时候注册,而放到第一次使用到该服务的时候,即使用懒加载的方式。

  1. void registerSingleton<T>(T instance)
  2. void registerLazySingleton<T>(FactoryFunc<T> func)

一个例子:
在app启动的时候调用注册方法

  1. void main() {
  2. setupLocator();
  3. runApp(MyApp());
  4. }
  5. 注册方法内部细节

单独的locator.dart文件:

  1. GetIt locator = GetIt.instance;
  2. void setupLocator() {
  3. locator.registerLazySingleton(() => Api());
  4. }

在数据类中使用服务

  1. Api _api = locator<Api>();
  2. List<Comment> comments;
  3. Future fetchComments(int postId) async {
  4. ...
  5. comments = await _api.getCommentsForPost(postId);
  6. ...
  7. }