FlowLayoutGroup.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. namespace SRF.UI.Layout
  2. {
  3. using System.Collections.Generic;
  4. using Internal;
  5. using UnityEngine;
  6. using UnityEngine.UI;
  7. /// <summary>
  8. /// Layout Group controller that arranges children in rows, fitting as many on a line until total width exceeds parent
  9. /// bounds
  10. /// </summary>
  11. [AddComponentMenu(ComponentMenuPaths.FlowLayoutGroup)]
  12. public class FlowLayoutGroup : LayoutGroup
  13. {
  14. /// <summary>
  15. /// Holds the rects that will make up the current row being processed
  16. /// </summary>
  17. private readonly IList<RectTransform> _rowList = new List<RectTransform>();
  18. private float _layoutHeight;
  19. public bool ChildForceExpandHeight = false;
  20. public bool ChildForceExpandWidth = false;
  21. public float Spacing = 0f;
  22. protected bool IsCenterAlign
  23. {
  24. get
  25. {
  26. return childAlignment == TextAnchor.LowerCenter || childAlignment == TextAnchor.MiddleCenter ||
  27. childAlignment == TextAnchor.UpperCenter;
  28. }
  29. }
  30. protected bool IsRightAlign
  31. {
  32. get
  33. {
  34. return childAlignment == TextAnchor.LowerRight || childAlignment == TextAnchor.MiddleRight ||
  35. childAlignment == TextAnchor.UpperRight;
  36. }
  37. }
  38. protected bool IsMiddleAlign
  39. {
  40. get
  41. {
  42. return childAlignment == TextAnchor.MiddleLeft || childAlignment == TextAnchor.MiddleRight ||
  43. childAlignment == TextAnchor.MiddleCenter;
  44. }
  45. }
  46. protected bool IsLowerAlign
  47. {
  48. get
  49. {
  50. return childAlignment == TextAnchor.LowerLeft || childAlignment == TextAnchor.LowerRight ||
  51. childAlignment == TextAnchor.LowerCenter;
  52. }
  53. }
  54. public override void CalculateLayoutInputHorizontal()
  55. {
  56. base.CalculateLayoutInputHorizontal();
  57. var minWidth = GetGreatestMinimumChildWidth() + padding.left + padding.right;
  58. SetLayoutInputForAxis(minWidth, -1, -1, 0);
  59. }
  60. public override void SetLayoutHorizontal()
  61. {
  62. SetLayout(rectTransform.rect.width, 0, false);
  63. }
  64. public override void SetLayoutVertical()
  65. {
  66. SetLayout(rectTransform.rect.width, 1, false);
  67. }
  68. public override void CalculateLayoutInputVertical()
  69. {
  70. _layoutHeight = SetLayout(rectTransform.rect.width, 1, true);
  71. }
  72. /// <summary>
  73. /// Main layout method
  74. /// </summary>
  75. /// <param name="width">Width to calculate the layout with</param>
  76. /// <param name="axis">0 for horizontal axis, 1 for vertical</param>
  77. /// <param name="layoutInput">If true, sets the layout input for the axis. If false, sets child position for axis</param>
  78. public float SetLayout(float width, int axis, bool layoutInput)
  79. {
  80. var groupHeight = rectTransform.rect.height;
  81. // Width that is available after padding is subtracted
  82. var workingWidth = rectTransform.rect.width - padding.left - padding.right;
  83. // Accumulates the total height of the rows, including spacing and padding.
  84. var yOffset = IsLowerAlign ? padding.bottom : (float)padding.top;
  85. var currentRowWidth = 0f;
  86. var currentRowHeight = 0f;
  87. for (var i = 0; i < rectChildren.Count; i++)
  88. {
  89. // LowerAlign works from back to front
  90. var index = IsLowerAlign ? rectChildren.Count - 1 - i : i;
  91. var child = rectChildren[index];
  92. var childWidth = LayoutUtility.GetPreferredSize(child, 0);
  93. var childHeight = LayoutUtility.GetPreferredSize(child, 1);
  94. // Max child width is layout group with - padding
  95. childWidth = Mathf.Min(childWidth, workingWidth);
  96. // Apply spacing if not the first element in a row
  97. if (_rowList.Count > 0)
  98. {
  99. currentRowWidth += Spacing;
  100. }
  101. // If adding this element would exceed the bounds of the row,
  102. // go to a new line after processing the current row
  103. if (currentRowWidth + childWidth > workingWidth)
  104. {
  105. // Undo spacing addition if we're moving to a new line (Spacing is not applied on edges)
  106. currentRowWidth -= Spacing;
  107. // Process current row elements positioning
  108. if (!layoutInput)
  109. {
  110. var h = CalculateRowVerticalOffset(groupHeight, yOffset, currentRowHeight);
  111. LayoutRow(_rowList, currentRowWidth, currentRowHeight, workingWidth, padding.left, h, axis);
  112. }
  113. // Clear existing row
  114. _rowList.Clear();
  115. // Add the current row height to total height accumulator, and reset to 0 for the next row
  116. yOffset += currentRowHeight;
  117. yOffset += Spacing;
  118. currentRowHeight = 0;
  119. currentRowWidth = 0;
  120. }
  121. currentRowWidth += childWidth;
  122. _rowList.Add(child);
  123. // We need the largest element height to determine the starting position of the next line
  124. if (childHeight > currentRowHeight)
  125. {
  126. currentRowHeight = childHeight;
  127. }
  128. }
  129. if (!layoutInput)
  130. {
  131. var h = CalculateRowVerticalOffset(groupHeight, yOffset, currentRowHeight);
  132. // Layout the final row
  133. LayoutRow(_rowList, currentRowWidth, currentRowHeight, workingWidth, padding.left, h, axis);
  134. }
  135. _rowList.Clear();
  136. // Add the last rows height to the height accumulator
  137. yOffset += currentRowHeight;
  138. yOffset += IsLowerAlign ? padding.top : padding.bottom;
  139. if (layoutInput)
  140. {
  141. if (axis == 1)
  142. {
  143. SetLayoutInputForAxis(yOffset, yOffset, -1, axis);
  144. }
  145. }
  146. return yOffset;
  147. }
  148. private float CalculateRowVerticalOffset(float groupHeight, float yOffset, float currentRowHeight)
  149. {
  150. float h;
  151. if (IsLowerAlign)
  152. {
  153. h = groupHeight - yOffset - currentRowHeight;
  154. }
  155. else if (IsMiddleAlign)
  156. {
  157. h = groupHeight * 0.5f - _layoutHeight * 0.5f + yOffset;
  158. }
  159. else
  160. {
  161. h = yOffset;
  162. }
  163. return h;
  164. }
  165. protected void LayoutRow(IList<RectTransform> contents, float rowWidth, float rowHeight, float maxWidth,
  166. float xOffset, float yOffset, int axis)
  167. {
  168. var xPos = xOffset;
  169. if (!ChildForceExpandWidth && IsCenterAlign)
  170. {
  171. xPos += (maxWidth - rowWidth) * 0.5f;
  172. }
  173. else if (!ChildForceExpandWidth && IsRightAlign)
  174. {
  175. xPos += (maxWidth - rowWidth);
  176. }
  177. var extraWidth = 0f;
  178. if (ChildForceExpandWidth)
  179. {
  180. var flexibleChildCount = 0;
  181. for (var i = 0; i < _rowList.Count; i++)
  182. {
  183. if (LayoutUtility.GetFlexibleWidth(_rowList[i]) > 0f)
  184. {
  185. flexibleChildCount++;
  186. }
  187. }
  188. if (flexibleChildCount > 0)
  189. {
  190. extraWidth = (maxWidth - rowWidth) / flexibleChildCount;
  191. }
  192. }
  193. for (var j = 0; j < _rowList.Count; j++)
  194. {
  195. var index = IsLowerAlign ? _rowList.Count - 1 - j : j;
  196. var rowChild = _rowList[index];
  197. var rowChildWidth = LayoutUtility.GetPreferredSize(rowChild, 0);
  198. if (LayoutUtility.GetFlexibleWidth(rowChild) > 0f)
  199. {
  200. rowChildWidth += extraWidth;
  201. }
  202. var rowChildHeight = LayoutUtility.GetPreferredSize(rowChild, 1);
  203. if (ChildForceExpandHeight)
  204. {
  205. rowChildHeight = rowHeight;
  206. }
  207. rowChildWidth = Mathf.Min(rowChildWidth, maxWidth);
  208. var yPos = yOffset;
  209. if (IsMiddleAlign)
  210. {
  211. yPos += (rowHeight - rowChildHeight) * 0.5f;
  212. }
  213. else if (IsLowerAlign)
  214. {
  215. yPos += (rowHeight - rowChildHeight);
  216. }
  217. if (axis == 0)
  218. {
  219. #if UNITY_2019_1
  220. SetChildAlongAxis(rowChild, 0, 1f, xPos, rowChildWidth);
  221. #else
  222. SetChildAlongAxis(rowChild, 0, xPos, rowChildWidth);
  223. #endif
  224. }
  225. else
  226. {
  227. #if UNITY_2019_1
  228. SetChildAlongAxis(rowChild, 1, 1f, yPos, rowChildHeight);
  229. #else
  230. SetChildAlongAxis(rowChild, 1, yPos, rowChildHeight);
  231. #endif
  232. }
  233. xPos += rowChildWidth + Spacing;
  234. }
  235. }
  236. public float GetGreatestMinimumChildWidth()
  237. {
  238. var max = 0f;
  239. for (var i = 0; i < rectChildren.Count; i++)
  240. {
  241. var w = LayoutUtility.GetMinWidth(rectChildren[i]);
  242. max = Mathf.Max(w, max);
  243. }
  244. return max;
  245. }
  246. }
  247. }