LayoutGroup:LayoutGroup是控制子布局对象组件的基类,实现了ILayoutElement和ILayoutGroup接口,继承自UIBehaviour,说明它既是一个布局元素也是一个布局控制器,HorizontalLayoutGroup、VerticalLayoutGroup、GridLayoutGroup都继承或间接继承了此类。
源码解析
特性
[DisallowMultipleComponent]:不允许组件对象挂载重复的这个脚本。
[ExecuteAlways]:确保脚本在编辑器和游戏运行时都能被执行。
[RequireComponent(typeof(RectTransform))]:确保组件对象挂载了RectTransform组件。
内部实现
m_Padding:可以在Unity的Inspector窗口中直观的调整和配置这个矩形区域的边距偏移量,而无需通过代码进行硬编码。
1 2 3 4 protected RectOffset m_Padding = new RectOffset();public RectOffset padding { get { return m_Padding; } set { SetProperty(ref m_Padding, value );
SetProperty:帮助器方法,用于在给定属性发生更改时设置该属性。
这是类似MVVM模式中的使用方法
1 2 3 4 5 6 7 protected void SetProperty <T >(ref T currentValue, T newValue ){ if ((currentValue == null && newValue == null ) || (currentValue != null && currentValue.Equals(newValue))) return ; currentValue = newValue; SetDirty(); }
SetDirty:标记LayoutGroup为Dirty,标记当前对象的布局是否需要重新构建,如果是失活状态将不会被标记。
1 2 3 4 5 6 7 8 9 10 11 12 13 protected void SetDirty (){ if (!IsActive()) return ; if (!CanvasUpdateRegistry.IsRebuildingLayout()) LayoutRebuilder.MarkLayoutForRebuild(rectTransform); else StartCoroutine(DelayedSetDirty(rectTransform)); }
协程DelayedSetDirty:等待一帧之后再通过LayoutRebuilder.MarkLayoutForRebuild来标记当前对象的布局需要重新构建。可以使用LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform)方法强制更新布局。
1 2 3 4 5 6 IEnumerator DelayedSetDirty (RectTransform rectTransform ) { yield return null ; LayoutRebuilder.MarkLayoutForRebuild(rectTransform); }
子布局元素的对齐方式
1 2 3 [SerializeField ] protected TextAnchor m_ChildAlignment = TextAnchor.UpperLeft; public TextAnchor childAlignment { get { return m_ChildAlignment; } set { SetProperty(ref m_ChildAlignment, value ); } }
获取此布局元素的RectTransform组件
1 2 3 4 5 6 7 8 9 10 [System.NonSerialized ] private RectTransform m_Rect; protected RectTransform rectTransform{ get { if (m_Rect == null ) m_Rect = GetComponent<RectTransform>(); return m_Rect; } }
存储布局组中所有的子布局元素
1 2 [System.NonSerialized ] private List<RectTransform> m_RectChildren = new List<RectTransform>(); protected List<RectTransform> rectChildren { get { return m_RectChildren; } }
一些其他属性和字段
1 2 3 4 5 6 7 8 protected DrivenRectTransformTracker m_Tracker;private Vector2 m_TotalMinSize = Vector2.zero;private Vector2 m_TotalPreferredSize = Vector2.zero;private Vector2 m_TotalFlexibleSize = Vector2.zero;
CalculateLayoutInputHorizontal:实现ILayoutElement中的接口,子类可重写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public virtual void CalculateLayoutInputHorizontal (){ m_RectChildren.Clear(); var toIgnoreList = ListPool<Component>.Get(); for (int i = 0 ; i < rectTransform.childCount; i++) { var rect = rectTransform.GetChild(i) as RectTransform; if (rect == null || !rect.gameObject.activeInHierarchy) continue ; rect.GetComponents(typeof (ILayoutIgnorer), toIgnoreList); if (toIgnoreList.Count == 0 ) { m_RectChildren.Add(rect); continue ; } for (int j = 0 ; j < toIgnoreList.Count; j++) { var ignorer = (ILayoutIgnorer)toIgnoreList[j]; if (!ignorer.ignoreLayout) { m_RectChildren.Add(rect); break ; } } } ListPool<Component>.Release(toIgnoreList); m_Tracker.Clear(); }
抽象方法CalculateLayoutInputVertical计算垂直布局输入,子类必须实现
1 public abstract void CalculateLayoutInputVertical () ;
GetTotalMinSize:由子类调用给定轴上布局组的最小大小
1 2 3 4 5 protected float GetTotalMinSize (int axis ){ return m_TotalMinSize[axis]; }
GetTotalPreferredSize
1 2 3 4 5 protected float GetTotalPreferredSize (int axis ){ return m_TotalPreferredSize[axis]; }
GetTotalFlexibleSize:由子类调用给定轴上布局组的灵活大小。
1 2 3 4 5 protected float GetTotalFlexibleSize (int axis ){ return m_TotalFlexibleSize[axis]; }
ILayoutController中的接口,抽象方法,子类必须实现的回调
1 2 public abstract void SetLayoutHorizontal () ;public abstract void SetLayoutVertical () ;
构造方法
1 2 3 4 5 protected LayoutGroup (){ if (m_Padding == null ) m_Padding = new RectOffset(); }
生命周期OnEnable:启用时,标记为需要进行布局重建
1 2 3 4 5 protected override void OnEnable (){ base .OnEnable(); SetDirty(); }
生命周期OnDisable:失活时,清空布局追踪器,强制更新布局
1 2 3 4 5 6 protected override void OnDisable (){ m_Tracker.Clear(); LayoutRebuilder.MarkLayoutForRebuild(rectTransform); base .OnDisable(); }
OnDidApplyAnimationProperties:当动画属性改变时,标记为需要进行布局重建
1 2 3 4 protected override void OnDidApplyAnimationProperties (){ SetDirty(); }
OnRectTransformDimensionsChange:当动画属性改变时,标记为需要进行布局重建
1 2 3 4 protected override void OnDidApplyAnimationProperties (){ SetDirty(); }
GetStartOffset:根据给定轴和对齐方式计算第一个子布局元素起始偏移坐标,由子类调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 protected float GetStartOffset (int axis, float requiredSpaceWithoutPadding ){ float requiredSpace = requiredSpaceWithoutPadding + (axis == 0 ? padding.horizontal : padding.vertical); float availableSpace = rectTransform.rect.size[axis]; float surplusSpace = availableSpace - requiredSpace; float alignmentOnAxis = GetAlignmentOnAxis(axis); return (axis == 0 ? padding.left : padding.top) + surplusSpace * alignmentOnAxis; }
GetAlignmentOnAxis:以分数形式返回指定轴上的对齐方式,其中0为左/上,0.5为中,1为右/下。
1 2 3 4 5 6 7 8 9 10 protected float GetAlignmentOnAxis (int axis ){ if (axis == 0 ) return ((int )childAlignment % 3 ) * 0.5f ; else return ((int )childAlignment / 3 ) * 0.5f ; }
SetLayoutInputForAxis:用于设置给定轴的计算布局属性。由子类调用。
1 2 3 4 5 6 7 protected void SetLayoutInputForAxis (float totalMin, float totalPreferred, float totalFlexible, int axis ){ m_TotalMinSize[axis] = totalMin; m_TotalPreferredSize[axis] = totalPreferred; m_TotalFlexibleSize[axis] = totalFlexible; }
SetChildAlongAxis:设置子布局元素沿给定轴的位置和大小。
1 2 3 4 5 6 7 8 protected void SetChildAlongAxis (RectTransform rect, int axis, float pos ){ if (rect == null ) return ; SetChildAlongAxisWithScale(rect, axis, pos, 1.0f ); }
SetChildAlongAxisWithScale:设置子布局元素沿给定轴的位置和大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 protected void SetChildAlongAxisWithScale (RectTransform rect, int axis, float pos, float scaleFactor ){ if (rect == null ) return ; m_Tracker.Add(this , rect, DrivenTransformProperties.Anchors | (axis == 0 ? DrivenTransformProperties.AnchoredPositionX : DrivenTransformProperties.AnchoredPositionY)); rect.anchorMin = Vector2.up; rect.anchorMax = Vector2.up; Vector2 anchoredPosition = rect.anchoredPosition; anchoredPosition[axis] = (axis == 0 ) ? (pos + rect.sizeDelta[axis] * rect.pivot[axis] * scaleFactor) : (-pos - rect.sizeDelta[axis] * (1f - rect.pivot[axis]) * scaleFactor); rect.anchoredPosition = anchoredPosition; }
SetChildAlongAxis:上面方法的重载,多了一个参数size,子布局元素的尺寸大小
1 2 3 4 5 6 7 protected void SetChildAlongAxis (RectTransform rect, int axis, float pos, float size ){ if (rect == null ) return ; SetChildAlongAxisWithScale(rect, axis, pos, size, 1.0f ); }
SetChildAlongAxisWithScale:上面方法的重载,多了一个参数size,子布局元素的尺寸大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected void SetChildAlongAxisWithScale (RectTransform rect, int axis, float pos, float size, float scaleFactor ){ if (rect == null ) return ; m_Tracker.Add(this , rect, DrivenTransformProperties.Anchors | (axis == 0 ? (DrivenTransformProperties.AnchoredPositionX | DrivenTransformProperties.SizeDeltaX) : (DrivenTransformProperties.AnchoredPositionY | DrivenTransformProperties.SizeDeltaY) ) ); rect.anchorMin = Vector2.up; rect.anchorMax = Vector2.up; Vector2 sizeDelta = rect.sizeDelta; sizeDelta[axis] = size; rect.sizeDelta = sizeDelta; Vector2 anchoredPosition = rect.anchoredPosition; anchoredPosition[axis] = (axis == 0 ) ? (pos + size * rect.pivot[axis] * scaleFactor) : (-pos - size * (1f - rect.pivot[axis]) * scaleFactor); rect.anchoredPosition = anchoredPosition; }
isRootLayoutGroup:判断当前对象是否是布局组的根布局对象组件。
1 2 3 4 5 6 7 8 9 10 private bool isRootLayoutGroup{ get { Transform parent = transform.parent; if (parent == null ) return true ; return transform.parent.GetComponent(typeof (ILayoutGroup)) == null ; } }
OnRectTransformDimensionsChange:如果当前对象是根布局对象,且RectTransform发生了变化,则标记为需要进行布局重建
1 2 3 4 5 6 protected override void OnRectTransformDimensionsChange (){ base .OnRectTransformDimensionsChange(); if (isRootLayoutGroup) SetDirty(); }
OnTransformChildrenChanged:子布局元素如果的Transform如果发生改变则标记为需要进行布局重建
1 2 3 4 protected virtual void OnTransformChildrenChanged (){ SetDirty(); }
在编辑器模式下,每次对该组件的属性进行修改并保存时自动调用OnValidate方法,标记为需要进行布局重建
1 2 3 4 5 6 #if UNITY_EDITOR protected override void OnValidate () { SetDirty(); } #endif