欢迎您光临深圳塔灯网络科技有限公司!
电话图标 余先生:13699882642

网站百科

为您解码网站建设的点点滴滴

Flutter自定义布局套路

发表日期:2018-08 文章编辑:小灯 浏览次数:1902

开始

在Android中我们要实现一个布局需要继承ViewGroup, 重写其中的onLayoutonMeasure方法. 其中onLayout负责给子控件设置布局区域, onMeaseure度量子控件大小和自身大小. 今天我们就研究下Flutter是如何实现布局的.

Flutter布局

首先我们挑选一个Flutter控件去看源码, 我们就选Stack, 因为它足够简单. 从表象上讲它只要重叠摆放一组子控件即可. 先看下Stack的源码:

class Stack extends MultiChildRenderObjectWidget { Stack({ Key key, this.alignment: AlignmentDirectional.topStart, this.textDirection, this.fit: StackFit.loose, this.overflow: Overflow.clip, List<Widget> children: const <Widget>[], }) : super(key: key, children: children);final AlignmentGeometry alignment; final StackFit fit; final Overflow overflow;@override RenderStack createRenderObject(BuildContext context) { return new RenderStack( alignment: alignment, textDirection: textDirection ?? Directionality.of(context), fit: fit, overflow: overflow, ); }@override void updateRenderObject(BuildContext context, RenderStack renderObject) { renderObject ..alignment = alignment ..textDirection = textDirection ?? Directionality.of(context) ..fit = fit ..overflow = overflow; }@override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(new DiagnosticsProperty<AlignmentGeometry>('alignment', alignment)); properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); properties.add(new EnumProperty<StackFit>('fit', fit)); properties.add(new EnumProperty<Overflow>('overflow', overflow)); } } 

Stack继承自MultiChildRenderObjectWidget, 重写了createRenderObject其返回了一个RenderStack对象, 实际的工作者. 而updateRenderObject则只是修改RenderStack对象的属性. debugFillProperties方法则是填充该类属性的参数值到DiagnosticPropertiesBuilder中.

我们看看Flex, 也是如此, 重写了createRenderObject其返回了一个RenderFlex对象, 实际的工作者. 而updateRenderObject则只是修改RenderFlex对象的属性.

所以我们接下来看看RenderStack, 精简代码如下:

class RenderStack extends RenderBox with ContainerRenderObjectMixin<RenderBox, StackParentData>,RenderBoxContainerDefaultsMixin<RenderBox, StackParentData> { RenderStack({ List<RenderBox> children, AlignmentGeometry alignment: AlignmentDirectional.topStart, TextDirection textDirection, StackFit fit: StackFit.loose, Overflow overflow: Overflow.clip, }) : assert(alignment != null),assert(fit != null),assert(overflow != null),_alignment = alignment,_textDirection = textDirection,_fit = fit,_overflow = overflow { addAll(children); }bool _hasVisualOverflow = false;@override void performLayout() { _resolve(); assert(_resolvedAlignment != null); _hasVisualOverflow = false; bool hasNonPositionedChildren = false; if (childCount == 0) { size = constraints.biggest; assert(size.isFinite); return; }double width = constraints.minWidth; double height = constraints.minHeight;BoxConstraints nonPositionedConstraints; assert(fit != null); switch (fit) { case StackFit.loose: nonPositionedConstraints = constraints.loosen(); break; case StackFit.expand: nonPositionedConstraints = new BoxConstraints.tight(constraints.biggest); break; case StackFit.passthrough: nonPositionedConstraints = constraints; break; } assert(nonPositionedConstraints != null);RenderBox child = firstChild; while (child != null) { final StackParentData childParentData = child.parentData;if (!childParentData.isPositioned) { hasNonPositionedChildren = true;child.layout(nonPositionedConstraints, parentUsesSize: true);final Size childSize = child.size; width = math.max(width, childSize.width); height = math.max(height, childSize.height); }child = childParentData.nextSibling; }if (hasNonPositionedChildren) { size = new Size(width, height); assert(size.width == constraints.constrainWidth(width)); assert(size.height == constraints.constrainHeight(height)); } else { size = constraints.biggest; }assert(size.isFinite);child = firstChild; while (child != null) { final StackParentData childParentData = child.parentData;if (!childParentData.isPositioned) { childParentData.offset = _resolvedAlignment.alongOffset(size - child.size); } else { BoxConstraints childConstraints = const BoxConstraints();if (childParentData.left != null && childParentData.right != null) childConstraints = childConstraints.tighten(width: size.width - childParentData.right - childParentData.left); else if (childParentData.width != null) childConstraints = childConstraints.tighten(width: childParentData.width);if (childParentData.top != null && childParentData.bottom != null) childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom - childParentData.top); else if (childParentData.height != null) childConstraints = childConstraints.tighten(height: childParentData.height);child.layout(childConstraints, parentUsesSize: true);double x; if (childParentData.left != null) { x = childParentData.left; } else if (childParentData.right != null) { x = size.width - childParentData.right - child.size.width; } else { x = _resolvedAlignment.alongOffset(size - child.size).dx; }if (x < 0.0 || x + child.size.width > size.width) _hasVisualOverflow = true;double y; if (childParentData.top != null) { y = childParentData.top; } else if (childParentData.bottom != null) { y = size.height - childParentData.bottom - child.size.height; } else { y = _resolvedAlignment.alongOffset(size - child.size).dy; }if (y < 0.0 || y + child.size.height > size.height) _hasVisualOverflow = true;childParentData.offset = new Offset(x, y); }assert(child.parentData == childParentData); child = childParentData.nextSibling; } }@protected void paintStack(PaintingContext context, Offset offset) { defaultPaint(context, offset); }@override void paint(PaintingContext context, Offset offset) { if (_overflow == Overflow.clip && _hasVisualOverflow) { context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack); } else { paintStack(context, offset); } } } 

