父节点动态添加子节点,怎么让父节点跟着动态刷新size

3.8版本上父节点动态添加子节点,怎么让父节点跟着变化大小?以前2.x版本layout的type设为none可以选ResizeMode为Container,3.x的layout没这个选项了

2.x可以像下面图中这样用,白色是layout,红绿是两个按钮,layout的大小会是它们的包围矩形的大小
57095a6fcd3d12b9a53399739cd9b3fb fe0c938420004b3421cf391d3670af08

我也遇到了这个问题

可以”监听“里面的大小 然后在代码动态修改外面的大小。

今天我根据v2.x 版本自己实现了一下。正好我的v3项目也需要用。拿走不谢!好用的话喊个老铁666 :smiley:

import { CCInteger, Component, Enum, Node, Rect, Size, TransformBit, UITransform, Vec3, _decorator } from 'cc'

const { ccclass, menu, property, disallowMultiple, requireComponent, executeInEditMode } = _decorator

/**
 * 布局类型
 * - NONE 不做任何缩放
 * - CONTAINER 容器的大小会根据子节点的大小自动缩放。
 */
enum ResizeMode {
  NONE = 0,
  /** 容器的大小会根据子节点的大小自动缩放。*/
  CONTAINER,
}

@ccclass
@executeInEditMode // 在编辑器模式下执行
@disallowMultiple
@menu('自定义组件/SpriteButton')
@requireComponent(UITransform)
export default class LayoutResize extends Component {
  private _resize = ResizeMode.NONE
  @property({
    type: Enum(ResizeMode),
    displayName: '布局类型',
  })
  get resizeMode() {
    return this._resize
  }
  set resizeMode(value: ResizeMode) {
    this._resize = value
    this._doLayoutDirty()
  }

  private _paddingTop = 0
  @property({ type: CCInteger, tooltip: '容器内上边距' })
  get paddingTop() {
    return this._paddingTop
  }
  set paddingTop(value: number) {
    this._paddingTop = value
    this._doLayoutDirty()
  }

  private _paddingBottom = 0
  @property({ type: CCInteger, tooltip: '容器内下边距' })
  get paddingBottom() {
    return this._paddingBottom
  }
  set paddingBottom(value: number) {
    this._paddingBottom = value
    this._doLayoutDirty()
  }

  private _paddingLeft = 0
  @property({ type: CCInteger, tooltip: '容器内左边距' })
  get paddingLeft() {
    return this._paddingLeft
  }
  set paddingLeft(value: number) {
    this._paddingLeft = value
    this._doLayoutDirty()
  }

  private _paddingRight = 0
  @property({ type: CCInteger, tooltip: '容器内右边距' })
  get paddingRight() {
    return this._paddingRight
  }
  set paddingRight(value: number) {
    this._paddingRight = value
    this._doLayoutDirty()
  }

  private _layoutDirty = true
  private _layoutSize = new Size(100, 100)

  onEnable() {
    this._addEventListeners()
    const trans = this.node.getComponent(UITransform)
    if (trans?.contentSize.equals(new Size(0, 0))) {
      trans?.setContentSize(this._layoutSize)
    }
    this._doLayoutDirty()
  }

  onDisable() {
    this._removeEventListeners()
  }

  onLoad() {
    this.onEnable()
  }

  onDestroy() {
    this.onDisable()
  }

  _doLayoutDirty() {
    this._layoutDirty = true
    this.updateLayout()
  }

  _doLayoutTransrorm(type: TransformBit) {
    switch (type) {
      case TransformBit.POSITION:
      case TransformBit.SCALE:
      case TransformBit.RS:
      case TransformBit.TRS:
        this._doLayoutDirty()
        break
      default:
        break
    }
  }

  _addEventListeners() {
    this.node.on(Node.EventType.SIZE_CHANGED, this._resized, this)
    this.node.on(Node.EventType.ANCHOR_CHANGED, this._doLayoutDirty, this)
    this.node.on(Node.EventType.CHILD_ADDED, this._childAdded, this)
    this.node.on(Node.EventType.CHILD_REMOVED, this._childRemoved, this)
    this._addChildrenEventListeners()
  }

  _removeEventListeners() {
    this.node.off(Node.EventType.SIZE_CHANGED, this._resized, this) // 大小改变
    this.node.off(Node.EventType.ANCHOR_CHANGED, this._doLayoutDirty, this) // 锚点改变
    this.node.off(Node.EventType.CHILD_ADDED, this._childAdded, this) // 添加子节点
    this.node.off(Node.EventType.CHILD_REMOVED, this._childRemoved, this) // 删除子节点
    this._removeChildrenEventListeners()
  }

