import UIComponent = Laya.UIComponent; import Node = Laya.Node; import Sprite = Laya.Sprite; import Point = Laya.Point; import SScript from './SScript'; import { UIPlugin } from '../core/UIPlugin'; import { GameUtils } from '../core/GameUtils'; type LAYOUT_TYPE = 'Horizontal' | 'Vertical'; type ALIGN_TYPE = 'middle' | 'min' | 'max'; /** 使用它来替代VBox和HBox。设置type可以改变排序方向,设置align('middle'|'min'|'max')可以改变在另一方向上的排列方式。设置wrap可设置换行规则。 * 排列顺序与子元素索引号一致 */ export default class SLayout extends SScript{ private _type:LAYOUT_TYPE = 'Horizontal'; private _align:ALIGN_TYPE = 'min'; private _itemChanged: boolean = false; private _space: number = 0; private _wrap: number = 0; private _lineMargin: number = 0; private _colReverseDirection: boolean = false; private _rowReverseDirection: boolean = false; private _colReverseZOrder: boolean = false; private _rowReverseZOrder: boolean = false; /** @prop {name:ignoreChildrenScale, tips:"是否忽略子元素的缩放对自身的影响", type:bool, default:true} */ ignoreChildrenScale: boolean = true; /** 由于排列过程中会改变child的zOrder,而zOrder的改变会导致child在children中的索引位置改变进而导致SLayout排列 * 子节点时的顺序错乱,故在此需要自己维护一份children列表。 */ private _children: Node[]; constructor(){ super(); } onAwake(){ super.onAwake(); let self = this; let o:UIComponent = self.owner as UIComponent; self._children = o._children.concat();//复制一份子节点列表到this let addEventListenerForChild = (c:Node)=>{ c.on(Laya.Event.RESIZE, self, self.$setItemChanged); c.on('visible', self, self.$setItemChanged); }; let removeEventListenerForChild = (c:Node)=>{ c.off(Laya.Event.RESIZE, self, self.$setItemChanged); c.off('visible', self, self.$setItemChanged); }; self._children.forEach(addEventListenerForChild); let origiAddChild:Function = o.addChild; o.addChild = function(child:Node):Node{ if (!child || this.destroyed || child===this)return child; addEventListenerForChild(child); self.$setItemChanged(); self._children.push(child); return origiAddChild.call(this, child); }; let origiAddChildAt = o.addChildAt; o.addChildAt = function(child:Node, index:number):Node{ if (!child || this.destroyed || child===this)return child; addEventListenerForChild(child); self.$setItemChanged(); self._children.splice(index, 0, child); return origiAddChildAt.call(this, child, index); } let origiRemoveChildAt = o.removeChildAt; o.removeChildAt = function(index:number):Node{ let c = this.getChildAt(index); removeEventListenerForChild(c); self.$setItemChanged(); self._children.splice(self._children.indexOf(c), 1); return origiRemoveChildAt.call(this, index); } o['measureWidth'] = this['measureWidth'].bind(this); o['measureHeight'] = this['measureHeight'].bind(this); this.refresh(); } refresh(){ this.$setItemChanged(); } /** @prop {name:type, tips:"排序方向", type:option, option:"Horizontal,Vertical", default:"Horizontal"} */ get type() { return this._type; } set type(v:LAYOUT_TYPE) { if(this._type != v){ this._type = v; this.$setItemChanged(); } } /** @prop {name:align, tips:"在另一个方向上的排序方式", type:option, option:"middle,min,max", default:"min"} */ get align() { return this._align; } set align(v:ALIGN_TYPE) { if(this._align != v){ this.$setItemChanged(); this._align = v; } } /** @prop {name:space, tips:"子节点之间的间距", type:Number, default:0} */ get space() { return this._space; } set space(v:number) { if(!v)v = 0; if(this._space != v){ this._space = v; this.$setItemChanged(); } } /** @prop {name:wrap, tips:"一行能够排列多少个节点,为0则会根据宿主的宽高进行自动换行", type:Number, default:0} */ get wrap() { return this._wrap; } set wrap(v:number) { if(this._wrap != v){ this._wrap = v; this.$setItemChanged(); } } /** @prop {name:lineMargin, tips:"行间距", type:Number, default:0} */ get lineMargin() { return this._lineMargin; } set lineMargin(v:number) { if(this._lineMargin != v){ this._lineMargin = v; this.$setItemChanged(); } } /** @prop {name:colReverseDirection, tips:"列排列方向是否逆向。默认为false,会以从左到右/从上到下方式排列。为true则会逆向。你必须设置宽/高为非0的值才可以真正起效", type:bool, default:false} */ get colReverseDirection() { return this._colReverseDirection; } set colReverseDirection(v:boolean) { if(this._colReverseDirection != v){ this._colReverseDirection = v; this.$setItemChanged(); } } /** @prop {name:rowReverseDirection, tips:"行排列方向是否逆向。默认为false,会以从上到下/从左到右方式换行。为true则会逆向。你必须设置高/宽为非0的值才可以真正起效", type:bool, default:false} */ get rowReverseDirection() { return this._rowReverseDirection; } set rowReverseDirection(v:boolean) { if(this._rowReverseDirection != v){ this._rowReverseDirection = v; this.$setItemChanged(); } } /** @prop {name:colReverseZOrder, tips:"列排列时是否将ZOrder逆序。默认为false,后加入父容器的子节点会将先加入的盖住", type:bool, default:false} */ get colReverseZOrder() { return this._colReverseZOrder; } set colReverseZOrder(v:any) { if(this._colReverseZOrder != v){ this._colReverseZOrder = v; } } /** @prop {name:rowReverseZOrder, tips:"行排列时是否将ZOrder逆序。默认为false,后加入父容器的子节点会将先加入的盖住", type:bool, default:false} */ get rowReverseZOrder() { return this._rowReverseZOrder; } set rowReverseZOrder(v:any) { if(this._rowReverseZOrder != v){ this._rowReverseZOrder = v; } } /** 不受zOrder干扰的宿主子节点列表 */ get children(){ return this._children; } measureWidth():number{ let r = 0; let isHType = this.type == 'Horizontal'; let eachRowWH = this._getEachRowWH(); for(let i of eachRowWH) { //横向排列的情况下比较哪行的宽度最大 if(isHType) { r = Math.max(r, i.x); } //纵向排列的情况下则需将所有行的宽度相加 else { r += i.x; } } return isHType ? r : r + (eachRowWH.length - 1) * this._lineMargin; } measureHeight():number{ let r = 0; let isHType = this.type == 'Horizontal'; let eachRowWH = this._getEachRowWH(); for(let i of eachRowWH) { //横向排列的情况下需将所有行的高度相加 if(isHType) { r += i.y; } //纵向排列的情况下比较哪行的高度最大 else { r = Math.max(r, i.y); } } return isHType ? r + (eachRowWH.length - 1) * this._lineMargin : r; } $setItemChanged(changeSizeReason?:string){ if(!this.owner)return; if(this.ignoreChildrenScale && (changeSizeReason == 'scaleX' || changeSizeReason == 'scaleY'))return; if (!this._itemChanged){ this._itemChanged=true; this.owner.callLater(this.changeItems.bind(this)); } } changeItems(){ this._itemChanged=false; let o:Laya.Sprite = this.owner as Laya.Sprite; let isHType = this.type == 'Horizontal'; let isColDirectionReversed = this._colReverseDirection; let isRowDirectionReversed = this._rowReverseDirection; let offsetRow = isRowDirectionReversed ? (isHType ? o.height : o.width) : 0; let eachRowWH = this._getEachRowWH(); let allRows = this._getAllRows(); let allRowsLength = allRows.length; allRows.forEach((row:Sprite[], rowIndex:number) => { //>>>行循环开始 let rowWH = eachRowWH[rowIndex]; let rowSize = isHType ? rowWH.y : rowWH.x; let offsetCol = isColDirectionReversed ? (isHType ? o.width : o.height) : 0; let rowLength = row.length; //>>>列循环开始 row.forEach((i:UIComponent, iIndex:number) => { let zOrder = this._colReverseZOrder ? rowLength - iIndex : 0; zOrder += this._rowReverseZOrder ? 1000 * (allRowsLength - rowIndex) : 0; i.zOrder = zOrder; let [w, h, ax, ay] = [i.width * i.scaleX, i.height * i.scaleY, GameUtils.toNumber(i.anchorX), GameUtils.toNumber(i.anchorY)]; if(isHType) { i.x = isColDirectionReversed ? (offsetCol - (1 - ax) * w) : (offsetCol + ax * w); offsetCol = isColDirectionReversed ? offsetCol - (w + this._space) : offsetCol + (w + this._space); if (this._align=="min"){//顶对齐 i.y = ay * h; }else if (this._align=="middle"){//中心对齐 i.y = rowSize * 0.5 + (ay - 0.5) * h; }else if (this._align=="max"){//底对齐 i.y = rowSize + (ay - 1) * h; } if(isRowDirectionReversed)i.y -= (1 - ay) * h; i.y += offsetRow; } else { i.y = isColDirectionReversed ? (offsetCol - (1 - ay) * h) : (offsetCol + ay * h); offsetCol = isColDirectionReversed ? offsetCol - (h + this._space) : offsetCol + (h + this._space); if (this._align=="min"){//左对齐 i.x = ax * w; }else if (this._align=="middle"){//中心对齐 i.x = rowSize * 0.5 + (ax - 0.5) * w; }else if (this._align=="max"){//右对齐 i.x = rowSize + (ax - 1) * w; } if(isRowDirectionReversed)i.x -= (1 - ax) * w; i.x += offsetRow; } }); //<<<列循环结束 offsetRow = isRowDirectionReversed ? (offsetRow - this._lineMargin - rowSize) : (offsetRow + this._lineMargin + rowSize); //<<<行循环结束 }); (o as any)._sizeChanged(); } /** 将所有可见子节点按照其所在行分好后返回 */ private _getAllRows():Sprite[][]{ let children:Sprite[] = this._children.filter(c=>(c as Sprite).visible) as Sprite[]; if(children.length == 0) return []; //自动分行的情况 if(this._wrap <= 0){ let isHType = this.type == 'Horizontal'; let o:Laya.Sprite = this.owner as Laya.Sprite; //横向排列时,判断是否指定owner的宽度,若未指定则所有子节点都在同一行上,否则排满宽度后进行换行 //纵向排列时则根据owner的宽度来判断换行时机 let [fixedW, fixedH] = [o._width, o._height]; if((isHType && !fixedW) || (!isHType && !fixedH))return [children]; let r = [], total = 0, crtRow = [], maximum = isHType ? fixedW : fixedH; for(let i of children) { let size = isHType ? (i.width * i.scaleX) : (i.height * i.scaleY); crtRow.push(i); if(crtRow.length === 1)r.push(crtRow); //换行 if(total + size >= maximum) { crtRow = []; total = 0; } else { total += size + this._space; } } return r; } return _.chunk(children, this._wrap); } /** 获取每一行的行高与行宽 */ private _getEachRowWH():Point[]{ let isHType = this.type == 'Horizontal'; let maximum, total; return this._getAllRows().map((row:Sprite[])=>{ let p:Point; maximum = total = 0; for(let i of row) { let [w, h] = [i.width * i.scaleX, i.height * i.scaleY]; //横向排布的话,行高为最高元素的高度,行宽为所有元素宽度累积值 if(isHType) { maximum = Math.max(maximum, h); total += w; } //纵向排布的话,行高为所有元素高度累积值,行宽为最宽元素的宽度 else { maximum = Math.max(maximum, w); total += h; } } total += this._space * (row.length - 1); if(isHType) { p = new Point(total, maximum); } else { p = new Point(maximum, total); } return p; }); } }