可以看出RenderStack接收了所有传递给Stack的参数, 毕竟RenderStack才是实际干活的^^. performLayout负责了所有布局相关的工作. performLayout首先分析StackFit参数, 该参数有3个值:

  • StackFit.loose 按最小的来.
  • StackFit.expand 按最大的来.
  • StackFit.passthrough Stack上层为->Expanded->Row, 横向尽量大, 纵向尽量小.

得出BoxConstraints. 然后遍历所有子控件, 如果不是Positioned类型子控件, 则将BoxConstraints传给子控件让它根据父控件大小自己内部布局. 并且记录下所有子控件结合RenderStack自生大小得出的最大高度和宽度. 将其设置为当前控件大小.

接着再继续从头遍历子控件, 如果不是Positioned类型子控件, 根据alignment参数, 设置子控件在父控件中的偏移量, 比如Stack设置了居中, 上面计算出宽100, 高200, 而子控件宽30, 高30, 那么子控件需要偏移x=35, y=85. 如果是Positioned类型的子控件, 先将RenderStacksize大小, 减去Positioned属性里的大小. 再来计算便宜量.

这个里面有_hasVisualOverflow变量, 如果内容超出RenderStack大小, 其值为true. 也就是我们写布局时, 内容超过范围了, 报出来一个色块提示, 就是如此得出的.
_overflow属性则指定了子控件的绘制区域是否能超过父控件, 跟Android中的clipChildren属性很像.

另外我们再分析下IndexedStack, 该控件一次只能显示一个子控件. 其实际差异在RenderIndexedStack

class RenderIndexedStack extends RenderStack { ... @override bool hitTestChildren(HitTestResult result, { @required Offset position }) { if (firstChild == null || index == null) return false; assert(position != null); final RenderBox child = _childAtIndex(); final StackParentData childParentData = child.parentData; return child.hitTest(result, position: position - childParentData.offset); }@override void paintStack(PaintingContext context, Offset offset) { if (firstChild == null || index == null) return; final RenderBox child = _childAtIndex(); final StackParentData childParentData = child.parentData; context.paintChild(child, childParentData.offset + offset); } ... } 

重写了RenderStackpaintStackhitTestChildren方法, 只绘制选中的子控件, 和接收事件.

总结

实现一个自定义布局, 我们需要先继承MultiChildRenderObjectWidget, 然后重写createRenderObjectupdateRenderObject方法, 前者返回我们自定义的RenderBox的对象. 后者更新想要传递的属性. 然后需要我们继承RenderBox, 来扩展我们想要的功能特性.


本页内容由塔灯网络科技有限公司通过网络收集编辑所得,所有资料仅供用户学习参考,本站不拥有所有权,如您认为本网页中由涉嫌抄袭的内容,请及时与我们联系,并提供相关证据,工作人员会在5工作日内联系您,一经查实,本站立刻删除侵权内容。本文链接:https://dengtar.com/17531.html
相关APP开发
 八年  行业经验

多一份参考,总有益处

联系深圳网站公司塔灯网络,免费获得网站建设方案及报价

咨询相关问题或预约面谈,可以通过以下方式与我们联系

业务热线:余经理:13699882642

Copyright ? 2013-2018 Tadeng NetWork Technology Co., LTD. All Rights Reserved.