  _addChildrenEventListeners() {
    const children = this.node.children
    for (let i = 0; i < children.length; i += 1) {
      const child = children[i]
      child.on(Node.EventType.TRANSFORM_CHANGED, this._doLayoutTransrorm, this)
      child.on(Node.EventType.SIZE_CHANGED, this._doLayoutDirty, this)
      child.on(Node.EventType.ANCHOR_CHANGED, this._doLayoutDirty, this)
      child.on('active-in-hierarchy-changed', this._doLayoutDirty, this)
    }
  }

  _removeChildrenEventListeners() {
    const children = this.node.children
    for (let i = 0; i < children.length; i += 1) {
      const child = children[i]
      child.off(Node.EventType.TRANSFORM_CHANGED, this._doLayoutTransrorm, this)
      child.off(Node.EventType.SIZE_CHANGED, this._doLayoutDirty, this)
      child.off(Node.EventType.ANCHOR_CHANGED, this._doLayoutDirty, this)
      child.off('active-in-hierarchy-changed', this._doLayoutDirty, this)
    }
  }

  _childAdded(child) {
    child.on(Node.EventType.TRANSFORM_CHANGED, this._doLayoutTransrorm, this)
    child.on(Node.EventType.SIZE_CHANGED, this._doLayoutDirty, this)
    child.on(Node.EventType.ANCHOR_CHANGED, this._doLayoutDirty, this)
    child.on('active-in-hierarchy-changed', this._doLayoutDirty, this)
    this._doLayoutDirty()
  }

  _childRemoved(child) {
    child.off(Node.EventType.TRANSFORM_CHANGED, this._doLayoutTransrorm, this)
    child.off(Node.EventType.SIZE_CHANGED, this._doLayoutDirty, this)
    child.off(Node.EventType.ANCHOR_CHANGED, this._doLayoutDirty, this)
    child.off('active-in-hierarchy-changed', this._doLayoutDirty, this)
    this._doLayoutDirty()
  }

  _resized() {
    this._layoutSize = this.node.getComponent(UITransform)!.contentSize
    this._doLayoutDirty()
  }

  _doLayoutBasic() {
    const { children } = this.node
    let allChildrenBoundingBox: Rect | null = null
    for (let i = 0; i < children.length; i += 1) {
      const child = children[i]
      if (child.activeInHierarchy) {
        if (allChildrenBoundingBox) {
          Rect.union(
            allChildrenBoundingBox,
            allChildrenBoundingBox,
            child.getComponent(UITransform)!.getBoundingBoxToWorld()
          )
        } else {
          allChildrenBoundingBox = child.getComponent(UITransform)!.getBoundingBoxToWorld()
        }
      }
    }

    if (allChildrenBoundingBox) {
      const trans = this.node.getComponent(UITransform) ?? this.node.addComponent(UITransform)
      let leftBottomSpace = trans.convertToNodeSpaceAR(new Vec3(allChildrenBoundingBox.x, allChildrenBoundingBox.y))
      leftBottomSpace = new Vec3(leftBottomSpace.x - this.paddingLeft, leftBottomSpace.y - this.paddingBottom)
      let rightTopSpace = trans.convertToNodeSpaceAR(new Vec3(allChildrenBoundingBox.xMax, allChildrenBoundingBox.yMax))
      rightTopSpace = new Vec3(rightTopSpace.x + this.paddingRight, rightTopSpace.y + this.paddingTop)

      const space = rightTopSpace.subtract(leftBottomSpace)
      const newSize = new Size(parseFloat(space.x.toFixed(2)), parseFloat(space.y.toFixed(2)))
      if (newSize.width !== 0) {
        // 反转是为了得到子节点在父坐标系中的坐标点
        const newAnchorX = -leftBottomSpace.x / newSize.width
        trans.anchorX = parseFloat(newAnchorX.toFixed(2))
      }
      if (newSize.height !== 0) {
        // 反转是为了得到子节点在父坐标系中的坐标点
        const newAnchorY = -leftBottomSpace.y / newSize.height
        trans.anchorY = parseFloat(newAnchorY.toFixed(2))
      }
      trans.setContentSize(newSize)
    }
  }

  /**
   * 立即执行更新布局
   *
   * @method updateLayout
   *
   * @example
   * layout.type = cc.Layout.HORIZONTAL;
   * layout.node.addChild(childNode);
   * cc.log(childNode.x); // not yet changed
   * layout.updateLayout();
   * cc.log(childNode.x); // changed
   */
  updateLayout() {
    if (this._resize !== ResizeMode.CONTAINER) return
    if (!this._layoutDirty) return
    if (!this.node.children.length) return
    const activeChild = this.node.children.find(node => node.activeInHierarchy)
    if (!activeChild) return
    this._doLayoutBasic()
    this._layoutDirty = false
  }
}