Flutter 完整开发实战详解自定义布局,“金三银四”春招指南

     height,

  );

}




然后自定义 `RenderCloudWidget` 继承 `RenderBox` ,并混入 `ContainerRenderObjectMixin` 和 `RenderBoxContainerDefaultsMixin` 实现 `RenderBox` 自定义的简化。



class RenderCloudWidget extends RenderBox

with

    ContainerRenderObjectMixin<RenderBox, RenderCloudParentData>,

    RenderBoxContainerDefaultsMixin<RenderBox, RenderCloudParentData> {

RenderCloudWidget({

List<RenderBox> children,

Overflow overflow = Overflow.visible,

double ratio,

}) : _ratio = ratio,

    _overflow = overflow {

///添加所有 child

addAll(children);

}




如下代码所示,接下来主要看 `RenderCloudWidget` 中`override performLayout` 中的实现,这里我们只放关键代码:



*   1、我们首先拿到 `ContainerRenderObjectMixin` 链表中的 `firstChild` ,然后从头到位读取整个链表。

*   2、对于每个 child 首先通过 `child.layout` 设置他们的大小,然后记录下大小之后。

*   3、以容器控件的中心为起点,从内到外设置布局,这是设置的时候,需要通过记录的 `Rect` 判断是否会重复,每次布局都需要计算位置,直到当前 child 不在重复区域内。

*   4、得到最终布局内大小,然后设置整体居中。



///设置为我们的数据

@override

void setupParentData(RenderBox child) {

if (child.parentData is! RenderCloudParentData)

child.parentData = RenderCloudParentData();

}

@override

void performLayout() {

///默认不需要裁剪

_needClip = false;



///没有 childCount 不玩

if (childCount == 0) {

  size = constraints.smallest;

  return;

}



///初始化区域

var recordRect = Rect.zero;

var previousChildRect = Rect.zero;



RenderBox child = firstChild;



while (child != null) {

  var curIndex = -1;



  ///提出数据

  final RenderCloudParentData childParentData = child.parentData;



  child.layout(constraints, parentUsesSize: true);



  var childSize = child.size;



  ///记录大小

  childParentData.width = childSize.width;

  childParentData.height = childSize.height;



  do {

    ///设置 xy 轴的比例

    var rX = ratio >= 1 ? ratio : 1.0;

    var rY = ratio <= 1 ? ratio : 1.0;



    ///调整位置

    var step = 0.02 * _mathPi;

    var rotation = 0.0;

    var angle = curIndex * step;

    var angleRadius = 5 + 5 * angle;

    var x = rX * angleRadius * math.cos(angle + rotation);

    var y = rY * angleRadius * math.sin(angle + rotation);

    var position = Offset(x, y);



    ///计算得到绝对偏移

    var childOffset = position - Alignment.center.alongSize(childSize);



    ++curIndex;



    ///设置为遏制

    childParentData.offset = childOffset;



    ///判处是否交叠

  } while (overlaps(childParentData));



  ///记录区域

  previousChildRect = childParentData.content;

  recordRect = recordRect.expandToInclude(previousChildRect);



  ///下一个

  child = childParentData.nextSibling;

}



///调整布局大小

size = constraints

    .tighten(

      height: recordRect.height,

      width: recordRect.width,

    )

    .smallest;



///居中

var contentCenter = size.center(Offset.zero);

var recordRectCenter = recordRect.center;

var transCenter = contentCenter - recordRectCenter;

child = firstChild;

while (child != null) {

  final RenderCloudParentData childParentData = child.parentData;

  childParentData.offset += transCenter;

  child = childParentData.nextSibling;

}



///超过了嘛?

_needClip =

    size.width < recordRect.width || size.height < recordRect.height;

}




**其实看完代码可以发现,关键就在于你怎么设置 `child.parentData` 的 `offset` ,来控制其位置。**



最后通过 `CloudWidget` 加载我们的 `RenderCloudWidget` 即可, 当然完整代码还需要结合 `FittedBox` 与 `RotatedBox` 简化完成,具体可见 :[GSYFlutterDemo]( )



class CloudWidget extends MultiChildRenderObjectWidget {

final Overflow overflow;

final double ratio;

CloudWidget({

Key key,

this.ratio = 1,

this.overflow = Overflow.clip,

List<Widget> children = const <Widget>[],

}) : super(key: key, children: children);

@override

RenderObject createRenderObject(BuildContext context) {

return RenderCloudWidget(

  ratio: ratio,

  overflow: overflow,

);

}

@override

void updateRenderObject(

  BuildContext context, RenderCloudWidget renderObject) {

renderObject

  ..ratio = ratio

  ..overflow = overflow;

}

}




**最后我们总结,实现自定义布局的流程就是,实现自定义 `RenderBox` 中 `performLayout` child 的 `offset` 。**



[]( )四、CustomMultiChildLayout

-------------------------------------------------------------------------------------------



`CustomMultiChildLayout` 是 Flutter 为我们封装的简化自定义布局实现,**它的内部同样是通过 `MultiChildRenderObjectWidget` 实现,但是它为我们封装了 `RenderCustomMultiChildLayoutBox` 和 `MultiChildLayoutParentData` ,并通过 `MultiChildLayoutDelegate` 暴露出需要自定义的地方。**



![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi1iMmQyYTgxNTczMTc4ZGZl?x-oss-process=image/format,png)



使用 `CustomMultiChildLayout` 你只需要继承 `MultiChildLayoutDelegate` ,并实现如下方法即可:



void performLayout(Size size);

bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate);




