flutter 下拉選擇框,Flutter自定義布局套路

 2023-12-25 阅读 24 评论 0

摘要:開始 在Android中我們要實現一個布局需要繼承ViewGroup, 重寫其中的onLayout和onMeasure方法. 其中onLayout負責給子控件設置布局區域, onMeaseure度量子控件大小和自身大小. 今天我們就研究下Flutter是如何實現布局的. Flutter布局 首先我們挑選一個Flutter控件去看源碼, 我們

開始

在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;@overrideRenderStack createRenderObject(BuildContext context) {return new RenderStack(alignment: alignment,textDirection: textDirection ?? Directionality.of(context),fit: fit,overflow: overflow,);}@overridevoid updateRenderObject(BuildContext context, RenderStack renderObject) {renderObject..alignment = alignment..textDirection = textDirection ?? Directionality.of(context)..fit = fit..overflow = overflow;}@overridevoid 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 RenderBoxwith 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;@overridevoid 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;}}@protectedvoid paintStack(PaintingContext context, Offset offset) {defaultPaint(context, offset);}@overridevoid 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 {...@overridebool 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);}@overridevoid 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, 來擴展我們想要的功能特性.

轉載于:https://www.cnblogs.com/zhujiabin/p/10248009.html

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/3/194906.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息