1. 前言
有时候,RenderObject需要在其子节点中存储一些数据,比如用于布局的一些参数,或者和其他子节点之间的关系。为此,Flutter提供了ParentData,用于存储父节点的一些信息。每个RenderObject都有这个成员变量,该成员在setupParentData方法中初始化。子类如果需要ParentData的某个子类,需要重写该方法,并在该方法中对ParentData进行初始化。
2. ParentData分类
ParentData可以分为三大类:BoxParentData,SliverLogicalParentData,以及SliverPhysicalParentData。其中SliverLogicalParentData和SliverPhysicalParentData用于sliver,对应滑动视图场景,此文不进行展开。BoxParentData则用于RenderBox,对应普通视图场景,是本文讲解的重点。
BoxParentData中主要属性是offset,用于描述子节点在父节点中的坐标偏移,主要用于子节点的布局,其源码如下:
class BoxParentData extends ParentData { /// The offset at which to paint the child in the parent\’s coordinate system. Offset offset = Offset.zero; @override String toString() => \’offset=$offset\’; }
BoxParentData的子类TableCellParentData主要用于表格布局,_ToolbarParentData主要用于iOS风格的工具栏布局,ContainerBoxParentData主要用于需要
ContainerRenderObjectMixin的节点布局。
其中,ContainerBoxParentData使用频率很高,基本上所有父节点ParentData都混入了该类,该类需要与
ContainerRenderObjectMixin共同使用,主要解决了对 child的管理,它用双链表存储了所有子节点并提供了方便的接口去获取他们。对于开发者,一般来说只用到
ContainerRenderObjectMixin中的firstChild、lastChild、childCount,用来获取首末child,child的个数,配合使用 ContainerParentDataMixin中的previousSibling、nextSibling就可以对child进行遍历了。
这些ParentData的基类解决了child的布局位置信息的存储和child的管理以及引用的获取,再往下的子类就是与各布局的功能相关的类了,如 FlexParentData,存储了flex和fit的值,分别表示该child的flex比重和布局的fit 策略。
3. 关键流程
对于BoxParentData的常用属性offset,通常情况下,其在子RenderObject节点的setupParentData函数中对BoxParentData进行初始化;在performLayout中,对offset进行赋值;在paint函数中,使用offset确认子节点的绘制位置,在hitTestChildren中,使用offset辅助判断是否在点击区域内。
接下来,将使用一个示例,来分析BoxParentData中的各个流程。示例代码如下:
class StackTestWidget extends StatelessWidget { @override Widget build(BuildContext context) { return ConstrainedBox( constraints: BoxConstraints.expand(), child: Stack( alignment:Alignment.center , //指定未定位或部分定位widget的对齐方式 children: [ // Positioned( // top: 80.0, // child: RichText(text: TextSpan(text: “first text”), // textDirection: TextDirection.ltr,), // ), Container( child: RichText( text: TextSpan(text: “first text”), textDirection: TextDirection.ltr), color: Colors.red, ), Positioned( top: 180.0, left: 100, child: RichText(text: TextSpan(text: “second”), textDirection: TextDirection.ltr,), ) ], ), ); } }
- 初始化
其中,Stack对应的ParentData是StackParentData,其被存储在Stack的Child RenderObject中。StackParentData初始化的时序图如下图:
由该时序图可以看出,StackParentData的初始化函数的调用时机是Element被加载时,即mount函数中,其对应的setupParentData代码如下。
@override void setupParentData(RenderBox child) { if (child.parentData is! StackParentData) child.parentData = StackParentData(); }
- 赋值
StackParentData被初始化后,其赋值是在performLayout中,其流程图如下所示。
如流程图所示,在performLayout中,对子节点是否是isPositioned,会分别进行处理,但最终都会对offset进行赋值。
- 使用
offset的使用场景主要有两处,第一个是在绘制子RenderObject节点的时候用于确认子RenderObject节点的位置,对应的函数是paint;第二个是在判断点击事件的时候,用于判断是否会触发子RenderObject节点的点击事件,对应的函数是hitTestChildren。
其在paint函数中使用的流程图如下:
最后会调用到RenderBoxContainerDefaultsMixin的defaultPaint函数,其代码如下:
void defaultPaint(PaintingContext context, Offset offset) { ChildType child = firstChild; while (child != null) { final ParentDataType childParentData = child.parentData as ParentDataType; context.paintChild(child, childParentData.offset offset); child = childParentData.nextSibling; } }
offset在hitTestChildren函数中使用的流程图如下:
最后会调用到
RenderBoxContainerDefaultsMixin的defaultHitTestChildren函数,其代码如下:
bool defaultHitTestChildren(BoxHitTestResult result, { Offset position }) { // The x, y parameters have the top left of the node\’s box as the origin. ChildType child = lastChild; while (child != null) { final ParentDataType childParentData = child.parentData as ParentDataType; final bool isHit = result.addWithPaintOffset( offset: childParentData.offset, position: position, hitTest: (BoxHitTestResult result, Offset transformed) { assert(transformed == position – childParentData.offset); return child.hitTest(result, position: transformed); }, ); if (isHit) return true; child = childParentData.previousSibling; } return false; }
除了offset,StackParentData还有自己特有的属性top等,这些属性保存了子RenderObject在RenderStack中的位置信息,其赋值是在applyParentData中,这个函数也是Flutter Framework开放出来对ParentData进行赋值的接口。其流程图如下:
此处,Positioned是Stack的子Widget,RenderObjectElement是Positioned的子Widget对应的Element。
值得注意的是applyParentData只在父Element是ParentDataElement,且其有更新的时候才会被调用,包括但不限于本Element attachRenderObject和父ParentDataElement对应的Widget被重建。
4. 常用使用场景
ParentData的常用使用场景是在自定义RenderObject中,自定义一种ParentData,例如CustomParentData,存储其特有的布局信息。然后在setupParentData中对其进行初始化,在applyParentData中对其进行赋值,然后在paint和hitTestChildren中对其进行使用。具体使用示例会在自定义RenderObject章节中进行详述。
5. 小结
本文主要介绍了ParentData的作用、分类和关键流程,并通过一个示例分析了ParentData的关键流程。其重点如下:
- RenderObject通常使用ParentData在其子节点中存储一些数据,比如用于布局的一些参数。
- ParentData一般在setupParentData中进行初始化,在performLayout中进行赋值,在paint和hitTestChildren中进行使用。
- 如果是自定义的ParentData,通常需要在applyParentData中对其进行赋值。
内容出处:,
声明:本网站所收集的部分公开资料来源于互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。文章链接:http://www.yixao.com/tech/23346.html