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 collectionView;
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 [EditorBrowsable(EditorBrowsableState.Never)]
51 protected new IGroupableItemSource Source => collectionView?.InternalSource;
56 [EditorBrowsable(EditorBrowsableState.Never)]
57 protected override List<GroupInfo> GroupItems => groups;
60 /// Clean up ItemsLayouter.
62 /// <param name="view"> CollectionView of layouter.</param>
63 /// <remarks>please note that, view must be type of CollectionView</remarks>
64 /// <since_tizen> 9 </since_tizen>
65 public override void Initialize(RecyclerView view)
67 collectionView = view as CollectionView;
68 if (collectionView == null)
70 throw new ArgumentException("LinearLayouter only can be applied CollectionView.", nameof(view));
74 foreach (RecyclerViewItem item in VisibleItems)
76 collectionView.UnrealizeItem(item, false);
83 IsHorizontal = (collectionView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
85 RecyclerViewItem header = collectionView?.Header;
86 RecyclerViewItem footer = collectionView?.Footer;
88 int count = Source.Count;
92 MeasureChild(collectionView, header);
94 width = header.Layout != null? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
95 height = header.Layout != null? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
97 Extents itemMargin = header.Margin;
98 headerSize = IsHorizontal?
99 width + itemMargin.Start + itemMargin.End:
100 height + itemMargin.Top + itemMargin.Bottom;
101 headerMargin = new Extents(itemMargin);
104 collectionView.UnrealizeItem(header);
106 else hasHeader = false;
110 MeasureChild(collectionView, footer);
112 width = footer.Layout != null? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
113 height = footer.Layout != null? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
115 Extents itemMargin = footer.Margin;
116 footerSize = IsHorizontal?
117 width + itemMargin.Start + itemMargin.End:
118 height + itemMargin.Top + itemMargin.Bottom;
119 footerMargin = new Extents(itemMargin);
120 footer.Index = count - 1;
123 collectionView.UnrealizeItem(footer);
125 else hasFooter = false;
127 //No Internal Source exist.
128 if (count == (hasHeader? (hasFooter? 2 : 1) : 0))
130 isSourceEmpty = true;
131 base.Initialize(view);
134 isSourceEmpty = false;
136 int firstIndex = hasHeader? 1 : 0;
138 if (collectionView.IsGrouped)
142 if (collectionView.GroupHeaderTemplate != null)
144 while (!Source.IsGroupHeader(firstIndex)) firstIndex++;
145 //must be always true
146 if (Source.IsGroupHeader(firstIndex))
148 RecyclerViewItem groupHeader = collectionView.RealizeItem(firstIndex);
151 if (groupHeader == null) throw new Exception("[" + firstIndex + "] Group Header failed to realize!");
153 // Need to Set proper height or width on scroll direction.
154 if (groupHeader.Layout == null)
156 width = groupHeader.WidthSpecification;
157 height = groupHeader.HeightSpecification;
161 MeasureChild(collectionView, groupHeader);
163 width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
164 height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
166 // pick the StepCandidate.
167 Extents itemMargin = groupHeader.Margin;
168 groupHeaderSize = IsHorizontal?
169 width + itemMargin.Start + itemMargin.End:
170 height + itemMargin.Top + itemMargin.Bottom;
171 groupHeaderMargin = new Extents(itemMargin);
172 collectionView.UnrealizeItem(groupHeader);
177 groupHeaderSize = 0F;
180 if (collectionView.GroupFooterTemplate != null)
182 int firstFooter = firstIndex;
183 while (!Source.IsGroupFooter(firstFooter)) firstFooter++;
184 //must be always true
185 if (Source.IsGroupFooter(firstFooter))
187 RecyclerViewItem groupFooter = collectionView.RealizeItem(firstFooter);
189 if (groupFooter == null) throw new Exception("[" + firstFooter + "] Group Footer failed to realize!");
190 // Need to Set proper height or width on scroll direction.
191 if (groupFooter.Layout == null)
193 width = groupFooter.WidthSpecification;
194 height = groupFooter.HeightSpecification;
198 MeasureChild(collectionView, groupFooter);
200 width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
201 height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
203 // pick the StepCandidate.
204 Extents itemMargin = groupFooter.Margin;
205 groupFooterSize = IsHorizontal?
206 width + itemMargin.Start + itemMargin.End:
207 height + itemMargin.Top + itemMargin.Bottom;
208 groupFooterMargin = new Extents(itemMargin);
209 collectionView.UnrealizeItem(groupFooter);
214 groupFooterSize = 0F;
217 else isGrouped = false;
221 //Final Check of FirstIndex
222 if ((Source.Count - 1 < firstIndex) ||
223 (Source.IsFooter(firstIndex) && (Source.Count - 1) == firstIndex))
230 Source.IsHeader(firstIndex) ||
231 Source.IsGroupHeader(firstIndex) ||
232 Source.IsGroupFooter(firstIndex))
234 if (Source.IsFooter(firstIndex)
235 || ((Source.Count - 1) <= firstIndex))
246 RecyclerViewItem sizeDeligate = collectionView.RealizeItem(firstIndex);
247 if (sizeDeligate == null)
250 throw new Exception("Cannot create content from DatTemplate.");
253 sizeDeligate.BindingContext = Source.GetItem(firstIndex);
255 // Need to Set proper height or width on scroll direction.
256 if (sizeDeligate.Layout == null)
258 width = sizeDeligate.WidthSpecification;
259 height = sizeDeligate.HeightSpecification;
263 MeasureChild(collectionView, sizeDeligate);
265 width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
266 height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
268 // pick the StepCandidate.
269 Extents itemMargin = sizeDeligate.Margin;
270 StepCandidate = IsHorizontal?
271 width + itemMargin.Start + itemMargin.End:
272 height + itemMargin.Top + itemMargin.Bottom;
273 CandidateMargin = new Extents(itemMargin);
274 if (StepCandidate == 0) StepCandidate = 1; //????
276 collectionView.UnrealizeItem(sizeDeligate, false);
279 float Current = IsHorizontal? Padding.Start : Padding.Top;
280 IGroupableItemSource source = Source;
281 GroupInfo currentGroup = null;
282 object currentParent = null;
283 for (int i = 0; i < count; i++)
285 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
287 if (i == 0 && hasHeader)
288 ItemSize.Add(headerSize);
289 else if (i == count - 1 && hasFooter)
290 ItemSize.Add(footerSize);
291 else if (source.IsGroupHeader(i))
292 ItemSize.Add(groupHeaderSize);
293 else if (source.IsGroupFooter(i))
294 ItemSize.Add(groupFooterSize);
295 else ItemSize.Add(StepCandidate);
299 if (source.IsHeader(i))
301 //ItemPosition.Add(Current);
302 Current += headerSize;
304 else if (source.IsFooter(i))
306 //ItemPosition.Add(Current);
307 Current += footerSize;
311 //GroupHeader must always exist in group usage.
312 //if (source.IsGroupHeader(i))
313 if (source.GetGroupParent(i) != currentParent)
315 currentParent = source.GetGroupParent(i);
316 float currentSize = (source.IsGroupHeader(i)? groupHeaderSize :
317 (source.IsGroupFooter(i)? groupFooterSize: StepCandidate));
318 currentGroup = new GroupInfo()
320 GroupParent = currentParent,
325 GroupSize = currentSize,
326 GroupPosition = Current
328 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
329 currentGroup.ItemPosition.Add(0);
330 groups.Add(currentGroup);
331 if (source.IsGroupHeader(i)) Current += currentSize;
334 else if (source.IsGroupFooter(i))
336 //currentGroup.hasFooter = true;
337 if (currentGroup != null)
339 currentGroup.Count++;
340 currentGroup.GroupSize += groupFooterSize;
341 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
342 currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
343 Current += groupFooterSize;
348 if (currentGroup != null)
350 currentGroup.Count++;
351 currentGroup.GroupSize += StepCandidate;
352 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
353 currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
354 Current += StepCandidate;
361 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
362 ItemPosition.Add(Current);
364 if (i == 0 && hasHeader) Current += headerSize;
365 else if (i == count - 1 && hasFooter) Current += footerSize;
366 else Current += StepCandidate;
370 ScrollContentSize = Current + (IsHorizontal? Padding.End : Padding.Bottom);
371 if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
372 else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
374 base.Initialize(view);
375 //Console.WriteLine("[NUI] Init Done, StepCnadidate{0}, Scroll{1}", StepCandidate, ScrollContentSize);
379 /// This is called to find out where items are lain out according to current scroll position.
381 /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
382 /// <param name="force">boolean force flag to layouting forcely.</param>
383 /// <since_tizen> 9 </since_tizen>
384 public override void RequestLayout(float scrollPosition, bool force = false)
386 // Layouting is only possible after once it initialized.
387 if (!IsInitialized) return;
389 if (requestLayoutTimer != null)
391 requestLayoutTimer.Dispose();
392 requestLayoutTimer = null;
396 int LastIndex = Source.Count - 1;
398 if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
399 PrevScrollPosition = Math.Abs(scrollPosition);
401 if (ItemSizeChanged >= 0)
403 for (int i = ItemSizeChanged; i <= LastIndex; i++)
405 (float updateX, float updateY) = GetItemPosition(LastIndex);
406 ScrollContentSize = GetItemStepSize(LastIndex) + (IsHorizontal? updateX + Padding.End : updateY + Padding.Bottom);
409 int prevFirstVisible = FirstVisible;
410 int prevLastVisible = LastVisible;
412 (float X, float Y) visibleArea = (PrevScrollPosition,
413 PrevScrollPosition + (IsHorizontal? collectionView.Size.Width : collectionView.Size.Height)
416 // 1. Set First/Last Visible Item Index.
417 (int start, int end) = FindVisibleItems(visibleArea);
418 FirstVisible = start;
421 // 2. Unrealize invisible items.
422 List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
423 foreach (RecyclerViewItem item in VisibleItems)
425 if (item.Index < FirstVisible || item.Index > LastVisible)
427 unrealizedItems.Add(item);
428 collectionView.UnrealizeItem(item);
431 VisibleItems.RemoveAll(unrealizedItems.Contains);
432 unrealizedItems.Clear();
434 // 3. Realize and placing visible items.
435 for (int i = FirstVisible; i <= LastVisible; i++)
437 RecyclerViewItem item = null;
438 // 4. Get item if visible or realize new.
439 if (i >= prevFirstVisible && i <= prevLastVisible)
441 item = GetVisibleItem(i);
442 if (item != null && !force) continue;
447 item = collectionView.RealizeItem(i);
448 if (item != null) VisibleItems.Add(item);
449 else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]");
452 (float posX, float posY) = GetItemPosition(i);
453 item.Position = new Position(posX, posY);
455 var size = (IsHorizontal? item.SizeWidth: item.SizeHeight);
457 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureFirst)
459 if (item.IsHeader || item.IsFooter || item.IsGroupHeader || item.IsGroupFooter)
461 if (item.IsHeader) size = headerSize;
462 else if (item.IsFooter) size = footerSize;
463 else if (item.IsGroupHeader) size = groupHeaderSize;
464 else if (item.IsGroupFooter) size = groupFooterSize;
466 else size = StepCandidate;
468 if (IsHorizontal && item.HeightSpecification == LayoutParamPolicies.MatchParent)
470 item.Size = new Size(size, Container.Size.Height - Padding.Top - Padding.Bottom - item.Margin.Top - item.Margin.Bottom);
472 else if (!IsHorizontal && item.WidthSpecification == LayoutParamPolicies.MatchParent)
474 item.Size = new Size(Container.Size.Width - Padding.Start - Padding.End - item.Margin.Start - item.Margin.End, size);
481 /// Clear the current screen and all properties.
483 [EditorBrowsable(EditorBrowsableState.Never)]
484 public override void Clear()
487 if (requestLayoutTimer != null)
489 requestLayoutTimer.Dispose();
494 foreach (GroupInfo group in groups)
496 //group.ItemPosition?.Clear();
503 if (ItemPosition != null)
505 ItemPosition.Clear();
507 if (ItemSize != null)
511 if (headerMargin != null)
513 headerMargin.Dispose();
516 if (footerMargin != null)
518 footerMargin.Dispose();
521 if (groupHeaderMargin != null)
523 groupHeaderMargin.Dispose();
524 groupHeaderMargin = null;
526 if (groupFooterMargin != null)
528 groupFooterMargin.Dispose();
529 groupFooterMargin = null;
537 [EditorBrowsable(EditorBrowsableState.Never)]
538 public override void NotifyItemSizeChanged(RecyclerViewItem item)
541 throw new ArgumentNullException(nameof(item));
542 if (collectionView == null) return;
544 if (!IsInitialized ||
545 (collectionView.SizingStrategy == ItemSizingStrategy.MeasureFirst &&
550 float PrevSize, CurrentSize;
551 if (item.Index == (Source.Count - 1))
553 PrevSize = ScrollContentSize - ItemPosition[item.Index];
557 PrevSize = ItemPosition[item.Index + 1] - ItemPosition[item.Index];
560 CurrentSize = (IsHorizontal? item.Size.Width : item.Size.Height);
562 if (CurrentSize != PrevSize)
564 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
565 ItemSize[item.Index] = CurrentSize;
567 StepCandidate = CurrentSize;
569 if (ItemSizeChanged == -1) ItemSizeChanged = item.Index;
570 else ItemSizeChanged = Math.Min(ItemSizeChanged, item.Index);
572 //ScrollContentSize += Diff; UpdateOnce?
576 [EditorBrowsable(EditorBrowsableState.Never)]
577 public override void NotifyItemInserted(IItemSource source, int startIndex)
579 // Insert Single item.
580 if (source == null) throw new ArgumentNullException(nameof(source));
581 if (collectionView == null) return;
583 if (isSourceEmpty || StepCandidate == 0)
585 Initialize(collectionView);
588 // Will be null if not a group.
589 float currentSize = StepCandidate;
590 IGroupableItemSource gSource = source as IGroupableItemSource;
592 // Get the first Visible Position to adjust.
594 int topInScreenIndex = 0;
596 (topInScreenIndex, offset) = FindTopItemInScreen();
599 // 1. Handle MeasureAll
601 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
607 //2. Handle Group Case.
608 if (isGrouped && gSource != null)
610 GroupInfo groupInfo = null;
611 object groupParent = gSource.GetGroupParent(startIndex);
612 int parentIndex = gSource.GetPosition(groupParent);
613 if (gSource.HasHeader) parentIndex--;
615 // Check item is group parent or not
616 // if group parent, add new gorupinfo
617 if (gSource.IsHeader(startIndex))
619 // This is childless group.
620 // create new groupInfo!
621 groupInfo = new GroupInfo()
623 GroupParent = groupParent,
624 StartIndex = startIndex,
626 GroupSize = groupHeaderSize,
629 if (parentIndex >= groups.Count)
631 groupInfo.GroupPosition = ScrollContentSize;
632 groups.Add(groupInfo);
636 groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
637 groups.Insert(parentIndex, groupInfo);
640 currentSize = groupHeaderSize;
644 // If not group parent, add item into the groupinfo.
645 if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts.");
646 groupInfo = groups[parentIndex];//GetGroupInfo(groupParent);
647 if (groupInfo == null) throw new Exception("Cannot find group information!");
650 if (gSource.IsGroupFooter(startIndex))
652 // It doesn't make sence to adding footer by notify...
653 // if GroupFooterTemplate is added,
654 // need to implement on here.
658 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
660 float curPos = groupInfo.ItemPosition[startIndex - groupInfo.StartIndex];
661 groupInfo.ItemPosition.Insert(startIndex - groupInfo.StartIndex, curPos);
662 for (int i = startIndex - groupInfo.StartIndex; i < groupInfo.Count; i++)
664 groupInfo.ItemPosition[i] = curPos;
665 curPos += GetItemStepSize(parentIndex + i);
667 groupInfo.GroupSize = curPos;
671 groupInfo.GroupSize += currentSize;
676 if (parentIndex + 1 < groups.Count)
678 for(int i = parentIndex + 1; i < groups.Count; i++)
680 groups[i].GroupPosition += currentSize;
681 groups[i].StartIndex++;
687 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
689 // Need to Implements
694 // 3. Update Scroll Content Size
695 ScrollContentSize += currentSize;
697 if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
698 else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
700 // 4. Update Visible Items.
701 foreach (RecyclerViewItem item in VisibleItems)
703 if (item.Index >= startIndex)
709 if (startIndex <= FirstVisible)
714 else if (startIndex > FirstVisible && startIndex <= LastVisible)
719 if (FirstVisible > Source.Count - 1) FirstVisible = Source.Count -1;
720 if (LastVisible > Source.Count - 1) LastVisible = Source.Count -1;
722 float scrollPosition = PrevScrollPosition;
726 // Insertion above Top Visible!
727 if (startIndex <= topInScreenIndex)
729 scrollPosition = GetItemPosition(topInScreenIndex);
730 scrollPosition -= offset;
732 collectionView.ScrollTo(scrollPosition);
736 // Update Viewport in delay.
737 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
738 // but currently we do not have any accessor to pre-calculation so instead of this,
739 // using Timer temporarily.
740 DelayedRequestLayout(scrollPosition);
744 [EditorBrowsable(EditorBrowsableState.Never)]
745 public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
748 if (source == null) throw new ArgumentNullException(nameof(source));
749 if (collectionView == null) return;
751 if (isSourceEmpty || StepCandidate == 0)
753 Initialize(collectionView);
756 float currentSize = StepCandidate;
757 // Will be null if not a group.
758 IGroupableItemSource gSource = source as IGroupableItemSource;
760 // Get the first Visible Position to adjust.
762 int topInScreenIndex = 0;
764 (topInScreenIndex, offset) = FindTopItemInScreen();
767 // 1. Handle MeasureAll
769 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
775 // 2. Handle Group Case
776 // Adding ranged items should all same new groups.
777 if (isGrouped && gSource != null)
779 GroupInfo groupInfo = null;
780 object groupParent = gSource.GetGroupParent(startIndex);
781 int parentIndex = gSource.GetPosition(groupParent);
782 if (gSource.HasHeader) parentIndex--;
784 // We guess here that range inserted from GroupStartIndex.
785 int groupStartIndex = startIndex;
787 for (int current = startIndex; current - startIndex < count; current++)
789 // Check item is group parent or not
790 // if group parent, add new gorupinfo
791 if (groupStartIndex == current)
793 currentSize = (gSource.IsGroupHeader(current)? groupHeaderSize :
794 (gSource.IsGroupFooter(current)? groupFooterSize: currentSize));
795 //create new groupInfo!
796 groupInfo = new GroupInfo()
798 GroupParent = groupParent,
799 StartIndex = current,
801 GroupSize = currentSize,
807 //if not group parent, add item into the groupinfo.
808 //groupInfo = GetGroupInfo(groupStartIndex);
809 if (groupInfo == null) throw new Exception("Cannot find group information!");
812 if (gSource.IsGroupFooter(current))
814 groupInfo.GroupSize += groupFooterSize;
818 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
822 float curPos = groupInfo.ItemPosition[current - groupStartIndex];
823 groupInfo.ItemPosition.Insert(current - groupStartIndex, curPos);
824 for (int i = current - groupStartIndex; i < groupInfo.Count; i++)
826 groupInfo.ItemPosition[i] = curPos;
827 curPos += GetItemSize(parentIndex + i);
829 groupInfo.GroupSize = curPos;
834 groupInfo.GroupSize += StepCandidate;
840 if (groupInfo != null)
842 if (parentIndex >= groups.Count)
844 groupInfo.GroupPosition = ScrollContentSize;
845 groups.Add(groupInfo);
849 groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
850 groups.Insert(parentIndex, groupInfo);
853 // Update other below group's position
854 if (parentIndex + 1 < groups.Count)
856 for(int i = parentIndex + 1; i < groups.Count; i++)
858 groups[i].GroupPosition += groupInfo.GroupSize;
859 groups[i].StartIndex += count;
863 ScrollContentSize += groupInfo.GroupSize;
867 Tizen.Log.Error("NUI", "groupInfo is null! Check count = 0");
872 Tizen.Log.Error("NUI", "Not support insert ungrouped range items currently!");
875 // 3. Update Scroll Content Size
876 if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
877 else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
879 // 4. Update Visible Items.
880 foreach (RecyclerViewItem item in VisibleItems)
882 if (item.Index >= startIndex)
888 if (startIndex <= FirstVisible)
890 FirstVisible = FirstVisible + count;
891 LastVisible = LastVisible + count;
893 else if (startIndex > FirstVisible && startIndex <= LastVisible)
895 LastVisible = LastVisible + count;
898 if (FirstVisible > Source.Count - 1) FirstVisible = Source.Count -1;
899 if (LastVisible > Source.Count - 1) LastVisible = Source.Count -1;
902 float scrollPosition = PrevScrollPosition;
904 // Insertion above Top Visible!
905 if (startIndex + count <= topInScreenIndex)
907 scrollPosition = GetItemPosition(topInScreenIndex);
908 scrollPosition -= offset;
910 collectionView.ScrollTo(scrollPosition);
914 // Update Viewport in delay.
915 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
916 // but currently we do not have any accessor to pre-calculation so instead of this,
917 // using Timer temporarily.
918 DelayedRequestLayout(scrollPosition);
922 [EditorBrowsable(EditorBrowsableState.Never)]
923 public override void NotifyItemRemoved(IItemSource source, int startIndex)
926 if (source == null) throw new ArgumentNullException(nameof(source));
927 if (collectionView == null) return;
929 // Will be null if not a group.
930 float currentSize = StepCandidate;
931 IGroupableItemSource gSource = source as IGroupableItemSource;
933 // Get the first Visible Position to adjust.
935 int topInScreenIndex = 0;
937 (topInScreenIndex, offset) = FindTopItemInScreen();
940 // 1. Handle MeasureAll
942 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
948 // 2. Handle Group Case
949 if (isGrouped && gSource != null)
952 GroupInfo groupInfo = null;
953 foreach(GroupInfo cur in groups)
955 if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex))
962 if (groupInfo == null) throw new Exception("Cannot find group information!");
963 // Check item is group parent or not
964 // if group parent, add new gorupinfo
965 if (groupInfo.StartIndex == startIndex)
967 // This is empty group!
968 // check group is empty.
969 if (groupInfo.Count != 1)
971 throw new Exception("Cannot remove group parent");
973 currentSize = groupInfo.GroupSize;
976 // groupInfo.Dispose();
977 groups.Remove(groupInfo);
984 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
986 //Need to Implement this.
990 groupInfo.GroupSize -= currentSize;
994 for (int i = parentIndex + 1; i < groups.Count; i++)
996 groups[i].GroupPosition -= currentSize;
997 groups[i].StartIndex--;
1002 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1004 // Need to Implements
1006 // else Nothing to Do
1009 ScrollContentSize -= currentSize;
1011 // 3. Update Scroll Content Size
1012 if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
1013 else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
1015 // 4. Update Visible Items.
1016 RecyclerViewItem targetItem = null;
1017 foreach (RecyclerViewItem item in VisibleItems)
1019 if (item.Index == startIndex)
1022 collectionView.UnrealizeItem(item);
1024 else if (item.Index > startIndex)
1029 VisibleItems.Remove(targetItem);
1032 if (startIndex <= FirstVisible)
1037 else if (startIndex > FirstVisible && startIndex <= LastVisible)
1042 if (FirstVisible < 0) FirstVisible = 0;
1043 if (LastVisible < 0) LastVisible = 0;
1046 float scrollPosition = PrevScrollPosition;
1048 // Insertion above Top Visible!
1049 if (startIndex <= topInScreenIndex)
1051 scrollPosition = GetItemPosition(topInScreenIndex);
1052 scrollPosition -= offset;
1054 collectionView.ScrollTo(scrollPosition);
1058 // Update Viewport in delay.
1059 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1060 // but currently we do not have any accessor to pre-calculation so instead of this,
1061 // using Timer temporarily.
1062 DelayedRequestLayout(scrollPosition);
1066 [EditorBrowsable(EditorBrowsableState.Never)]
1067 public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
1070 if (source == null) throw new ArgumentNullException(nameof(source));
1071 if (collectionView == null) return;
1073 // Will be null if not a group.
1074 float currentSize = StepCandidate;
1075 IGroupableItemSource gSource = source as IGroupableItemSource;
1077 // Get the first Visible Position to adjust.
1079 int topInScreenIndex = 0;
1081 (topInScreenIndex, offset) = FindTopItemInScreen();
1084 // 1. Handle MeasureAll
1086 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1092 // 2. Handle Group Case
1093 if (isGrouped && gSource != null)
1095 int parentIndex = 0;
1096 GroupInfo groupInfo = null;
1097 foreach(GroupInfo cur in groups)
1099 if ((cur.StartIndex == startIndex) && (cur.Count == count))
1106 if (groupInfo == null) throw new Exception("Cannot find group information!");
1107 // Check item is group parent or not
1108 // if group parent, add new gorupinfo
1109 currentSize = groupInfo.GroupSize;
1110 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1112 // Update ItemSize and ItemPosition
1115 // groupInfo.Dispose();
1116 groups.Remove(groupInfo);
1118 for (int i = parentIndex; i < groups.Count; i++)
1120 groups[i].GroupPosition -= currentSize;
1121 groups[i].StartIndex -= count;
1126 //It must group case! throw exception!
1127 Tizen.Log.Error("NUI", "Not support remove ungrouped range items currently!");
1130 ScrollContentSize -= currentSize;
1132 // 3. Update Scroll Content Size
1133 if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
1134 else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
1136 // 4. Update Visible Items.
1137 List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
1138 foreach (RecyclerViewItem item in VisibleItems)
1140 if ((item.Index >= startIndex)
1141 && (item.Index < startIndex + count))
1143 unrealizedItems.Add(item);
1144 collectionView.UnrealizeItem(item);
1146 else if (item.Index >= startIndex + count)
1148 item.Index -= count;
1151 VisibleItems.RemoveAll(unrealizedItems.Contains);
1152 unrealizedItems.Clear();
1154 if (startIndex <= FirstVisible)
1156 FirstVisible = FirstVisible - count;
1157 LastVisible = LastVisible - count;
1159 else if (startIndex > FirstVisible && startIndex <= LastVisible)
1161 LastVisible = LastVisible - count;
1164 if (FirstVisible < 0) FirstVisible = 0;
1165 if (LastVisible < 0) LastVisible = 0;
1168 float scrollPosition = PrevScrollPosition;
1170 // Insertion above Top Visible!
1171 if (startIndex <= topInScreenIndex)
1173 scrollPosition = GetItemPosition(topInScreenIndex);
1174 scrollPosition -= offset;
1176 collectionView.ScrollTo(scrollPosition);
1180 // Update Viewport in delay.
1181 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1182 // but currently we do not have any accessor to pre-calculation so instead of this,
1183 // using Timer temporarily.
1184 DelayedRequestLayout(scrollPosition);
1188 [EditorBrowsable(EditorBrowsableState.Never)]
1189 public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
1192 if (source == null) throw new ArgumentNullException(nameof(source));
1193 if (collectionView == null) return;
1195 // Will be null if not a group.
1196 float currentSize = StepCandidate;
1197 int diff = toPosition - fromPosition;
1199 // Get the first Visible Position to adjust.
1201 int topInScreenIndex = 0;
1203 (topInScreenIndex, offset) = FindTopItemInScreen();
1206 // 1. Handle MeasureAll
1208 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1214 // Move can only happen in it's own groups.
1215 // so there will be no changes in position, startIndex in ohter groups.
1216 // check visible item and update indexs.
1217 int startIndex = ( diff > 0 ? fromPosition: toPosition);
1218 int endIndex = (diff > 0 ? toPosition: fromPosition);
1220 if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1222 foreach (RecyclerViewItem item in VisibleItems)
1224 if ((item.Index >= startIndex)
1225 && (item.Index <= endIndex))
1227 if (item.Index == fromPosition) item.Index = toPosition;
1230 if (diff > 0) item.Index--;
1237 if (fromPosition > FirstVisible)
1239 if (toPosition > LastVisible)
1244 else if (toPosition > FirstVisible && toPosition <= LastVisible)
1249 else if (fromPosition >= FirstVisible && fromPosition <= LastVisible)
1251 if (toPosition < FirstVisible)
1255 else if (toPosition > LastVisible)
1260 else if (fromPosition > LastVisible)
1262 if (toPosition <= FirstVisible)
1267 else if (toPosition > FirstVisible && toPosition <= LastVisible)
1273 if (FirstVisible < 0) FirstVisible = 0;
1274 if (LastVisible < 0) LastVisible = 0;
1275 if (FirstVisible > Source.Count - 1) FirstVisible = Source.Count -1;
1276 if (LastVisible > Source.Count - 1) LastVisible = Source.Count -1;
1279 if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
1280 else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
1283 float scrollPosition = PrevScrollPosition;
1285 // Insertion above Top Visible!
1286 if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1287 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1289 scrollPosition = GetItemPosition(topInScreenIndex);
1290 scrollPosition -= offset;
1292 collectionView.ScrollTo(scrollPosition);
1296 // Update Viewport in delay.
1297 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1298 // but currently we do not have any accessor to pre-calculation so instead of this,
1299 // using Timer temporarily.
1300 DelayedRequestLayout(scrollPosition);
1304 [EditorBrowsable(EditorBrowsableState.Never)]
1305 public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
1308 if (source == null) throw new ArgumentNullException(nameof(source));
1309 if (collectionView == null) return;
1311 // Will be null if not a group.
1312 float currentSize = StepCandidate;
1313 int diff = toPosition - fromPosition;
1315 int startIndex = ( diff > 0 ? fromPosition: toPosition);
1316 int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1);
1318 // 2. Handle Group Case
1321 int fromParentIndex = 0;
1322 int toParentIndex = 0;
1323 bool findFrom = false;
1324 bool findTo = false;
1325 GroupInfo fromGroup = null;
1326 GroupInfo toGroup = null;
1328 foreach(GroupInfo cur in groups)
1330 if ((cur.StartIndex == fromPosition) && (cur.Count == count))
1334 if (findFrom && findTo) break;
1336 else if (cur.StartIndex == toPosition)
1340 if (findFrom && findTo) break;
1342 if (!findFrom) fromParentIndex++;
1343 if (!findTo) toParentIndex++;
1345 if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!");
1347 fromGroup.StartIndex = toGroup.StartIndex;
1348 fromGroup.GroupPosition = toGroup.GroupPosition;
1350 endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1);
1352 groups.Remove(fromGroup);
1353 groups.Insert(toParentIndex, fromGroup);
1355 int startGroup = (diff > 0? fromParentIndex: toParentIndex);
1356 int endGroup = (diff > 0? toParentIndex: fromParentIndex);
1358 for (int i = startGroup; i <= endGroup; i++)
1360 if (i == toParentIndex) continue;
1361 float prevPos = groups[i].GroupPosition;
1362 int prevIdx = groups[i].StartIndex;
1363 groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize;
1364 groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count;
1369 //It must group case! throw exception!
1370 Tizen.Log.Error("NUI", "Not support move ungrouped range items currently!");
1373 // Move can only happen in it's own groups.
1374 // so there will be no changes in position, startIndex in ohter groups.
1375 // check visible item and update indexs.
1376 if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1378 foreach (RecyclerViewItem item in VisibleItems)
1380 if ((item.Index >= startIndex)
1381 && (item.Index <= endIndex))
1383 if ((item.Index >= fromPosition) && (item.Index < fromPosition + count))
1385 item.Index = fromPosition - item.Index + toPosition;
1389 if (diff > 0) item.Index -= count;
1390 else item.Index += count;
1395 // FIXME!! Unraelize All and reset First/Last Visible
1396 foreach (RecyclerViewItem item in VisibleItems)
1398 collectionView.UnrealizeItem(item);
1400 VisibleItems.Clear();
1405 float scrollPosition = PrevScrollPosition;
1407 // Insertion above Top Visible!
1408 if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1409 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1411 scrollPosition = GetItemPosition(topInScreenIndex);
1412 scrollPosition -= offset;
1414 collectionView.ScrollTo(scrollPosition);
1418 // Update Viewport in delay.
1419 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1420 // but currently we do not have any accessor to pre-calculation so instead of this,
1421 // using Timer temporarily.
1422 DelayedRequestLayout(scrollPosition);
1426 [EditorBrowsable(EditorBrowsableState.Never)]
1427 public override void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
1430 if (source == null) throw new ArgumentNullException(nameof(source));
1431 IGroupableItemSource gSource = source as IGroupableItemSource;
1432 if (gSource == null)throw new Exception("Source is not group!");
1433 if (collectionView == null) return;
1435 // Get the first Visible Position to adjust.
1437 int topInScreenIndex = 0;
1439 (topInScreenIndex, offset) = FindTopItemInScreen();
1443 // Unrealize, initialized all items in the Range
1446 // Update Viewport in delay.
1447 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1448 // but currently we do not have any accessor to pre-calculation so instead of this,
1449 // using Timer temporarily.
1450 DelayedRequestLayout(PrevScrollPosition);
1454 [EditorBrowsable(EditorBrowsableState.Never)]
1455 public override float CalculateLayoutOrientationSize()
1457 //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
1458 return ScrollContentSize;
1462 [EditorBrowsable(EditorBrowsableState.Never)]
1463 public override float CalculateCandidateScrollPosition(float scrollPosition)
1465 //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
1466 return scrollPosition;
1470 [EditorBrowsable(EditorBrowsableState.Never)]
1471 public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
1473 if (currentFocusedView == null)
1474 throw new ArgumentNullException(nameof(currentFocusedView));
1476 View nextFocusedView = null;
1477 int targetSibling = -1;
1481 case View.FocusDirection.Left:
1483 targetSibling = IsHorizontal? currentFocusedView.SiblingOrder - 1 : targetSibling;
1486 case View.FocusDirection.Right:
1488 targetSibling = IsHorizontal? currentFocusedView.SiblingOrder + 1 : targetSibling;
1491 case View.FocusDirection.Up:
1493 targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder - 1;
1496 case View.FocusDirection.Down:
1498 targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder + 1;
1503 if (targetSibling > -1 && targetSibling < Container.Children.Count)
1505 RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
1506 if (candidate != null && candidate.Index >= 0 && candidate.Index < Source.Count)
1508 nextFocusedView = candidate;
1512 return nextFocusedView;
1516 [EditorBrowsable(EditorBrowsableState.Never)]
1517 protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
1519 int MaxIndex = Source.Count - 1 - (hasFooter? 1 : 0);
1522 (int start, int end) found = (0, 0);
1524 // 1. Find the start index.
1525 // Header is Showing
1526 if (hasHeader && visibleArea.X <= headerSize + (IsHorizontal? Padding.Start: Padding.Top))
1535 foreach (GroupInfo gInfo in groups)
1539 if (gInfo.GroupPosition <= visibleArea.X &&
1540 gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
1542 for (int i = 0; i < gInfo.Count; i++)
1544 // Reach last index of group.
1545 if (i == (gInfo.Count - 1))
1547 found.start = gInfo.StartIndex + i - adds;
1552 else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.X &&
1553 GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.X)
1555 found.start = gInfo.StartIndex + i - adds;
1562 //footer only shows?
1565 found.start = MaxIndex;
1570 float visibleAreaX = visibleArea.X - (hasHeader? headerSize : 0);
1571 // Prevent zero division.
1572 var itemSize = (StepCandidate != 0)? StepCandidate : 1f;
1573 found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / itemSize)) - adds);
1576 if (found.start < 0) found.start = 0;
1579 if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
1581 found.end = MaxIndex + 1;
1588 // can it be start from founded group...?
1589 //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
1590 foreach (GroupInfo gInfo in groups)
1593 if (gInfo.GroupPosition <= visibleArea.Y &&
1594 gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
1596 for (int i = 0; i < gInfo.Count; i++)
1598 if (i == (gInfo.Count - 1))
1600 //Should be groupFooter!
1601 found.end = gInfo.StartIndex + i + adds;
1606 else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.Y &&
1607 GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.Y)
1609 found.end = gInfo.StartIndex + i + adds;
1616 if (failed) found.end = MaxIndex;
1620 float visibleAreaY = visibleArea.Y - (hasHeader? headerSize : 0);
1621 // Prevent zero division.
1622 var itemSize = (StepCandidate != 0)? StepCandidate : 1f;
1623 found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / itemSize)) + adds);
1624 if (hasHeader) found.end += 1;
1626 if (found.end > (MaxIndex)) found.end = MaxIndex;
1632 [EditorBrowsable(EditorBrowsableState.Never)]
1633 protected internal override (float X, float Y) GetItemPosition(int index)
1635 int spaceStartX = Padding.Start;
1636 int spaceStartY = Padding.Top;
1637 if (Source.IsHeader(index))
1639 return (spaceStartX + headerMargin.Start, spaceStartY + headerMargin.Top);
1641 else if (Source.IsFooter(index))
1643 return ((IsHorizontal? ScrollContentSize - footerSize - Padding.End + footerMargin.Start : spaceStartX + footerMargin.Start),
1644 (IsHorizontal? spaceStartY + footerMargin.Top : ScrollContentSize - footerSize - Padding.Bottom + footerMargin.Top));
1648 GroupInfo gInfo = GetGroupInfo(index);
1651 Tizen.Log.Error("NUI", "GroupInfo failed to get in GetItemPosition()!");
1654 float current = GetGroupPosition(gInfo, index);
1655 Extents itemMargin = CandidateMargin;
1657 if (Source.IsGroupHeader(index))
1659 itemMargin = groupHeaderMargin;
1661 else if (Source.IsGroupFooter(index))
1663 itemMargin = groupFooterMargin;
1665 return ((IsHorizontal?
1666 itemMargin.Start + GetGroupPosition(gInfo, index):
1667 spaceStartX + itemMargin.Start),
1669 spaceStartY + itemMargin.Top:
1670 itemMargin.Top + GetGroupPosition(gInfo, index)));
1672 else if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1674 //FIXME : CandidateMargin need to be actual itemMargin
1675 return ((IsHorizontal? ItemPosition[index] + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
1676 (IsHorizontal? spaceStartY + CandidateMargin.Top : ItemPosition[index] + CandidateMargin.Top));
1680 int adjustIndex = index - (hasHeader ? 1 : 0);
1681 float current = (IsHorizontal ? spaceStartX : spaceStartY) + (hasHeader? headerSize : 0) + adjustIndex * StepCandidate;
1682 //FIXME : CandidateMargin need to be actual itemMargin
1683 return ((IsHorizontal? current + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
1684 (IsHorizontal? spaceStartY + CandidateMargin.Top : current + CandidateMargin.Top));
1689 [EditorBrowsable(EditorBrowsableState.Never)]
1690 protected internal override (float Width, float Height) GetItemSize(int index)
1692 if (Source.IsHeader(index))
1694 return ((IsHorizontal? (int)headerSize : (int)(collectionView.Size.Width) - Padding.Start - Padding.End)
1695 - headerMargin.Start - headerMargin.End,
1696 (IsHorizontal? (int)collectionView.Size.Height - Padding.Top - Padding.Bottom: (int)headerSize)
1697 - headerMargin.Top - headerMargin.Bottom);
1699 else if (Source.IsFooter(index))
1701 return ((IsHorizontal? (int)footerSize : (int)(collectionView.Size.Width) - Padding.Start - Padding.End)
1702 - footerMargin.Start - footerMargin.End,
1703 (IsHorizontal? (int)collectionView.Size.Height - Padding.Top - Padding.Bottom: (int)footerSize)
1704 - footerMargin.Top - footerMargin.Bottom);
1706 else if (Source.IsGroupHeader(index))
1708 return ((IsHorizontal? (int)groupHeaderSize : (int)(collectionView.Size.Width) - Padding.Start - Padding.End)
1709 - groupHeaderMargin.Start - groupHeaderMargin.End,
1710 (IsHorizontal? (int)collectionView.Size.Height - Padding.Top - Padding.Bottom: (int)groupHeaderSize)
1711 - groupHeaderMargin.Top - groupHeaderMargin.Bottom);
1713 else if (Source.IsGroupFooter(index))
1715 return ((IsHorizontal? (int)groupFooterSize : (int)(collectionView.Size.Width) - Padding.Start - Padding.End)
1716 - groupFooterMargin.Start - groupFooterMargin.End,
1717 (IsHorizontal? (int)collectionView.Size.Height - Padding.Top - Padding.Bottom: (int)groupFooterSize)
1718 - groupFooterMargin.Top - groupFooterMargin.Bottom);
1722 return ((IsHorizontal? (int)StepCandidate : (int)(collectionView.Size.Width) - Padding.Start - Padding.End)
1723 - CandidateMargin.Start - CandidateMargin.End,
1724 (IsHorizontal? (int)collectionView.Size.Height - Padding.Top - Padding.Bottom: (int)StepCandidate)
1725 - CandidateMargin.Top - CandidateMargin.Bottom);
1729 private void DelayedRequestLayout(float scrollPosition , bool force = true)
1731 if (requestLayoutTimer != null)
1733 requestLayoutTimer.Dispose();
1736 requestLayoutTimer = new Timer(1);
1737 requestLayoutTimer.Interval = 1;
1738 requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) =>
1740 RequestLayout(scrollPosition, force);
1743 requestLayoutTimer.Start();
1747 private (int, float) FindTopItemInScreen()
1750 float offset = 0.0F, Pos, Size;
1752 foreach(RecyclerViewItem item in VisibleItems)
1754 Pos = IsHorizontal ? item.PositionX : item.PositionY;
1755 Size = IsHorizontal ? item.SizeWidth : item.SizeHeight;
1756 if (PrevScrollPosition >= Pos && PrevScrollPosition < Pos + Size)
1759 offset = Pos - PrevScrollPosition;
1764 return (index, offset);
1768 private float GetItemStepSize(int index)
1770 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1772 return ItemSize[index];
1776 if (Source.IsHeader(index))
1778 else if (Source.IsFooter(index))
1780 else if (Source.IsGroupHeader(index))
1781 return groupHeaderSize;
1782 else if (Source.IsGroupFooter(index))
1783 return groupFooterSize;
1785 return StepCandidate;
1789 private void UpdatePosition(int index)
1791 bool IsGroup = (Source is IGroupableItemSource);
1793 if (index <= 0) return;
1794 if (index >= Source.Count)
1798 //IsGroupHeader = (Source as IGroupableItemSource).IsGroupHeader(index);
1799 //IsGroupFooter = (Source as IGroupableItemSource).IsGroupFooter(index);
1802 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1803 ItemPosition[index] = ItemPosition[index - 1] + GetItemStepSize(index - 1);
1806 private GroupInfo GetGroupInfo(int index)
1808 if (Visited != null)
1810 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1813 if (hasHeader && index == 0) return null;
1814 foreach (GroupInfo group in groups)
1816 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1826 private float GetGroupPosition(GroupInfo groupInfo, int index)
1828 if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1829 return groupInfo.GroupPosition + groupInfo.ItemPosition[index - groupInfo.StartIndex];
1832 float pos = groupInfo.GroupPosition;
1833 if (groupInfo.StartIndex == index) return pos;
1835 pos = pos + groupHeaderSize + StepCandidate * (index - groupInfo.StartIndex - 1);
1841 private object GetGroupParent(int index)
1843 if (Visited != null)
1845 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1846 return Visited.GroupParent;
1848 if (hasHeader && index == 0) return null;
1849 foreach (GroupInfo group in groups)
1851 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1854 return group.GroupParent;