3.8版本上父节点动态添加子节点,怎么让父节点跟着变化大小?以前2.x版本layout的type设为none可以选ResizeMode为Container,3.x的layout没这个选项了
2.x可以像下面图中这样用,白色是layout,红绿是两个按钮,layout的大小会是它们的包围矩形的大小
3.8版本上父节点动态添加子节点,怎么让父节点跟着变化大小?以前2.x版本layout的type设为none可以选ResizeMode为Container,3.x的layout没这个选项了
2.x可以像下面图中这样用,白色是layout,红绿是两个按钮,layout的大小会是它们的包围矩形的大小
我也遇到了这个问题
可以”监听“里面的大小 然后在代码动态修改外面的大小。
今天我根据v2.x 版本自己实现了一下。正好我的v3项目也需要用。拿走不谢!好用的话喊个老铁666
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
}
}