通过继承 `MultiChildLayoutDelegate`,并且实现 `performLayout` 方法,我们可以快速自定义我们需要的控件,当然便捷的封装也代表了灵活性的丧失,可以看到 `performLayout` 方法中只有布局自身的 `Size` 参数,所以完成上图需求时,**我们还需要 child 的大小和位置** ,也就是 `childSize` 和 `childId` 。



`childSize` 相信大家都能故名思义,那 `childId` 是什么呢?



这就要从 `MultiChildLayoutDelegate` 的实现说起,**在 `MultiChildLayoutDelegate` 内部会有一个 `Map<Object, RenderBox> _idToChild;` 对象,这个 `Map` 对象保存着 `Object id` 和 `RenderBox` 的映射关系,而在 `MultiChildLayoutDelegate` 中获取 `RenderBox` 都需要通过 `id` 获取。**



`_idToChild` 这个 `Map` 是在 `RenderBox performLayout` 时,在 `delegate._callPerformLayout` 方法内创建的,创建后所用的 `id` 为 `MultiChildLayoutParentData` 中的 id, **而 `MultiChildLayoutParentData` 的 id ,可以通过 `LayoutId` 嵌套时自定义指定赋值。**



而完成上述布局,我们需要知道每个 child 的 index ,所以我们可以把 index 作为 id 设置给每个 child 的 `LayoutId` 。



**所以我们可以通过 `LayoutId` 指定 id 为数字 index , 同时告知 delegate ,这样我们就知道 child 顺序和位置啦。**



> 这个 id 是 `Object` 类型 ,所以你懂得,你可以赋予很多属性进去。



如下代码所示,这样在自定义的 `CircleLayoutDelegate` 中,就知道每个控件的 `index` 位置,也就是知道了,圆形布局中每个 item 需要的位置。



我们只需要通过 `index` ,计算出 child 所在的角度,然后利用 `layoutChild` 和 `positionChild` 对每个item进行布局即可,完整代码:[GSYFlutterDemo]( )



///自定义实现圆形布局

class CircleLayoutDelegate extends MultiChildLayoutDelegate {

final List customLayoutId;

final Offset center;

Size childSize;

CircleLayoutDelegate(this.customLayoutId,

  {this.center = Offset.zero, this.childSize});

@override

void performLayout(Size size) {

for (var item in customLayoutId) {

  if (hasChild(item)) {

    double r = 100;



    int index = int.parse(item);



    double step = 360 / customLayoutId.length;

结尾

如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴。为此我整理了一份Android学习资料路线:

这里是一份BAT大厂面试资料专题包:

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

dex = int.parse(item);

    double step = 360 / customLayoutId.length;

结尾

如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴。为此我整理了一份Android学习资料路线:

[外链图片转存中…(img-rONzxMdf-1630927619984)]

这里是一份BAT大厂面试资料专题包:

[外链图片转存中…(img-rVM8gCqJ-1630927619986)]

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值