1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.ComponentModel;
21 namespace Tizen.NUI.Components
24 /// layouter for CollectionView to display items in linear layout.
26 /// <since_tizen> 9 </since_tizen>
27 public class LinearLayouter : ItemsLayouter
29 private readonly List<float> ItemPosition = new List<float>();
30 private readonly List<float> ItemSize = new List<float>();
31 private int ItemSizeChanged = -1;
32 private CollectionView colView;
33 private bool hasHeader;
34 private float headerSize;
35 private Extents headerMargin;
36 private bool hasFooter;
37 private float footerSize;
38 private Extents footerMargin;
39 private bool isGrouped;
40 private readonly List<GroupInfo> groups = new List<GroupInfo>();
41 private float groupHeaderSize;
42 private Extents groupHeaderMargin;
43 private float groupFooterSize;
44 private Extents groupFooterMargin;
45 private GroupInfo Visited;
46 private Timer requestLayoutTimer = null;
47 private bool isSourceEmpty;
50 /// Clean up ItemsLayouter.
52 /// <param name="view"> CollectionView of layouter.</param>
53 /// <remarks>please note that, view must be type of CollectionView</remarks>
54 /// <since_tizen> 9 </since_tizen>
55 public override void Initialize(RecyclerView view)
57 colView = view as CollectionView;
60 throw new ArgumentException("LinearLayouter only can be applied CollectionView.", nameof(view));
68 IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
70 RecyclerViewItem header = colView?.Header;
71 RecyclerViewItem footer = colView?.Footer;
73 int count = colView.InternalItemSource.Count;
77 MeasureChild(colView, header);
79 width = header.Layout != null? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
80 height = header.Layout != null? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
82 Extents itemMargin = header.Margin;
83 headerSize = IsHorizontal?
84 width + itemMargin.Start + itemMargin.End:
85 height + itemMargin.Top + itemMargin.Bottom;
86 headerMargin = new Extents(itemMargin);
89 colView.UnrealizeItem(header);
91 else hasHeader = false;
95 MeasureChild(colView, footer);
97 width = footer.Layout != null? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
98 height = footer.Layout != null? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
100 Extents itemMargin = footer.Margin;
101 footerSize = IsHorizontal?
102 width + itemMargin.Start + itemMargin.End:
103 height + itemMargin.Top + itemMargin.Bottom;
104 footerMargin = new Extents(itemMargin);
105 footer.Index = count - 1;
108 colView.UnrealizeItem(footer);
110 else hasFooter = false;
112 //No Internal Source exist.
113 if (count == (hasHeader? (hasFooter? 2 : 1) : 0))
115 isSourceEmpty = true;
116 base.Initialize(view);
119 isSourceEmpty = false;
121 int firstIndex = hasHeader? 1 : 0;
123 if (colView.IsGrouped)
127 if (colView.GroupHeaderTemplate != null)
129 while (!colView.InternalItemSource.IsGroupHeader(firstIndex)) firstIndex++;
130 //must be always true
131 if (colView.InternalItemSource.IsGroupHeader(firstIndex))
133 RecyclerViewItem groupHeader = colView.RealizeItem(firstIndex);
136 if (groupHeader == null) throw new Exception("[" + firstIndex + "] Group Header failed to realize!");
138 // Need to Set proper height or width on scroll direction.
139 if (groupHeader.Layout == null)
141 width = groupHeader.WidthSpecification;
142 height = groupHeader.HeightSpecification;
146 MeasureChild(colView, groupHeader);
148 width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
149 height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
151 // pick the StepCandidate.
152 Extents itemMargin = groupHeader.Margin;
153 groupHeaderSize = IsHorizontal?
154 width + itemMargin.Start + itemMargin.End:
155 height + itemMargin.Top + itemMargin.Bottom;
156 groupHeaderMargin = new Extents(itemMargin);
157 colView.UnrealizeItem(groupHeader);
162 groupHeaderSize = 0F;
165 if (colView.GroupFooterTemplate != null)
167 int firstFooter = firstIndex;
168 while (!colView.InternalItemSource.IsGroupFooter(firstFooter)) firstFooter++;
169 //must be always true
170 if (colView.InternalItemSource.IsGroupFooter(firstFooter))
172 RecyclerViewItem groupFooter = colView.RealizeItem(firstFooter);
174 if (groupFooter == null) throw new Exception("[" + firstFooter + "] Group Footer failed to realize!");
175 // Need to Set proper height or width on scroll direction.
176 if (groupFooter.Layout == null)
178 width = groupFooter.WidthSpecification;
179 height = groupFooter.HeightSpecification;
183 MeasureChild(colView, groupFooter);
185 width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
186 height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
188 // pick the StepCandidate.
189 Extents itemMargin = groupFooter.Margin;
190 groupFooterSize = IsHorizontal?
191 width + itemMargin.Start + itemMargin.End:
192 height + itemMargin.Top + itemMargin.Bottom;
193 groupFooterMargin = new Extents(itemMargin);
194 colView.UnrealizeItem(groupFooter);
199 groupFooterSize = 0F;
202 else isGrouped = false;
205 //Final Check of FirstIndex
206 while (colView.InternalItemSource.IsHeader(firstIndex) ||
207 colView.InternalItemSource.IsGroupHeader(firstIndex) ||
208 colView.InternalItemSource.IsGroupFooter(firstIndex))
210 if (colView.InternalItemSource.IsFooter(firstIndex))
221 RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
222 if (sizeDeligate == null)
225 throw new Exception("Cannot create content from DatTemplate.");
228 sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
230 // Need to Set proper height or width on scroll direction.
231 if (sizeDeligate.Layout == null)
233 width = sizeDeligate.WidthSpecification;
234 height = sizeDeligate.HeightSpecification;
238 MeasureChild(colView, sizeDeligate);
240 width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
241 height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
243 // pick the StepCandidate.
244 Extents itemMargin = sizeDeligate.Margin;
245 StepCandidate = IsHorizontal?
246 width + itemMargin.Start + itemMargin.End:
247 height + itemMargin.Top + itemMargin.Bottom;
248 CandidateMargin = new Extents(itemMargin);
249 if (StepCandidate == 0) StepCandidate = 1; //????
251 colView.UnrealizeItem(sizeDeligate);
254 float Current = IsHorizontal? Padding.Start : Padding.Top;
255 IGroupableItemSource source = colView.InternalItemSource;
256 GroupInfo currentGroup = null;
257 object currentParent = null;
258 for (int i = 0; i < count; i++)
260 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
262 if (i == 0 && hasHeader)
263 ItemSize.Add(headerSize);
264 else if (i == count - 1 && hasFooter)
265 ItemSize.Add(footerSize);
266 else if (source.IsGroupHeader(i))
267 ItemSize.Add(groupHeaderSize);
268 else if (source.IsGroupFooter(i))
269 ItemSize.Add(groupFooterSize);
270 else ItemSize.Add(StepCandidate);
274 if (source.IsHeader(i))
276 //ItemPosition.Add(Current);
277 Current += headerSize;
279 else if (source.IsFooter(i))
281 //ItemPosition.Add(Current);
282 Current += footerSize;
286 //GroupHeader must always exist in group usage.
287 //if (source.IsGroupHeader(i))
288 if (source.GetGroupParent(i) != currentParent)
290 currentParent = source.GetGroupParent(i);
291 float currentSize = (source.IsGroupHeader(i)? groupHeaderSize :
292 (source.IsGroupFooter(i)? groupFooterSize: StepCandidate));
293 currentGroup = new GroupInfo()
295 GroupParent = currentParent,
300 GroupSize = currentSize,
301 GroupPosition = Current
303 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
304 currentGroup.ItemPosition.Add(0);
305 groups.Add(currentGroup);
306 if (source.IsGroupHeader(i)) Current += currentSize;
309 else if (source.IsGroupFooter(i))
311 //currentGroup.hasFooter = true;
312 if (currentGroup != null)
314 currentGroup.Count++;
315 currentGroup.GroupSize += groupFooterSize;
316 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
317 currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
318 Current += groupFooterSize;
323 if (currentGroup != null)
325 currentGroup.Count++;
326 currentGroup.GroupSize += StepCandidate;
327 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
328 currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
329 Current += StepCandidate;
336 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
337 ItemPosition.Add(Current);
339 if (i == 0 && hasHeader) Current += headerSize;
340 else if (i == count - 1 && hasFooter) Current += footerSize;
341 else Current += StepCandidate;
345 ScrollContentSize = Current + (IsHorizontal? Padding.End : Padding.Bottom);
346 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
347 else colView.ContentContainer.SizeHeight = ScrollContentSize;
349 base.Initialize(view);
350 //Console.WriteLine("[NUI] Init Done, StepCnadidate{0}, Scroll{1}", StepCandidate, ScrollContentSize);
354 /// This is called to find out where items are lain out according to current scroll position.
356 /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
357 /// <param name="force">boolean force flag to layouting forcely.</param>
358 /// <since_tizen> 9 </since_tizen>
359 public override void RequestLayout(float scrollPosition, bool force = false)
361 // Layouting is only possible after once it initialized.
362 if (!IsInitialized) return;
364 if (requestLayoutTimer != null)
366 requestLayoutTimer.Dispose();
367 requestLayoutTimer = null;
371 int LastIndex = colView.InternalItemSource.Count - 1;
373 if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
374 PrevScrollPosition = Math.Abs(scrollPosition);
376 if (ItemSizeChanged >= 0)
378 for (int i = ItemSizeChanged; i <= LastIndex; i++)
380 (float updateX, float updateY) = GetItemPosition(LastIndex);
381 ScrollContentSize = GetItemStepSize(LastIndex) + (IsHorizontal? updateX + Padding.End : updateY + Padding.Bottom);
384 int prevFirstVisible = FirstVisible;
385 int prevLastVisible = LastVisible;
387 (float X, float Y) visibleArea = (PrevScrollPosition,
388 PrevScrollPosition + (IsHorizontal? colView.Size.Width : colView.Size.Height)
391 // 1. Set First/Last Visible Item Index.
392 (int start, int end) = FindVisibleItems(visibleArea);
393 FirstVisible = start;
396 // 2. Unrealize invisible items.
397 List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
398 foreach (RecyclerViewItem item in VisibleItems)
400 if (item.Index < FirstVisible || item.Index > LastVisible)
402 //Console.WriteLine("[NUI] Unrealize{0}!", item.Index);
403 unrealizedItems.Add(item);
404 colView.UnrealizeItem(item);
407 VisibleItems.RemoveAll(unrealizedItems.Contains);
408 unrealizedItems.Clear();
410 // 3. Realize and placing visible items.
411 for (int i = FirstVisible; i <= LastVisible; i++)
413 RecyclerViewItem item = null;
414 // 4. Get item if visible or realize new.
415 if (i >= prevFirstVisible && i <= prevLastVisible)
417 item = GetVisibleItem(i);
418 if (item != null && !force) continue;
423 item = colView.RealizeItem(i);
424 if (item != null) VisibleItems.Add(item);
425 else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]");
428 (float posX, float posY) = GetItemPosition(i);
429 item.Position = new Position(posX, posY);
431 var size = (IsHorizontal? item.SizeWidth: item.SizeHeight);
433 if (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst)
435 if (item.IsHeader || item.IsFooter || item.isGroupHeader || item.isGroupFooter)
437 if (item.IsHeader) size = headerSize;
438 else if (item.IsFooter) size = footerSize;
439 else if (item.isGroupHeader) size = groupHeaderSize;
440 else if (item.isGroupFooter) size = groupFooterSize;
442 else size = StepCandidate;
444 if (IsHorizontal && item.HeightSpecification == LayoutParamPolicies.MatchParent)
446 item.Size = new Size(size, Container.Size.Height - Padding.Top - Padding.Bottom - item.Margin.Top - item.Margin.Bottom);
448 else if (!IsHorizontal && item.WidthSpecification == LayoutParamPolicies.MatchParent)
450 item.Size = new Size(Container.Size.Width - Padding.Start - Padding.End - item.Margin.Start - item.Margin.End, size);
457 /// Clear the current screen and all properties.
459 [EditorBrowsable(EditorBrowsableState.Never)]
460 public override void Clear()
463 if (requestLayoutTimer != null)
465 requestLayoutTimer.Dispose();
470 foreach (GroupInfo group in groups)
472 //group.ItemPosition?.Clear();
479 if (ItemPosition != null)
481 ItemPosition.Clear();
483 if (ItemSize != null)
487 if (headerMargin != null)
489 headerMargin.Dispose();
492 if (footerMargin != null)
494 footerMargin.Dispose();
497 if (groupHeaderMargin != null)
499 groupHeaderMargin.Dispose();
500 groupHeaderMargin = null;
502 if (groupFooterMargin != null)
504 groupFooterMargin.Dispose();
505 groupFooterMargin = null;
513 [EditorBrowsable(EditorBrowsableState.Never)]
514 public override void NotifyItemSizeChanged(RecyclerViewItem item)
517 throw new ArgumentNullException(nameof(item));
519 if (!IsInitialized ||
520 (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst &&
525 float PrevSize, CurrentSize;
526 if (item.Index == (colView.InternalItemSource.Count - 1))
528 PrevSize = ScrollContentSize - ItemPosition[item.Index];
532 PrevSize = ItemPosition[item.Index + 1] - ItemPosition[item.Index];
535 CurrentSize = (IsHorizontal? item.Size.Width : item.Size.Height);
537 if (CurrentSize != PrevSize)
539 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
540 ItemSize[item.Index] = CurrentSize;
542 StepCandidate = CurrentSize;
544 if (ItemSizeChanged == -1) ItemSizeChanged = item.Index;
545 else ItemSizeChanged = Math.Min(ItemSizeChanged, item.Index);
547 //ScrollContentSize += Diff; UpdateOnce?
551 [EditorBrowsable(EditorBrowsableState.Never)]
552 public override void NotifyItemInserted(IItemSource source, int startIndex)
554 // Insert Single item.
555 if (source == null) throw new ArgumentNullException(nameof(source));
562 // Will be null if not a group.
563 float currentSize = StepCandidate;
564 IGroupableItemSource gSource = source as IGroupableItemSource;
566 // Get the first Visible Position to adjust.
568 int topInScreenIndex = 0;
570 (topInScreenIndex, offset) = FindTopItemInScreen();
573 // 1. Handle MeasureAll
575 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
581 //2. Handle Group Case.
582 if (isGrouped && gSource != null)
584 GroupInfo groupInfo = null;
585 object groupParent = gSource.GetGroupParent(startIndex);
586 int parentIndex = gSource.GetPosition(groupParent);
587 if (gSource.HasHeader) parentIndex--;
589 // Check item is group parent or not
590 // if group parent, add new gorupinfo
591 if (gSource.IsHeader(startIndex))
593 // This is childless group.
594 // create new groupInfo!
595 groupInfo = new GroupInfo()
597 GroupParent = groupParent,
598 StartIndex = startIndex,
600 GroupSize = groupHeaderSize,
603 if (parentIndex >= groups.Count)
605 groupInfo.GroupPosition = ScrollContentSize;
606 groups.Add(groupInfo);
610 groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
611 groups.Insert(parentIndex, groupInfo);
614 currentSize = groupHeaderSize;
618 // If not group parent, add item into the groupinfo.
619 if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts.");
620 groupInfo = groups[parentIndex];//GetGroupInfo(groupParent);
621 if (groupInfo == null) throw new Exception("Cannot find group information!");
624 if (gSource.IsGroupFooter(startIndex))
626 // It doesn't make sence to adding footer by notify...
627 // if GroupFooterTemplate is added,
628 // need to implement on here.
632 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
634 float curPos = groupInfo.ItemPosition[startIndex - groupInfo.StartIndex];
635 groupInfo.ItemPosition.Insert(startIndex - groupInfo.StartIndex, curPos);
636 for (int i = startIndex - groupInfo.StartIndex; i < groupInfo.Count; i++)
638 groupInfo.ItemPosition[i] = curPos;
639 curPos += GetItemStepSize(parentIndex + i);
641 groupInfo.GroupSize = curPos;
645 groupInfo.GroupSize += currentSize;
650 if (parentIndex + 1 < groups.Count)
652 for(int i = parentIndex + 1; i < groups.Count; i++)
654 groups[i].GroupPosition += currentSize;
655 groups[i].StartIndex++;
661 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
663 // Need to Implements
668 // 3. Update Scroll Content Size
669 ScrollContentSize += currentSize;
671 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
672 else colView.ContentContainer.SizeHeight = ScrollContentSize;
674 // 4. Update Visible Items.
675 foreach (RecyclerViewItem item in VisibleItems)
677 if (item.Index >= startIndex)
684 float scrollPosition = PrevScrollPosition;
688 // Insertion above Top Visible!
689 if (startIndex <= topInScreenIndex)
691 scrollPosition = GetItemPosition(topInScreenIndex);
692 scrollPosition -= offset;
694 colView.ScrollTo(scrollPosition);
698 // Update Viewport in delay.
699 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
700 // but currently we do not have any accessor to pre-calculation so instead of this,
701 // using Timer temporarily.
702 DelayedRequestLayout(scrollPosition);
706 [EditorBrowsable(EditorBrowsableState.Never)]
707 public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
710 if (source == null) throw new ArgumentNullException(nameof(source));
717 float currentSize = StepCandidate;
718 // Will be null if not a group.
719 IGroupableItemSource gSource = source as IGroupableItemSource;
721 // Get the first Visible Position to adjust.
723 int topInScreenIndex = 0;
725 (topInScreenIndex, offset) = FindTopItemInScreen();
728 // 1. Handle MeasureAll
730 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
736 // 2. Handle Group Case
737 // Adding ranged items should all same new groups.
738 if (isGrouped && gSource != null)
740 GroupInfo groupInfo = null;
741 object groupParent = gSource.GetGroupParent(startIndex);
742 int parentIndex = gSource.GetPosition(groupParent);
743 if (gSource.HasHeader) parentIndex--;
745 // We guess here that range inserted from GroupStartIndex.
746 int groupStartIndex = startIndex;
748 for (int current = startIndex; current - startIndex < count; current++)
750 // Check item is group parent or not
751 // if group parent, add new gorupinfo
752 if (groupStartIndex == current)
754 currentSize = (gSource.IsGroupHeader(current)? groupHeaderSize :
755 (gSource.IsGroupFooter(current)? groupFooterSize: currentSize));
756 //create new groupInfo!
757 groupInfo = new GroupInfo()
759 GroupParent = groupParent,
760 StartIndex = current,
762 GroupSize = currentSize,
768 //if not group parent, add item into the groupinfo.
769 //groupInfo = GetGroupInfo(groupStartIndex);
770 if (groupInfo == null) throw new Exception("Cannot find group information!");
773 if (gSource.IsGroupFooter(current))
775 groupInfo.GroupSize += groupFooterSize;
779 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
783 float curPos = groupInfo.ItemPosition[current - groupStartIndex];
784 groupInfo.ItemPosition.Insert(current - groupStartIndex, curPos);
785 for (int i = current - groupStartIndex; i < groupInfo.Count; i++)
787 groupInfo.ItemPosition[i] = curPos;
788 curPos += GetItemSize(parentIndex + i);
790 groupInfo.GroupSize = curPos;
795 groupInfo.GroupSize += StepCandidate;
801 if (parentIndex >= groups.Count)
803 groupInfo.GroupPosition = ScrollContentSize;
804 groups.Add(groupInfo);
808 groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
809 groups.Insert(parentIndex, groupInfo);
812 // Update other below group's position
813 if (parentIndex + 1 < groups.Count)
815 for(int i = parentIndex + 1; i < groups.Count; i++)
817 groups[i].GroupPosition += groupInfo.GroupSize;
818 groups[i].StartIndex += count;
822 ScrollContentSize += groupInfo.GroupSize;
826 Tizen.Log.Error("NUI", "Not support insert ungrouped range items currently!");
829 // 3. Update Scroll Content Size
830 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
831 else colView.ContentContainer.SizeHeight = ScrollContentSize;
833 // 4. Update Visible Items.
834 foreach (RecyclerViewItem item in VisibleItems)
836 if (item.Index >= startIndex)
843 float scrollPosition = PrevScrollPosition;
845 // Insertion above Top Visible!
846 if (startIndex + count <= topInScreenIndex)
848 scrollPosition = GetItemPosition(topInScreenIndex);
849 scrollPosition -= offset;
851 colView.ScrollTo(scrollPosition);
855 // Update Viewport in delay.
856 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
857 // but currently we do not have any accessor to pre-calculation so instead of this,
858 // using Timer temporarily.
859 DelayedRequestLayout(scrollPosition);
863 [EditorBrowsable(EditorBrowsableState.Never)]
864 public override void NotifyItemRemoved(IItemSource source, int startIndex)
867 if (source == null) throw new ArgumentNullException(nameof(source));
869 // Will be null if not a group.
870 float currentSize = StepCandidate;
871 IGroupableItemSource gSource = source as IGroupableItemSource;
873 // Get the first Visible Position to adjust.
875 int topInScreenIndex = 0;
877 (topInScreenIndex, offset) = FindTopItemInScreen();
880 // 1. Handle MeasureAll
882 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
888 // 2. Handle Group Case
889 if (isGrouped && gSource != null)
892 GroupInfo groupInfo = null;
893 foreach(GroupInfo cur in groups)
895 if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex))
902 if (groupInfo == null) throw new Exception("Cannot find group information!");
903 // Check item is group parent or not
904 // if group parent, add new gorupinfo
905 if (groupInfo.StartIndex == startIndex)
907 // This is empty group!
908 // check group is empty.
909 if (groupInfo.Count != 1)
911 throw new Exception("Cannot remove group parent");
913 currentSize = groupInfo.GroupSize;
916 // groupInfo.Dispose();
917 groups.Remove(groupInfo);
923 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
925 //Need to Implement this.
929 groupInfo.GroupSize -= currentSize;
933 for (int i = parentIndex + 1; i < groups.Count; i++)
935 groups[i].GroupPosition -= currentSize;
936 groups[i].StartIndex--;
941 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
943 // Need to Implements
945 // else Nothing to Do
948 ScrollContentSize -= currentSize;
950 // 3. Update Scroll Content Size
951 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
952 else colView.ContentContainer.SizeHeight = ScrollContentSize;
954 // 4. Update Visible Items.
955 RecyclerViewItem targetItem = null;
956 foreach (RecyclerViewItem item in VisibleItems)
958 if (item.Index == startIndex)
961 colView.UnrealizeItem(item);
963 else if (item.Index > startIndex)
968 VisibleItems.Remove(targetItem);
971 float scrollPosition = PrevScrollPosition;
973 // Insertion above Top Visible!
974 if (startIndex <= topInScreenIndex)
976 scrollPosition = GetItemPosition(topInScreenIndex);
977 scrollPosition -= offset;
979 colView.ScrollTo(scrollPosition);
983 // Update Viewport in delay.
984 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
985 // but currently we do not have any accessor to pre-calculation so instead of this,
986 // using Timer temporarily.
987 DelayedRequestLayout(scrollPosition);
991 [EditorBrowsable(EditorBrowsableState.Never)]
992 public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
995 if (source == null) throw new ArgumentNullException(nameof(source));
997 // Will be null if not a group.
998 float currentSize = StepCandidate;
999 IGroupableItemSource gSource = source as IGroupableItemSource;
1001 // Get the first Visible Position to adjust.
1003 int topInScreenIndex = 0;
1005 (topInScreenIndex, offset) = FindTopItemInScreen();
1008 // 1. Handle MeasureAll
1010 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1016 // 2. Handle Group Case
1017 if (isGrouped && gSource != null)
1019 int parentIndex = 0;
1020 GroupInfo groupInfo = null;
1021 foreach(GroupInfo cur in groups)
1023 if ((cur.StartIndex == startIndex) && (cur.Count == count))
1030 if (groupInfo == null) throw new Exception("Cannot find group information!");
1031 // Check item is group parent or not
1032 // if group parent, add new gorupinfo
1033 currentSize = groupInfo.GroupSize;
1034 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1036 // Update ItemSize and ItemPosition
1039 // groupInfo.Dispose();
1040 groups.Remove(groupInfo);
1042 for (int i = parentIndex; i < groups.Count; i++)
1044 groups[i].GroupPosition -= currentSize;
1045 groups[i].StartIndex -= count;
1050 //It must group case! throw exception!
1051 Tizen.Log.Error("NUI", "Not support remove ungrouped range items currently!");
1054 ScrollContentSize -= currentSize;
1056 // 3. Update Scroll Content Size
1057 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
1058 else colView.ContentContainer.SizeHeight = ScrollContentSize;
1060 // 4. Update Visible Items.
1061 List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
1062 foreach (RecyclerViewItem item in VisibleItems)
1064 if ((item.Index >= startIndex)
1065 && (item.Index < startIndex + count))
1067 unrealizedItems.Add(item);
1068 colView.UnrealizeItem(item);
1070 else if (item.Index >= startIndex + count)
1072 item.Index -= count;
1075 VisibleItems.RemoveAll(unrealizedItems.Contains);
1076 unrealizedItems.Clear();
1079 float scrollPosition = PrevScrollPosition;
1081 // Insertion above Top Visible!
1082 if (startIndex <= topInScreenIndex)
1084 scrollPosition = GetItemPosition(topInScreenIndex);
1085 scrollPosition -= offset;
1087 colView.ScrollTo(scrollPosition);
1091 // Update Viewport in delay.
1092 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1093 // but currently we do not have any accessor to pre-calculation so instead of this,
1094 // using Timer temporarily.
1095 DelayedRequestLayout(scrollPosition);
1099 [EditorBrowsable(EditorBrowsableState.Never)]
1100 public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
1103 if (source == null) throw new ArgumentNullException(nameof(source));
1105 // Will be null if not a group.
1106 float currentSize = StepCandidate;
1107 int diff = toPosition - fromPosition;
1109 // Get the first Visible Position to adjust.
1111 int topInScreenIndex = 0;
1113 (topInScreenIndex, offset) = FindTopItemInScreen();
1116 // 1. Handle MeasureAll
1118 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1124 // Move can only happen in it's own groups.
1125 // so there will be no changes in position, startIndex in ohter groups.
1126 // check visible item and update indexs.
1127 int startIndex = ( diff > 0 ? fromPosition: toPosition);
1128 int endIndex = (diff > 0 ? toPosition: fromPosition);
1130 if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1132 foreach (RecyclerViewItem item in VisibleItems)
1134 if ((item.Index >= startIndex)
1135 && (item.Index <= endIndex))
1137 if (item.Index == fromPosition) item.Index = toPosition;
1140 if (diff > 0) item.Index--;
1147 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
1148 else colView.ContentContainer.SizeHeight = ScrollContentSize;
1151 float scrollPosition = PrevScrollPosition;
1153 // Insertion above Top Visible!
1154 if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1155 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1157 scrollPosition = GetItemPosition(topInScreenIndex);
1158 scrollPosition -= offset;
1160 colView.ScrollTo(scrollPosition);
1164 // Update Viewport in delay.
1165 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1166 // but currently we do not have any accessor to pre-calculation so instead of this,
1167 // using Timer temporarily.
1168 DelayedRequestLayout(scrollPosition);
1172 [EditorBrowsable(EditorBrowsableState.Never)]
1173 public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
1176 if (source == null) throw new ArgumentNullException(nameof(source));
1178 // Will be null if not a group.
1179 float currentSize = StepCandidate;
1180 int diff = toPosition - fromPosition;
1182 int startIndex = ( diff > 0 ? fromPosition: toPosition);
1183 int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1);
1185 // 2. Handle Group Case
1188 int fromParentIndex = 0;
1189 int toParentIndex = 0;
1190 bool findFrom = false;
1191 bool findTo = false;
1192 GroupInfo fromGroup = null;
1193 GroupInfo toGroup = null;
1195 foreach(GroupInfo cur in groups)
1197 if ((cur.StartIndex == fromPosition) && (cur.Count == count))
1201 if (findFrom && findTo) break;
1203 else if (cur.StartIndex == toPosition)
1207 if (findFrom && findTo) break;
1209 if (!findFrom) fromParentIndex++;
1210 if (!findTo) toParentIndex++;
1212 if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!");
1214 fromGroup.StartIndex = toGroup.StartIndex;
1215 fromGroup.GroupPosition = toGroup.GroupPosition;
1217 endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1);
1219 groups.Remove(fromGroup);
1220 groups.Insert(toParentIndex, fromGroup);
1222 int startGroup = (diff > 0? fromParentIndex: toParentIndex);
1223 int endGroup = (diff > 0? toParentIndex: fromParentIndex);
1225 for (int i = startGroup; i <= endGroup; i++)
1227 if (i == toParentIndex) continue;
1228 float prevPos = groups[i].GroupPosition;
1229 int prevIdx = groups[i].StartIndex;
1230 groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize;
1231 groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count;
1236 //It must group case! throw exception!
1237 Tizen.Log.Error("NUI", "Not support move ungrouped range items currently!");
1240 // Move can only happen in it's own groups.
1241 // so there will be no changes in position, startIndex in ohter groups.
1242 // check visible item and update indexs.
1243 if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1245 foreach (RecyclerViewItem item in VisibleItems)
1247 if ((item.Index >= startIndex)
1248 && (item.Index <= endIndex))
1250 if ((item.Index >= fromPosition) && (item.Index < fromPosition + count))
1252 item.Index = fromPosition - item.Index + toPosition;
1256 if (diff > 0) item.Index -= count;
1257 else item.Index += count;
1264 float scrollPosition = PrevScrollPosition;
1266 // Insertion above Top Visible!
1267 if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1268 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1270 scrollPosition = GetItemPosition(topInScreenIndex);
1271 scrollPosition -= offset;
1273 colView.ScrollTo(scrollPosition);
1277 // Update Viewport in delay.
1278 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1279 // but currently we do not have any accessor to pre-calculation so instead of this,
1280 // using Timer temporarily.
1281 DelayedRequestLayout(scrollPosition);
1285 [EditorBrowsable(EditorBrowsableState.Never)]
1286 public override void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
1289 if (source == null) throw new ArgumentNullException(nameof(source));
1290 IGroupableItemSource gSource = source as IGroupableItemSource;
1291 if (gSource == null)throw new Exception("Source is not group!");
1293 // Get the first Visible Position to adjust.
1295 int topInScreenIndex = 0;
1297 (topInScreenIndex, offset) = FindTopItemInScreen();
1301 // Unrealize, initialized all items in the Range
1304 // Update Viewport in delay.
1305 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1306 // but currently we do not have any accessor to pre-calculation so instead of this,
1307 // using Timer temporarily.
1308 DelayedRequestLayout(PrevScrollPosition);
1312 [EditorBrowsable(EditorBrowsableState.Never)]
1313 public override float CalculateLayoutOrientationSize()
1315 //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
1316 return ScrollContentSize;
1320 [EditorBrowsable(EditorBrowsableState.Never)]
1321 public override float CalculateCandidateScrollPosition(float scrollPosition)
1323 //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
1324 return scrollPosition;
1328 [EditorBrowsable(EditorBrowsableState.Never)]
1329 public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
1331 if (currentFocusedView == null)
1332 throw new ArgumentNullException(nameof(currentFocusedView));
1334 View nextFocusedView = null;
1335 int targetSibling = -1;
1339 case View.FocusDirection.Left:
1341 targetSibling = IsHorizontal? currentFocusedView.SiblingOrder - 1 : targetSibling;
1344 case View.FocusDirection.Right:
1346 targetSibling = IsHorizontal? currentFocusedView.SiblingOrder + 1 : targetSibling;
1349 case View.FocusDirection.Up:
1351 targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder - 1;
1354 case View.FocusDirection.Down:
1356 targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder + 1;
1361 if (targetSibling > -1 && targetSibling < Container.Children.Count)
1363 RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
1364 if (candidate != null && candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
1366 nextFocusedView = candidate;
1370 return nextFocusedView;
1374 [EditorBrowsable(EditorBrowsableState.Never)]
1375 protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
1377 int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter? 1 : 0);
1380 (int start, int end) found = (0, 0);
1382 // 1. Find the start index.
1383 // Header is Showing
1384 if (hasHeader && visibleArea.X <= headerSize + (IsHorizontal? Padding.Start: Padding.Top))
1393 foreach (GroupInfo gInfo in groups)
1397 if (gInfo.GroupPosition <= visibleArea.X &&
1398 gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
1400 for (int i = 0; i < gInfo.Count; i++)
1402 // Reach last index of group.
1403 if (i == (gInfo.Count - 1))
1405 found.start = gInfo.StartIndex + i - adds;
1410 else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.X &&
1411 GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.X)
1413 found.start = gInfo.StartIndex + i - adds;
1420 //footer only shows?
1423 found.start = MaxIndex;
1428 float visibleAreaX = visibleArea.X - (hasHeader? headerSize : 0);
1429 // Prevent zero division.
1430 var itemSize = (StepCandidate != 0)? StepCandidate : 1f;
1431 found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / itemSize)) - adds);
1434 if (found.start < 0) found.start = 0;
1437 if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
1439 found.end = MaxIndex + 1;
1446 // can it be start from founded group...?
1447 //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
1448 foreach (GroupInfo gInfo in groups)
1451 if (gInfo.GroupPosition <= visibleArea.Y &&
1452 gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
1454 for (int i = 0; i < gInfo.Count; i++)
1456 if (i == (gInfo.Count - 1))
1458 //Should be groupFooter!
1459 found.end = gInfo.StartIndex + i + adds;
1464 else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.Y &&
1465 GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.Y)
1467 found.end = gInfo.StartIndex + i + adds;
1474 if (failed) found.end = MaxIndex;
1478 float visibleAreaY = visibleArea.Y - (hasHeader? headerSize : 0);
1479 // Prevent zero division.
1480 var itemSize = (StepCandidate != 0)? StepCandidate : 1f;
1481 found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / itemSize)) + adds);
1482 if (hasHeader) found.end += 1;
1484 if (found.end > (MaxIndex)) found.end = MaxIndex;
1489 // Item position excluding margins.
1490 internal override (float X, float Y) GetItemPosition(int index)
1492 int spaceStartX = Padding.Start;
1493 int spaceStartY = Padding.Top;
1494 if (colView.InternalItemSource.IsHeader(index))
1496 return (spaceStartX + headerMargin.Start, spaceStartY + headerMargin.Top);
1498 else if (colView.InternalItemSource.IsFooter(index))
1500 return ((IsHorizontal? ScrollContentSize - footerSize - Padding.End + footerMargin.Start : spaceStartX + footerMargin.Start),
1501 (IsHorizontal? spaceStartY + footerMargin.Top : ScrollContentSize - footerSize - Padding.Bottom + footerMargin.Top));
1505 GroupInfo gInfo = GetGroupInfo(index);
1508 Tizen.Log.Error("NUI", "GroupInfo failed to get in GetItemPosition()!");
1511 float current = GetGroupPosition(gInfo, index);
1512 Extents itemMargin = CandidateMargin;
1514 if (colView.InternalItemSource.IsGroupHeader(index))
1516 itemMargin = groupHeaderMargin;
1518 else if (colView.InternalItemSource.IsGroupFooter(index))
1520 itemMargin = groupFooterMargin;
1522 return ((IsHorizontal?
1523 itemMargin.Start + GetGroupPosition(gInfo, index):
1524 spaceStartX + itemMargin.Start),
1526 spaceStartY + itemMargin.Top:
1527 itemMargin.Top + GetGroupPosition(gInfo, index)));
1529 else if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1531 //FIXME : CandidateMargin need to be actual itemMargin
1532 return ((IsHorizontal? ItemPosition[index] + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
1533 (IsHorizontal? spaceStartY + CandidateMargin.Top : ItemPosition[index] + CandidateMargin.Top));
1537 int adjustIndex = index - (hasHeader ? 1 : 0);
1538 float current = (IsHorizontal ? spaceStartX : spaceStartY) + (hasHeader? headerSize : 0) + adjustIndex * StepCandidate;
1539 //FIXME : CandidateMargin need to be actual itemMargin
1540 return ((IsHorizontal? current + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
1541 (IsHorizontal? spaceStartY + CandidateMargin.Top : current + CandidateMargin.Top));
1545 // Item size excluding margins. this size is approximated size.
1546 internal override (float Width, float Height) GetItemSize(int index)
1548 if (colView.InternalItemSource.IsHeader(index))
1550 return ((IsHorizontal? (int)headerSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1551 - headerMargin.Start - headerMargin.End,
1552 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)headerSize)
1553 - headerMargin.Top - headerMargin.Bottom);
1555 else if (colView.InternalItemSource.IsFooter(index))
1557 return ((IsHorizontal? (int)footerSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1558 - footerMargin.Start - footerMargin.End,
1559 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)footerSize)
1560 - footerMargin.Top - footerMargin.Bottom);
1562 else if (colView.InternalItemSource.IsGroupHeader(index))
1564 return ((IsHorizontal? (int)groupHeaderSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1565 - groupHeaderMargin.Start - groupHeaderMargin.End,
1566 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)groupHeaderSize)
1567 - groupHeaderMargin.Top - groupHeaderMargin.Bottom);
1569 else if (colView.InternalItemSource.IsGroupFooter(index))
1571 return ((IsHorizontal? (int)groupFooterSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1572 - groupFooterMargin.Start - groupFooterMargin.End,
1573 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)groupFooterSize)
1574 - groupFooterMargin.Top - groupFooterMargin.Bottom);
1578 return ((IsHorizontal? (int)StepCandidate : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1579 - CandidateMargin.Start - CandidateMargin.End,
1580 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)StepCandidate)
1581 - CandidateMargin.Top - CandidateMargin.Bottom);
1585 private void DelayedRequestLayout(float scrollPosition , bool force = true)
1587 if (requestLayoutTimer != null)
1589 requestLayoutTimer.Dispose();
1592 requestLayoutTimer = new Timer(1);
1593 requestLayoutTimer.Interval = 1;
1594 requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) =>
1596 RequestLayout(scrollPosition, force);
1602 private (int, float) FindTopItemInScreen()
1605 float offset = 0.0F, Pos, Size;
1607 foreach(RecyclerViewItem item in VisibleItems)
1609 Pos = IsHorizontal ? item.PositionX : item.PositionY;
1610 Size = IsHorizontal ? item.SizeWidth : item.SizeHeight;
1611 if (PrevScrollPosition >= Pos && PrevScrollPosition < Pos + Size)
1614 offset = Pos - PrevScrollPosition;
1619 return (index, offset);
1623 private float GetItemStepSize(int index)
1625 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1627 return ItemSize[index];
1631 if (colView.InternalItemSource.IsHeader(index))
1633 else if (colView.InternalItemSource.IsFooter(index))
1635 else if (colView.InternalItemSource.IsGroupHeader(index))
1636 return groupHeaderSize;
1637 else if (colView.InternalItemSource.IsGroupFooter(index))
1638 return groupFooterSize;
1640 return StepCandidate;
1644 private void UpdatePosition(int index)
1646 bool IsGroup = (colView.InternalItemSource is IGroupableItemSource);
1648 if (index <= 0) return;
1649 if (index >= colView.InternalItemSource.Count)
1653 //IsGroupHeader = (colView.InternalItemSource as IGroupableItemSource).IsGroupHeader(index);
1654 //IsGroupFooter = (colView.InternalItemSource as IGroupableItemSource).IsGroupFooter(index);
1657 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1658 ItemPosition[index] = ItemPosition[index - 1] + GetItemStepSize(index - 1);
1661 private RecyclerViewItem GetVisibleItem(int index)
1663 foreach (RecyclerViewItem item in VisibleItems)
1665 if (item.Index == index) return item;
1670 private GroupInfo GetGroupInfo(int index)
1672 if (Visited != null)
1674 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1677 if (hasHeader && index == 0) return null;
1678 foreach (GroupInfo group in groups)
1680 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1690 private float GetGroupPosition(GroupInfo groupInfo, int index)
1692 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1693 return groupInfo.GroupPosition + groupInfo.ItemPosition[index - groupInfo.StartIndex];
1696 float pos = groupInfo.GroupPosition;
1697 if (groupInfo.StartIndex == index) return pos;
1699 pos = pos + groupHeaderSize + StepCandidate * (index - groupInfo.StartIndex - 1);
1705 private object GetGroupParent(int index)
1707 if (Visited != null)
1709 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1710 return Visited.GroupParent;
1712 if (hasHeader && index == 0) return null;
1713 foreach (GroupInfo group in groups)
1715 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1718 return group.GroupParent;
1727 public object GroupParent;
1728 public int StartIndex;
1730 public float GroupSize;
1731 public float GroupPosition;
1732 //Items relative position from the GroupPosition. Only use for MeasureAll.
1733 public List<float> ItemPosition = new List<float>();