FlowLayoutGroup.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. /// Credit Simie
  2. /// Sourced from - http://forum.unity3d.com/threads/flowlayoutgroup.296709/
  3. /// Example http://forum.unity3d.com/threads/flowlayoutgroup.296709/
  4. /// Update by Martin Sharkbomb - http://forum.unity3d.com/threads/flowlayoutgroup.296709/#post-1977028
  5. /// Last item alignment fix by Vicente Russo - https://bitbucket.org/SimonDarksideJ/unity-ui-extensions/issues/22/flow-layout-group-align
  6. /// Vertical Flow by Ramon Molossi
  7. using System.Collections.Generic;
  8. namespace UnityEngine.UI.Extensions
  9. {
  10. /// <summary>
  11. /// Layout Group controller that arranges children in bars, fitting as many on a line until total size exceeds parent bounds
  12. /// </summary>
  13. [AddComponentMenu("Layout/Extensions/Flow Layout Group")]
  14. public class FlowLayoutGroup : LayoutGroup
  15. {
  16. public enum Axis { Horizontal = 0, Vertical = 1 }
  17. public float SpacingX = 0f;
  18. public float SpacingY = 0f;
  19. public bool ExpandHorizontalSpacing = false;
  20. public bool ChildForceExpandWidth = false;
  21. public bool ChildForceExpandHeight = false;
  22. public bool invertOrder = false;
  23. private float _layoutHeight;
  24. private float _layoutWidth;
  25. [SerializeField] protected Axis m_StartAxis = Axis.Horizontal;
  26. public Axis startAxis { get { return m_StartAxis; } set { SetProperty(ref m_StartAxis, value); } }
  27. public override void CalculateLayoutInputHorizontal()
  28. {
  29. if (startAxis == Axis.Horizontal) {
  30. base.CalculateLayoutInputHorizontal ();
  31. var minWidth = GetGreatestMinimumChildWidth () + padding.left + padding.right;
  32. SetLayoutInputForAxis (minWidth, -1, -1, 0);
  33. } else {
  34. _layoutWidth = SetLayout (0, true);
  35. }
  36. }
  37. public override void SetLayoutHorizontal()
  38. {
  39. SetLayout(0, false);
  40. }
  41. public override void SetLayoutVertical()
  42. {
  43. SetLayout(1, false);
  44. }
  45. public override void CalculateLayoutInputVertical()
  46. {
  47. if (startAxis == Axis.Horizontal) {
  48. _layoutHeight = SetLayout (1, true);
  49. } else {
  50. base.CalculateLayoutInputHorizontal ();
  51. var minHeight = GetGreatestMinimumChildHeigth () + padding.bottom + padding.top;
  52. SetLayoutInputForAxis (minHeight, -1, -1, 1);
  53. }
  54. }
  55. protected bool IsCenterAlign
  56. {
  57. get
  58. {
  59. return childAlignment == TextAnchor.LowerCenter || childAlignment == TextAnchor.MiddleCenter ||
  60. childAlignment == TextAnchor.UpperCenter;
  61. }
  62. }
  63. protected bool IsRightAlign
  64. {
  65. get
  66. {
  67. return childAlignment == TextAnchor.LowerRight || childAlignment == TextAnchor.MiddleRight ||
  68. childAlignment == TextAnchor.UpperRight;
  69. }
  70. }
  71. protected bool IsMiddleAlign
  72. {
  73. get
  74. {
  75. return childAlignment == TextAnchor.MiddleLeft || childAlignment == TextAnchor.MiddleRight ||
  76. childAlignment == TextAnchor.MiddleCenter;
  77. }
  78. }
  79. protected bool IsLowerAlign
  80. {
  81. get
  82. {
  83. return childAlignment == TextAnchor.LowerLeft || childAlignment == TextAnchor.LowerRight ||
  84. childAlignment == TextAnchor.LowerCenter;
  85. }
  86. }
  87. /// <summary>
  88. /// Holds the rects that will make up the current bar being processed
  89. /// </summary>
  90. private readonly IList<RectTransform> _itemList = new List<RectTransform>();
  91. /// <summary>
  92. /// Main layout method
  93. /// </summary>
  94. /// <param name="width">Width to calculate the layout with</param>
  95. /// <param name="axis">0 for horizontal axis, 1 for vertical</param>
  96. /// <param name="layoutInput">If true, sets the layout input for the axis. If false, sets child position for axis</param>
  97. public float SetLayout(int axis, bool layoutInput)
  98. {
  99. //container height and width
  100. var groupHeight = rectTransform.rect.height;
  101. var groupWidth = rectTransform.rect.width;
  102. float spacingBetweenBars = 0;
  103. float spacingBetweenElements = 0;
  104. float offset = 0;
  105. float counterOffset = 0;
  106. float groupSize = 0;
  107. float workingSize = 0;
  108. if (startAxis == Axis.Horizontal) {
  109. groupSize = groupHeight;
  110. workingSize = groupWidth - padding.left - padding.right;
  111. if (IsLowerAlign) {
  112. offset = (float)padding.bottom;
  113. counterOffset = (float)padding.top;
  114. } else {
  115. offset = (float)padding.top;
  116. counterOffset = (float)padding.bottom;
  117. }
  118. spacingBetweenBars = SpacingY;
  119. spacingBetweenElements = SpacingX;
  120. } else if (startAxis == Axis.Vertical) {
  121. groupSize = groupWidth;
  122. workingSize = groupHeight - padding.top - padding.bottom;
  123. if (IsRightAlign) {
  124. offset = (float)padding.right;
  125. counterOffset = (float)padding.left;
  126. } else {
  127. offset = (float)padding.left;
  128. counterOffset = (float)padding.right;
  129. }
  130. spacingBetweenBars = SpacingX;
  131. spacingBetweenElements = SpacingY;
  132. }
  133. var currentBarSize = 0f;
  134. var currentBarSpace = 0f;
  135. for (var i = 0; i < rectChildren.Count; i++) {
  136. int index = i;
  137. var child = rectChildren [index];
  138. float childSize = 0;
  139. float childOtherSize = 0;
  140. //get height and width of elements.
  141. if (startAxis == Axis.Horizontal) {
  142. if (invertOrder) {
  143. index = IsLowerAlign ? rectChildren.Count - 1 - i : i;
  144. }
  145. child = rectChildren [index];
  146. childSize = LayoutUtility.GetPreferredSize (child, 0);
  147. childSize = Mathf.Min (childSize, workingSize);
  148. childOtherSize = LayoutUtility.GetPreferredSize (child, 1);
  149. childOtherSize = Mathf.Min (childOtherSize, workingSize);
  150. } else if (startAxis == Axis.Vertical) {
  151. if (invertOrder) {
  152. index = IsRightAlign ? rectChildren.Count - 1 - i : i;
  153. }
  154. child = rectChildren [index];
  155. childSize = LayoutUtility.GetPreferredSize (child, 1);
  156. childSize = Mathf.Min (childSize, workingSize);
  157. childOtherSize = LayoutUtility.GetPreferredSize (child, 0);
  158. childOtherSize = Mathf.Min (childOtherSize, workingSize);
  159. }
  160. // If adding this element would exceed the bounds of the container,
  161. // go to a new bar after processing the current bar
  162. if (currentBarSize + childSize > workingSize) {
  163. currentBarSize -= spacingBetweenElements;
  164. // Process current bar elements positioning
  165. if (!layoutInput) {
  166. if (startAxis == Axis.Horizontal) {
  167. float newOffset = CalculateRowVerticalOffset (groupSize, offset, currentBarSpace);
  168. LayoutRow (_itemList, currentBarSize, currentBarSpace, workingSize, padding.left, newOffset, axis);
  169. } else if (startAxis == Axis.Vertical) {
  170. float newOffset = CalculateColHorizontalOffset (groupSize, offset, currentBarSpace);
  171. LayoutCol (_itemList, currentBarSpace, currentBarSize, workingSize, newOffset, padding.top, axis);
  172. }
  173. }
  174. // Clear existing bar
  175. _itemList.Clear ();
  176. // Add the current bar space to total barSpace accumulator, and reset to 0 for the next row
  177. offset += currentBarSpace;
  178. offset += spacingBetweenBars;
  179. currentBarSpace = 0;
  180. currentBarSize = 0;
  181. }
  182. currentBarSize += childSize;
  183. _itemList.Add (child);
  184. // We need the largest element height to determine the starting position of the next line
  185. if (childOtherSize > currentBarSpace) {
  186. currentBarSpace = childOtherSize;
  187. }
  188. // Don't do this for the last one
  189. if (i < rectChildren.Count - 1){
  190. currentBarSize += spacingBetweenElements;
  191. }
  192. }
  193. // Layout the final bar
  194. if (!layoutInput) {
  195. if (startAxis == Axis.Horizontal) {
  196. float newOffset = CalculateRowVerticalOffset (groupHeight, offset, currentBarSpace);
  197. currentBarSize -= spacingBetweenElements;
  198. LayoutRow (_itemList, currentBarSize, currentBarSpace, workingSize - (ChildForceExpandWidth ? 0 : spacingBetweenElements), padding.left, newOffset, axis);
  199. }else if (startAxis == Axis.Vertical) {
  200. float newOffset = CalculateColHorizontalOffset(groupWidth, offset, currentBarSpace);
  201. currentBarSize -= spacingBetweenElements;
  202. LayoutCol(_itemList, currentBarSpace, currentBarSize, workingSize - (ChildForceExpandHeight ? 0 : spacingBetweenElements), newOffset, padding.top, axis);
  203. }
  204. }
  205. _itemList.Clear();
  206. // Add the last bar space to the barSpace accumulator
  207. offset += currentBarSpace;
  208. offset += counterOffset;
  209. if (layoutInput) {
  210. SetLayoutInputForAxis(offset, offset, -1, axis);
  211. }
  212. return offset;
  213. }
  214. private float CalculateRowVerticalOffset(float groupHeight, float yOffset, float currentRowHeight)
  215. {
  216. if (IsLowerAlign) {
  217. return groupHeight - yOffset - currentRowHeight;
  218. } else if (IsMiddleAlign) {
  219. return groupHeight * 0.5f - _layoutHeight * 0.5f + yOffset;
  220. } else {
  221. return yOffset;
  222. }
  223. }
  224. private float CalculateColHorizontalOffset(float groupWidth, float xOffset, float currentColWidth)
  225. {
  226. if (IsRightAlign) {
  227. return groupWidth - xOffset - currentColWidth;
  228. } else if (IsCenterAlign) {
  229. return groupWidth * 0.5f - _layoutWidth * 0.5f + xOffset;
  230. } else {
  231. return xOffset;
  232. }
  233. }
  234. protected void LayoutRow(IList<RectTransform> contents, float rowWidth, float rowHeight, float maxWidth, float xOffset, float yOffset, int axis)
  235. {
  236. var xPos = xOffset;
  237. if (!ChildForceExpandWidth && IsCenterAlign) {
  238. xPos += (maxWidth - rowWidth) * 0.5f;
  239. } else if (!ChildForceExpandWidth && IsRightAlign) {
  240. xPos += (maxWidth - rowWidth);
  241. }
  242. var extraWidth = 0f;
  243. var extraSpacing = 0f;
  244. if (ChildForceExpandWidth) {
  245. extraWidth = (maxWidth - rowWidth)/_itemList.Count;
  246. }
  247. else if (ExpandHorizontalSpacing) {
  248. extraSpacing = (maxWidth - rowWidth)/(_itemList.Count - 1);
  249. if (_itemList.Count > 1) {
  250. if (IsCenterAlign) {
  251. xPos -= extraSpacing * 0.5f * (_itemList.Count - 1);
  252. } else if (IsRightAlign) {
  253. xPos -= extraSpacing * (_itemList.Count - 1);
  254. }
  255. }
  256. }
  257. for (var j = 0; j < _itemList.Count; j++) {
  258. var index = IsLowerAlign ? _itemList.Count - 1 - j : j;
  259. var rowChild = _itemList[index];
  260. var rowChildWidth = LayoutUtility.GetPreferredSize(rowChild, 0) + extraWidth;
  261. var rowChildHeight = LayoutUtility.GetPreferredSize(rowChild, 1);
  262. if (ChildForceExpandHeight)
  263. rowChildHeight = rowHeight;
  264. rowChildWidth = Mathf.Min(rowChildWidth, maxWidth);
  265. var yPos = yOffset;
  266. if (IsMiddleAlign) {
  267. yPos += (rowHeight - rowChildHeight) * 0.5f;
  268. } else if (IsLowerAlign) {
  269. yPos += (rowHeight - rowChildHeight);
  270. }
  271. if (ExpandHorizontalSpacing && j > 0) {
  272. xPos += extraSpacing;
  273. }
  274. if (axis == 0) {
  275. SetChildAlongAxis (rowChild, 0, xPos, rowChildWidth);
  276. } else {
  277. SetChildAlongAxis (rowChild, 1, yPos, rowChildHeight);
  278. }
  279. // Don't do horizontal spacing for the last one
  280. if (j < _itemList.Count - 1) {
  281. xPos += rowChildWidth + SpacingX;
  282. }
  283. }
  284. }
  285. protected void LayoutCol(IList<RectTransform> contents, float colWidth, float colHeight, float maxHeight, float xOffset, float yOffset, int axis)
  286. {
  287. var yPos = yOffset;
  288. if (!ChildForceExpandHeight && IsMiddleAlign) {
  289. yPos += (maxHeight - colHeight) * 0.5f;
  290. } else if (!ChildForceExpandHeight && IsLowerAlign) {
  291. yPos += (maxHeight - colHeight);
  292. }
  293. var extraHeight = 0f;
  294. var extraSpacing = 0f;
  295. if (ChildForceExpandHeight) {
  296. extraHeight = (maxHeight - colHeight)/_itemList.Count;
  297. }
  298. else if (ExpandHorizontalSpacing) {
  299. extraSpacing = (maxHeight - colHeight)/(_itemList.Count - 1);
  300. if (_itemList.Count > 1) {
  301. if (IsMiddleAlign) {
  302. yPos -= extraSpacing * 0.5f * (_itemList.Count - 1);
  303. } else if (IsLowerAlign) {
  304. yPos -= extraSpacing * (_itemList.Count - 1);
  305. }
  306. }
  307. }
  308. for (var j = 0; j < _itemList.Count; j++) {
  309. var index = IsRightAlign ? _itemList.Count - 1 - j : j;
  310. var rowChild = _itemList[index];
  311. var rowChildWidth = LayoutUtility.GetPreferredSize(rowChild, 0) ;
  312. var rowChildHeight = LayoutUtility.GetPreferredSize(rowChild, 1) + extraHeight;
  313. if (ChildForceExpandWidth) {
  314. rowChildWidth = colWidth;
  315. }
  316. rowChildHeight = Mathf.Min(rowChildHeight, maxHeight);
  317. var xPos = xOffset;
  318. if (IsCenterAlign) {
  319. xPos += (colWidth - rowChildWidth) * 0.5f;
  320. } else if (IsRightAlign) {
  321. xPos += (colWidth - rowChildWidth);
  322. }
  323. //
  324. if (ExpandHorizontalSpacing && j > 0) {
  325. yPos += extraSpacing;
  326. }
  327. if (axis == 0) {
  328. SetChildAlongAxis (rowChild, 0, xPos, rowChildWidth);
  329. } else {
  330. SetChildAlongAxis (rowChild, 1, yPos, rowChildHeight);
  331. }
  332. // Don't do vertical spacing for the last one
  333. if (j < _itemList.Count - 1) {
  334. yPos += rowChildHeight + SpacingY;
  335. }
  336. }
  337. }
  338. public float GetGreatestMinimumChildWidth()
  339. {
  340. var max = 0f;
  341. for (var i = 0; i < rectChildren.Count; i++) {
  342. var w = LayoutUtility.GetMinWidth(rectChildren[i]);
  343. max = Mathf.Max(w, max);
  344. }
  345. return max;
  346. }
  347. public float GetGreatestMinimumChildHeigth()
  348. {
  349. var max = 0f;
  350. for (var i = 0; i < rectChildren.Count; i++) {
  351. var w = LayoutUtility.GetMinHeight(rectChildren[i]);
  352. max = Mathf.Max(w, max);
  353. }
  354. return max;
  355. }
  356. }
  357. }