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;
206 //Final Check of FirstIndex
207 if ((colView.InternalItemSource.Count - 1 < firstIndex) ||
208 (colView.InternalItemSource.IsFooter(firstIndex) && (colView.InternalItemSource.Count - 1) == firstIndex))
215 (colView.InternalItemSource.IsHeader(firstIndex) ||
216 colView.InternalItemSource.IsGroupHeader(firstIndex) ||
217 colView.InternalItemSource.IsGroupFooter(firstIndex)))
220 if (colView.InternalItemSource.IsFooter(firstIndex)
221 || ((colView.InternalItemSource.Count - 1) <= firstIndex))
232 RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
233 if (sizeDeligate == null)
236 throw new Exception("Cannot create content from DatTemplate.");
239 sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
241 // Need to Set proper height or width on scroll direction.
242 if (sizeDeligate.Layout == null)
244 width = sizeDeligate.WidthSpecification;
245 height = sizeDeligate.HeightSpecification;
249 MeasureChild(colView, sizeDeligate);
251 width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
252 height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
254 // pick the StepCandidate.
255 Extents itemMargin = sizeDeligate.Margin;
256 StepCandidate = IsHorizontal?
257 width + itemMargin.Start + itemMargin.End:
258 height + itemMargin.Top + itemMargin.Bottom;
259 CandidateMargin = new Extents(itemMargin);
260 if (StepCandidate == 0) StepCandidate = 1; //????
262 colView.UnrealizeItem(sizeDeligate, false);
265 float Current = IsHorizontal? Padding.Start : Padding.Top;
266 IGroupableItemSource source = colView.InternalItemSource;
267 GroupInfo currentGroup = null;
268 object currentParent = null;
269 for (int i = 0; i < count; i++)
271 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
273 if (i == 0 && hasHeader)
274 ItemSize.Add(headerSize);
275 else if (i == count - 1 && hasFooter)
276 ItemSize.Add(footerSize);
277 else if (source.IsGroupHeader(i))
278 ItemSize.Add(groupHeaderSize);
279 else if (source.IsGroupFooter(i))
280 ItemSize.Add(groupFooterSize);
281 else ItemSize.Add(StepCandidate);
285 if (source.IsHeader(i))
287 //ItemPosition.Add(Current);
288 Current += headerSize;
290 else if (source.IsFooter(i))
292 //ItemPosition.Add(Current);
293 Current += footerSize;
297 //GroupHeader must always exist in group usage.
298 //if (source.IsGroupHeader(i))
299 if (source.GetGroupParent(i) != currentParent)
301 currentParent = source.GetGroupParent(i);
302 float currentSize = (source.IsGroupHeader(i)? groupHeaderSize :
303 (source.IsGroupFooter(i)? groupFooterSize: StepCandidate));
304 currentGroup = new GroupInfo()
306 GroupParent = currentParent,
311 GroupSize = currentSize,
312 GroupPosition = Current
314 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
315 currentGroup.ItemPosition.Add(0);
316 groups.Add(currentGroup);
317 if (source.IsGroupHeader(i)) Current += currentSize;
320 else if (source.IsGroupFooter(i))
322 //currentGroup.hasFooter = true;
323 if (currentGroup != null)
325 currentGroup.Count++;
326 currentGroup.GroupSize += groupFooterSize;
327 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
328 currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
329 Current += groupFooterSize;
334 if (currentGroup != null)
336 currentGroup.Count++;
337 currentGroup.GroupSize += StepCandidate;
338 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
339 currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
340 Current += StepCandidate;
347 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
348 ItemPosition.Add(Current);
350 if (i == 0 && hasHeader) Current += headerSize;
351 else if (i == count - 1 && hasFooter) Current += footerSize;
352 else Current += StepCandidate;
356 ScrollContentSize = Current + (IsHorizontal? Padding.End : Padding.Bottom);
357 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
358 else colView.ContentContainer.SizeHeight = ScrollContentSize;
360 base.Initialize(view);
361 //Console.WriteLine("[NUI] Init Done, StepCnadidate{0}, Scroll{1}", StepCandidate, ScrollContentSize);
365 /// This is called to find out where items are lain out according to current scroll position.
367 /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
368 /// <param name="force">boolean force flag to layouting forcely.</param>
369 /// <since_tizen> 9 </since_tizen>
370 public override void RequestLayout(float scrollPosition, bool force = false)
372 // Layouting is only possible after once it initialized.
373 if (!IsInitialized) return;
375 if (requestLayoutTimer != null)
377 requestLayoutTimer.Dispose();
378 requestLayoutTimer = null;
382 int LastIndex = colView.InternalItemSource.Count - 1;
384 if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
385 PrevScrollPosition = Math.Abs(scrollPosition);
387 if (ItemSizeChanged >= 0)
389 for (int i = ItemSizeChanged; i <= LastIndex; i++)
391 (float updateX, float updateY) = GetItemPosition(LastIndex);
392 ScrollContentSize = GetItemStepSize(LastIndex) + (IsHorizontal? updateX + Padding.End : updateY + Padding.Bottom);
395 int prevFirstVisible = FirstVisible;
396 int prevLastVisible = LastVisible;
398 (float X, float Y) visibleArea = (PrevScrollPosition,
399 PrevScrollPosition + (IsHorizontal? colView.Size.Width : colView.Size.Height)
402 // 1. Set First/Last Visible Item Index.
403 (int start, int end) = FindVisibleItems(visibleArea);
404 FirstVisible = start;
407 // 2. Unrealize invisible items.
408 List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
409 foreach (RecyclerViewItem item in VisibleItems)
411 if (item.Index < FirstVisible || item.Index > LastVisible)
413 unrealizedItems.Add(item);
414 colView.UnrealizeItem(item);
417 VisibleItems.RemoveAll(unrealizedItems.Contains);
418 unrealizedItems.Clear();
420 // 3. Realize and placing visible items.
421 for (int i = FirstVisible; i <= LastVisible; i++)
423 RecyclerViewItem item = null;
424 // 4. Get item if visible or realize new.
425 if (i >= prevFirstVisible && i <= prevLastVisible)
427 item = GetVisibleItem(i);
428 if (item != null && !force) continue;
433 item = colView.RealizeItem(i);
434 if (item != null) VisibleItems.Add(item);
435 else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]");
438 (float posX, float posY) = GetItemPosition(i);
439 item.Position = new Position(posX, posY);
441 var size = (IsHorizontal? item.SizeWidth: item.SizeHeight);
443 if (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst)
445 if (item.IsHeader || item.IsFooter || item.isGroupHeader || item.isGroupFooter)
447 if (item.IsHeader) size = headerSize;
448 else if (item.IsFooter) size = footerSize;
449 else if (item.isGroupHeader) size = groupHeaderSize;
450 else if (item.isGroupFooter) size = groupFooterSize;
452 else size = StepCandidate;
454 if (IsHorizontal && item.HeightSpecification == LayoutParamPolicies.MatchParent)
456 item.Size = new Size(size, Container.Size.Height - Padding.Top - Padding.Bottom - item.Margin.Top - item.Margin.Bottom);
458 else if (!IsHorizontal && item.WidthSpecification == LayoutParamPolicies.MatchParent)
460 item.Size = new Size(Container.Size.Width - Padding.Start - Padding.End - item.Margin.Start - item.Margin.End, size);
467 /// Clear the current screen and all properties.
469 [EditorBrowsable(EditorBrowsableState.Never)]
470 public override void Clear()
473 if (requestLayoutTimer != null)
475 requestLayoutTimer.Dispose();
480 foreach (GroupInfo group in groups)
482 //group.ItemPosition?.Clear();
489 if (ItemPosition != null)
491 ItemPosition.Clear();
493 if (ItemSize != null)
497 if (headerMargin != null)
499 headerMargin.Dispose();
502 if (footerMargin != null)
504 footerMargin.Dispose();
507 if (groupHeaderMargin != null)
509 groupHeaderMargin.Dispose();
510 groupHeaderMargin = null;
512 if (groupFooterMargin != null)
514 groupFooterMargin.Dispose();
515 groupFooterMargin = null;
523 [EditorBrowsable(EditorBrowsableState.Never)]
524 public override void NotifyItemSizeChanged(RecyclerViewItem item)
527 throw new ArgumentNullException(nameof(item));
528 if (colView == null) return;
530 if (!IsInitialized ||
531 (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst &&
536 float PrevSize, CurrentSize;
537 if (item.Index == (colView.InternalItemSource.Count - 1))
539 PrevSize = ScrollContentSize - ItemPosition[item.Index];
543 PrevSize = ItemPosition[item.Index + 1] - ItemPosition[item.Index];
546 CurrentSize = (IsHorizontal? item.Size.Width : item.Size.Height);
548 if (CurrentSize != PrevSize)
550 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
551 ItemSize[item.Index] = CurrentSize;
553 StepCandidate = CurrentSize;
555 if (ItemSizeChanged == -1) ItemSizeChanged = item.Index;
556 else ItemSizeChanged = Math.Min(ItemSizeChanged, item.Index);
558 //ScrollContentSize += Diff; UpdateOnce?
562 [EditorBrowsable(EditorBrowsableState.Never)]
563 public override void NotifyItemInserted(IItemSource source, int startIndex)
565 // Insert Single item.
566 if (source == null) throw new ArgumentNullException(nameof(source));
567 if (colView == null) return;
569 if (isSourceEmpty || StepCandidate == 0)
574 // Will be null if not a group.
575 float currentSize = StepCandidate;
576 IGroupableItemSource gSource = source as IGroupableItemSource;
578 // Get the first Visible Position to adjust.
580 int topInScreenIndex = 0;
582 (topInScreenIndex, offset) = FindTopItemInScreen();
585 // 1. Handle MeasureAll
587 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
593 //2. Handle Group Case.
594 if (isGrouped && gSource != null)
596 GroupInfo groupInfo = null;
597 object groupParent = gSource.GetGroupParent(startIndex);
598 int parentIndex = gSource.GetPosition(groupParent);
599 if (gSource.HasHeader) parentIndex--;
601 // Check item is group parent or not
602 // if group parent, add new gorupinfo
603 if (gSource.IsHeader(startIndex))
605 // This is childless group.
606 // create new groupInfo!
607 groupInfo = new GroupInfo()
609 GroupParent = groupParent,
610 StartIndex = startIndex,
612 GroupSize = groupHeaderSize,
615 if (parentIndex >= groups.Count)
617 groupInfo.GroupPosition = ScrollContentSize;
618 groups.Add(groupInfo);
622 groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
623 groups.Insert(parentIndex, groupInfo);
626 currentSize = groupHeaderSize;
630 // If not group parent, add item into the groupinfo.
631 if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts.");
632 groupInfo = groups[parentIndex];//GetGroupInfo(groupParent);
633 if (groupInfo == null) throw new Exception("Cannot find group information!");
636 if (gSource.IsGroupFooter(startIndex))
638 // It doesn't make sence to adding footer by notify...
639 // if GroupFooterTemplate is added,
640 // need to implement on here.
644 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
646 float curPos = groupInfo.ItemPosition[startIndex - groupInfo.StartIndex];
647 groupInfo.ItemPosition.Insert(startIndex - groupInfo.StartIndex, curPos);
648 for (int i = startIndex - groupInfo.StartIndex; i < groupInfo.Count; i++)
650 groupInfo.ItemPosition[i] = curPos;
651 curPos += GetItemStepSize(parentIndex + i);
653 groupInfo.GroupSize = curPos;
657 groupInfo.GroupSize += currentSize;
662 if (parentIndex + 1 < groups.Count)
664 for(int i = parentIndex + 1; i < groups.Count; i++)
666 groups[i].GroupPosition += currentSize;
667 groups[i].StartIndex++;
673 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
675 // Need to Implements
680 // 3. Update Scroll Content Size
681 ScrollContentSize += currentSize;
683 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
684 else colView.ContentContainer.SizeHeight = ScrollContentSize;
686 // 4. Update Visible Items.
687 foreach (RecyclerViewItem item in VisibleItems)
689 if (item.Index >= startIndex)
695 if (startIndex <= FirstVisible)
700 else if (startIndex > FirstVisible && startIndex <= LastVisible)
705 if (FirstVisible > colView.InternalItemSource.Count - 1) FirstVisible = colView.InternalItemSource.Count -1;
706 if (LastVisible > colView.InternalItemSource.Count - 1) LastVisible = colView.InternalItemSource.Count -1;
708 float scrollPosition = PrevScrollPosition;
712 // Insertion above Top Visible!
713 if (startIndex <= topInScreenIndex)
715 scrollPosition = GetItemPosition(topInScreenIndex);
716 scrollPosition -= offset;
718 colView.ScrollTo(scrollPosition);
722 // Update Viewport in delay.
723 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
724 // but currently we do not have any accessor to pre-calculation so instead of this,
725 // using Timer temporarily.
726 DelayedRequestLayout(scrollPosition);
730 [EditorBrowsable(EditorBrowsableState.Never)]
731 public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
734 if (source == null) throw new ArgumentNullException(nameof(source));
735 if (colView == null) return;
737 if (isSourceEmpty || StepCandidate == 0)
742 float currentSize = StepCandidate;
743 // Will be null if not a group.
744 IGroupableItemSource gSource = source as IGroupableItemSource;
746 // Get the first Visible Position to adjust.
748 int topInScreenIndex = 0;
750 (topInScreenIndex, offset) = FindTopItemInScreen();
753 // 1. Handle MeasureAll
755 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
761 // 2. Handle Group Case
762 // Adding ranged items should all same new groups.
763 if (isGrouped && gSource != null)
765 GroupInfo groupInfo = null;
766 object groupParent = gSource.GetGroupParent(startIndex);
767 int parentIndex = gSource.GetPosition(groupParent);
768 if (gSource.HasHeader) parentIndex--;
770 // We guess here that range inserted from GroupStartIndex.
771 int groupStartIndex = startIndex;
773 for (int current = startIndex; current - startIndex < count; current++)
775 // Check item is group parent or not
776 // if group parent, add new gorupinfo
777 if (groupStartIndex == current)
779 currentSize = (gSource.IsGroupHeader(current)? groupHeaderSize :
780 (gSource.IsGroupFooter(current)? groupFooterSize: currentSize));
781 //create new groupInfo!
782 groupInfo = new GroupInfo()
784 GroupParent = groupParent,
785 StartIndex = current,
787 GroupSize = currentSize,
793 //if not group parent, add item into the groupinfo.
794 //groupInfo = GetGroupInfo(groupStartIndex);
795 if (groupInfo == null) throw new Exception("Cannot find group information!");
798 if (gSource.IsGroupFooter(current))
800 groupInfo.GroupSize += groupFooterSize;
804 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
808 float curPos = groupInfo.ItemPosition[current - groupStartIndex];
809 groupInfo.ItemPosition.Insert(current - groupStartIndex, curPos);
810 for (int i = current - groupStartIndex; i < groupInfo.Count; i++)
812 groupInfo.ItemPosition[i] = curPos;
813 curPos += GetItemSize(parentIndex + i);
815 groupInfo.GroupSize = curPos;
820 groupInfo.GroupSize += StepCandidate;
826 if (groupInfo != null)
828 if (parentIndex >= groups.Count)
830 groupInfo.GroupPosition = ScrollContentSize;
831 groups.Add(groupInfo);
835 groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
836 groups.Insert(parentIndex, groupInfo);
839 // Update other below group's position
840 if (parentIndex + 1 < groups.Count)
842 for(int i = parentIndex + 1; i < groups.Count; i++)
844 groups[i].GroupPosition += groupInfo.GroupSize;
845 groups[i].StartIndex += count;
849 ScrollContentSize += groupInfo.GroupSize;
853 Tizen.Log.Error("NUI", "groupInfo is null! Check count = 0");
858 Tizen.Log.Error("NUI", "Not support insert ungrouped range items currently!");
861 // 3. Update Scroll Content Size
862 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
863 else colView.ContentContainer.SizeHeight = ScrollContentSize;
865 // 4. Update Visible Items.
866 foreach (RecyclerViewItem item in VisibleItems)
868 if (item.Index >= startIndex)
874 if (startIndex <= FirstVisible)
876 FirstVisible = FirstVisible + count;
877 LastVisible = LastVisible + count;
879 else if (startIndex > FirstVisible && startIndex <= LastVisible)
881 LastVisible = LastVisible + count;
884 if (FirstVisible > colView.InternalItemSource.Count - 1) FirstVisible = colView.InternalItemSource.Count -1;
885 if (LastVisible > colView.InternalItemSource.Count - 1) LastVisible = colView.InternalItemSource.Count -1;
888 float scrollPosition = PrevScrollPosition;
890 // Insertion above Top Visible!
891 if (startIndex + count <= topInScreenIndex)
893 scrollPosition = GetItemPosition(topInScreenIndex);
894 scrollPosition -= offset;
896 colView.ScrollTo(scrollPosition);
900 // Update Viewport in delay.
901 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
902 // but currently we do not have any accessor to pre-calculation so instead of this,
903 // using Timer temporarily.
904 DelayedRequestLayout(scrollPosition);
908 [EditorBrowsable(EditorBrowsableState.Never)]
909 public override void NotifyItemRemoved(IItemSource source, int startIndex)
912 if (source == null) throw new ArgumentNullException(nameof(source));
913 if (colView == null) return;
915 // Will be null if not a group.
916 float currentSize = StepCandidate;
917 IGroupableItemSource gSource = source as IGroupableItemSource;
919 // Get the first Visible Position to adjust.
921 int topInScreenIndex = 0;
923 (topInScreenIndex, offset) = FindTopItemInScreen();
926 // 1. Handle MeasureAll
928 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
934 // 2. Handle Group Case
935 if (isGrouped && gSource != null)
938 GroupInfo groupInfo = null;
939 foreach(GroupInfo cur in groups)
941 if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex))
948 if (groupInfo == null) throw new Exception("Cannot find group information!");
949 // Check item is group parent or not
950 // if group parent, add new gorupinfo
951 if (groupInfo.StartIndex == startIndex)
953 // This is empty group!
954 // check group is empty.
955 if (groupInfo.Count != 1)
957 throw new Exception("Cannot remove group parent");
959 currentSize = groupInfo.GroupSize;
962 // groupInfo.Dispose();
963 groups.Remove(groupInfo);
970 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
972 //Need to Implement this.
976 groupInfo.GroupSize -= currentSize;
980 for (int i = parentIndex + 1; i < groups.Count; i++)
982 groups[i].GroupPosition -= currentSize;
983 groups[i].StartIndex--;
988 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
990 // Need to Implements
992 // else Nothing to Do
995 ScrollContentSize -= currentSize;
997 // 3. Update Scroll Content Size
998 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
999 else colView.ContentContainer.SizeHeight = ScrollContentSize;
1001 // 4. Update Visible Items.
1002 RecyclerViewItem targetItem = null;
1003 foreach (RecyclerViewItem item in VisibleItems)
1005 if (item.Index == startIndex)
1008 colView.UnrealizeItem(item);
1010 else if (item.Index > startIndex)
1015 VisibleItems.Remove(targetItem);
1018 if (startIndex <= FirstVisible)
1023 else if (startIndex > FirstVisible && startIndex <= LastVisible)
1028 if (FirstVisible < 0) FirstVisible = 0;
1029 if (LastVisible < 0) LastVisible = 0;
1032 float scrollPosition = PrevScrollPosition;
1034 // Insertion above Top Visible!
1035 if (startIndex <= topInScreenIndex)
1037 scrollPosition = GetItemPosition(topInScreenIndex);
1038 scrollPosition -= offset;
1040 colView.ScrollTo(scrollPosition);
1044 // Update Viewport in delay.
1045 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1046 // but currently we do not have any accessor to pre-calculation so instead of this,
1047 // using Timer temporarily.
1048 DelayedRequestLayout(scrollPosition);
1052 [EditorBrowsable(EditorBrowsableState.Never)]
1053 public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
1056 if (source == null) throw new ArgumentNullException(nameof(source));
1057 if (colView == null) return;
1059 // Will be null if not a group.
1060 float currentSize = StepCandidate;
1061 IGroupableItemSource gSource = source as IGroupableItemSource;
1063 // Get the first Visible Position to adjust.
1065 int topInScreenIndex = 0;
1067 (topInScreenIndex, offset) = FindTopItemInScreen();
1070 // 1. Handle MeasureAll
1072 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1078 // 2. Handle Group Case
1079 if (isGrouped && gSource != null)
1081 int parentIndex = 0;
1082 GroupInfo groupInfo = null;
1083 foreach(GroupInfo cur in groups)
1085 if ((cur.StartIndex == startIndex) && (cur.Count == count))
1092 if (groupInfo == null) throw new Exception("Cannot find group information!");
1093 // Check item is group parent or not
1094 // if group parent, add new gorupinfo
1095 currentSize = groupInfo.GroupSize;
1096 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1098 // Update ItemSize and ItemPosition
1101 // groupInfo.Dispose();
1102 groups.Remove(groupInfo);
1104 for (int i = parentIndex; i < groups.Count; i++)
1106 groups[i].GroupPosition -= currentSize;
1107 groups[i].StartIndex -= count;
1112 //It must group case! throw exception!
1113 Tizen.Log.Error("NUI", "Not support remove ungrouped range items currently!");
1116 ScrollContentSize -= currentSize;
1118 // 3. Update Scroll Content Size
1119 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
1120 else colView.ContentContainer.SizeHeight = ScrollContentSize;
1122 // 4. Update Visible Items.
1123 List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
1124 foreach (RecyclerViewItem item in VisibleItems)
1126 if ((item.Index >= startIndex)
1127 && (item.Index < startIndex + count))
1129 unrealizedItems.Add(item);
1130 colView.UnrealizeItem(item);
1132 else if (item.Index >= startIndex + count)
1134 item.Index -= count;
1137 VisibleItems.RemoveAll(unrealizedItems.Contains);
1138 unrealizedItems.Clear();
1140 if (startIndex <= FirstVisible)
1142 FirstVisible = FirstVisible - count;
1143 LastVisible = LastVisible - count;
1145 else if (startIndex > FirstVisible && startIndex <= LastVisible)
1147 LastVisible = LastVisible - count;
1150 if (FirstVisible < 0) FirstVisible = 0;
1151 if (LastVisible < 0) LastVisible = 0;
1154 float scrollPosition = PrevScrollPosition;
1156 // Insertion above Top Visible!
1157 if (startIndex <= topInScreenIndex)
1159 scrollPosition = GetItemPosition(topInScreenIndex);
1160 scrollPosition -= offset;
1162 colView.ScrollTo(scrollPosition);
1166 // Update Viewport in delay.
1167 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1168 // but currently we do not have any accessor to pre-calculation so instead of this,
1169 // using Timer temporarily.
1170 DelayedRequestLayout(scrollPosition);
1174 [EditorBrowsable(EditorBrowsableState.Never)]
1175 public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
1178 if (source == null) throw new ArgumentNullException(nameof(source));
1179 if (colView == null) return;
1181 // Will be null if not a group.
1182 float currentSize = StepCandidate;
1183 int diff = toPosition - fromPosition;
1185 // Get the first Visible Position to adjust.
1187 int topInScreenIndex = 0;
1189 (topInScreenIndex, offset) = FindTopItemInScreen();
1192 // 1. Handle MeasureAll
1194 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1200 // Move can only happen in it's own groups.
1201 // so there will be no changes in position, startIndex in ohter groups.
1202 // check visible item and update indexs.
1203 int startIndex = ( diff > 0 ? fromPosition: toPosition);
1204 int endIndex = (diff > 0 ? toPosition: fromPosition);
1206 if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1208 foreach (RecyclerViewItem item in VisibleItems)
1210 if ((item.Index >= startIndex)
1211 && (item.Index <= endIndex))
1213 if (item.Index == fromPosition) item.Index = toPosition;
1216 if (diff > 0) item.Index--;
1223 if (fromPosition > FirstVisible)
1225 if (toPosition > LastVisible)
1230 else if (toPosition > FirstVisible && toPosition <= LastVisible)
1235 else if (fromPosition >= FirstVisible && fromPosition <= LastVisible)
1237 if (toPosition < FirstVisible)
1241 else if (toPosition > LastVisible)
1246 else if (fromPosition > LastVisible)
1248 if (toPosition <= FirstVisible)
1253 else if (toPosition > FirstVisible && toPosition <= LastVisible)
1259 if (FirstVisible < 0) FirstVisible = 0;
1260 if (LastVisible < 0) LastVisible = 0;
1261 if (FirstVisible > colView.InternalItemSource.Count - 1) FirstVisible = colView.InternalItemSource.Count -1;
1262 if (LastVisible > colView.InternalItemSource.Count - 1) LastVisible = colView.InternalItemSource.Count -1;
1265 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
1266 else colView.ContentContainer.SizeHeight = ScrollContentSize;
1269 float scrollPosition = PrevScrollPosition;
1271 // Insertion above Top Visible!
1272 if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1273 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1275 scrollPosition = GetItemPosition(topInScreenIndex);
1276 scrollPosition -= offset;
1278 colView.ScrollTo(scrollPosition);
1282 // Update Viewport in delay.
1283 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1284 // but currently we do not have any accessor to pre-calculation so instead of this,
1285 // using Timer temporarily.
1286 DelayedRequestLayout(scrollPosition);
1290 [EditorBrowsable(EditorBrowsableState.Never)]
1291 public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
1294 if (source == null) throw new ArgumentNullException(nameof(source));
1295 if (colView == null) return;
1297 // Will be null if not a group.
1298 float currentSize = StepCandidate;
1299 int diff = toPosition - fromPosition;
1301 int startIndex = ( diff > 0 ? fromPosition: toPosition);
1302 int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1);
1304 // 2. Handle Group Case
1307 int fromParentIndex = 0;
1308 int toParentIndex = 0;
1309 bool findFrom = false;
1310 bool findTo = false;
1311 GroupInfo fromGroup = null;
1312 GroupInfo toGroup = null;
1314 foreach(GroupInfo cur in groups)
1316 if ((cur.StartIndex == fromPosition) && (cur.Count == count))
1320 if (findFrom && findTo) break;
1322 else if (cur.StartIndex == toPosition)
1326 if (findFrom && findTo) break;
1328 if (!findFrom) fromParentIndex++;
1329 if (!findTo) toParentIndex++;
1331 if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!");
1333 fromGroup.StartIndex = toGroup.StartIndex;
1334 fromGroup.GroupPosition = toGroup.GroupPosition;
1336 endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1);
1338 groups.Remove(fromGroup);
1339 groups.Insert(toParentIndex, fromGroup);
1341 int startGroup = (diff > 0? fromParentIndex: toParentIndex);
1342 int endGroup = (diff > 0? toParentIndex: fromParentIndex);
1344 for (int i = startGroup; i <= endGroup; i++)
1346 if (i == toParentIndex) continue;
1347 float prevPos = groups[i].GroupPosition;
1348 int prevIdx = groups[i].StartIndex;
1349 groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize;
1350 groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count;
1355 //It must group case! throw exception!
1356 Tizen.Log.Error("NUI", "Not support move ungrouped range items currently!");
1359 // Move can only happen in it's own groups.
1360 // so there will be no changes in position, startIndex in ohter groups.
1361 // check visible item and update indexs.
1362 if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1364 foreach (RecyclerViewItem item in VisibleItems)
1366 if ((item.Index >= startIndex)
1367 && (item.Index <= endIndex))
1369 if ((item.Index >= fromPosition) && (item.Index < fromPosition + count))
1371 item.Index = fromPosition - item.Index + toPosition;
1375 if (diff > 0) item.Index -= count;
1376 else item.Index += count;
1381 // FIXME!! Unraelize All and reset First/Last Visible
1382 foreach (RecyclerViewItem item in VisibleItems)
1384 colView.UnrealizeItem(item);
1386 VisibleItems.Clear();
1391 float scrollPosition = PrevScrollPosition;
1393 // Insertion above Top Visible!
1394 if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1395 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1397 scrollPosition = GetItemPosition(topInScreenIndex);
1398 scrollPosition -= offset;
1400 colView.ScrollTo(scrollPosition);
1404 // Update Viewport in delay.
1405 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1406 // but currently we do not have any accessor to pre-calculation so instead of this,
1407 // using Timer temporarily.
1408 DelayedRequestLayout(scrollPosition);
1412 [EditorBrowsable(EditorBrowsableState.Never)]
1413 public override void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
1416 if (source == null) throw new ArgumentNullException(nameof(source));
1417 IGroupableItemSource gSource = source as IGroupableItemSource;
1418 if (gSource == null)throw new Exception("Source is not group!");
1419 if (colView == null) return;
1421 // Get the first Visible Position to adjust.
1423 int topInScreenIndex = 0;
1425 (topInScreenIndex, offset) = FindTopItemInScreen();
1429 // Unrealize, initialized all items in the Range
1432 // Update Viewport in delay.
1433 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1434 // but currently we do not have any accessor to pre-calculation so instead of this,
1435 // using Timer temporarily.
1436 DelayedRequestLayout(PrevScrollPosition);
1440 [EditorBrowsable(EditorBrowsableState.Never)]
1441 public override float CalculateLayoutOrientationSize()
1443 //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
1444 return ScrollContentSize;
1448 [EditorBrowsable(EditorBrowsableState.Never)]
1449 public override float CalculateCandidateScrollPosition(float scrollPosition)
1451 //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
1452 return scrollPosition;
1456 [EditorBrowsable(EditorBrowsableState.Never)]
1457 public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
1459 if (currentFocusedView == null)
1460 throw new ArgumentNullException(nameof(currentFocusedView));
1462 View nextFocusedView = null;
1463 int targetSibling = -1;
1467 case View.FocusDirection.Left:
1469 targetSibling = IsHorizontal? currentFocusedView.SiblingOrder - 1 : targetSibling;
1472 case View.FocusDirection.Right:
1474 targetSibling = IsHorizontal? currentFocusedView.SiblingOrder + 1 : targetSibling;
1477 case View.FocusDirection.Up:
1479 targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder - 1;
1482 case View.FocusDirection.Down:
1484 targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder + 1;
1489 if (targetSibling > -1 && targetSibling < Container.Children.Count)
1491 RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
1492 if (candidate != null && candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
1494 nextFocusedView = candidate;
1498 return nextFocusedView;
1502 [EditorBrowsable(EditorBrowsableState.Never)]
1503 protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
1505 int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter? 1 : 0);
1508 (int start, int end) found = (0, 0);
1510 // 1. Find the start index.
1511 // Header is Showing
1512 if (hasHeader && visibleArea.X <= headerSize + (IsHorizontal? Padding.Start: Padding.Top))
1521 foreach (GroupInfo gInfo in groups)
1525 if (gInfo.GroupPosition <= visibleArea.X &&
1526 gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
1528 for (int i = 0; i < gInfo.Count; i++)
1530 // Reach last index of group.
1531 if (i == (gInfo.Count - 1))
1533 found.start = gInfo.StartIndex + i - adds;
1538 else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.X &&
1539 GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.X)
1541 found.start = gInfo.StartIndex + i - adds;
1548 //footer only shows?
1551 found.start = MaxIndex;
1556 float visibleAreaX = visibleArea.X - (hasHeader? headerSize : 0);
1557 // Prevent zero division.
1558 var itemSize = (StepCandidate != 0)? StepCandidate : 1f;
1559 found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / itemSize)) - adds);
1562 if (found.start < 0) found.start = 0;
1565 if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
1567 found.end = MaxIndex + 1;
1574 // can it be start from founded group...?
1575 //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
1576 foreach (GroupInfo gInfo in groups)
1579 if (gInfo.GroupPosition <= visibleArea.Y &&
1580 gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
1582 for (int i = 0; i < gInfo.Count; i++)
1584 if (i == (gInfo.Count - 1))
1586 //Should be groupFooter!
1587 found.end = gInfo.StartIndex + i + adds;
1592 else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.Y &&
1593 GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.Y)
1595 found.end = gInfo.StartIndex + i + adds;
1602 if (failed) found.end = MaxIndex;
1606 float visibleAreaY = visibleArea.Y - (hasHeader? headerSize : 0);
1607 // Prevent zero division.
1608 var itemSize = (StepCandidate != 0)? StepCandidate : 1f;
1609 found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / itemSize)) + adds);
1610 if (hasHeader) found.end += 1;
1612 if (found.end > (MaxIndex)) found.end = MaxIndex;
1617 // Item position excluding margins.
1618 internal override (float X, float Y) GetItemPosition(int index)
1620 int spaceStartX = Padding.Start;
1621 int spaceStartY = Padding.Top;
1622 if (colView.InternalItemSource.IsHeader(index))
1624 return (spaceStartX + headerMargin.Start, spaceStartY + headerMargin.Top);
1626 else if (colView.InternalItemSource.IsFooter(index))
1628 return ((IsHorizontal? ScrollContentSize - footerSize - Padding.End + footerMargin.Start : spaceStartX + footerMargin.Start),
1629 (IsHorizontal? spaceStartY + footerMargin.Top : ScrollContentSize - footerSize - Padding.Bottom + footerMargin.Top));
1633 GroupInfo gInfo = GetGroupInfo(index);
1636 Tizen.Log.Error("NUI", "GroupInfo failed to get in GetItemPosition()!");
1639 float current = GetGroupPosition(gInfo, index);
1640 Extents itemMargin = CandidateMargin;
1642 if (colView.InternalItemSource.IsGroupHeader(index))
1644 itemMargin = groupHeaderMargin;
1646 else if (colView.InternalItemSource.IsGroupFooter(index))
1648 itemMargin = groupFooterMargin;
1650 return ((IsHorizontal?
1651 itemMargin.Start + GetGroupPosition(gInfo, index):
1652 spaceStartX + itemMargin.Start),
1654 spaceStartY + itemMargin.Top:
1655 itemMargin.Top + GetGroupPosition(gInfo, index)));
1657 else if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1659 //FIXME : CandidateMargin need to be actual itemMargin
1660 return ((IsHorizontal? ItemPosition[index] + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
1661 (IsHorizontal? spaceStartY + CandidateMargin.Top : ItemPosition[index] + CandidateMargin.Top));
1665 int adjustIndex = index - (hasHeader ? 1 : 0);
1666 float current = (IsHorizontal ? spaceStartX : spaceStartY) + (hasHeader? headerSize : 0) + adjustIndex * StepCandidate;
1667 //FIXME : CandidateMargin need to be actual itemMargin
1668 return ((IsHorizontal? current + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
1669 (IsHorizontal? spaceStartY + CandidateMargin.Top : current + CandidateMargin.Top));
1673 // Item size excluding margins. this size is approximated size.
1674 internal override (float Width, float Height) GetItemSize(int index)
1676 if (colView.InternalItemSource.IsHeader(index))
1678 return ((IsHorizontal? (int)headerSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1679 - headerMargin.Start - headerMargin.End,
1680 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)headerSize)
1681 - headerMargin.Top - headerMargin.Bottom);
1683 else if (colView.InternalItemSource.IsFooter(index))
1685 return ((IsHorizontal? (int)footerSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1686 - footerMargin.Start - footerMargin.End,
1687 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)footerSize)
1688 - footerMargin.Top - footerMargin.Bottom);
1690 else if (colView.InternalItemSource.IsGroupHeader(index))
1692 return ((IsHorizontal? (int)groupHeaderSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1693 - groupHeaderMargin.Start - groupHeaderMargin.End,
1694 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)groupHeaderSize)
1695 - groupHeaderMargin.Top - groupHeaderMargin.Bottom);
1697 else if (colView.InternalItemSource.IsGroupFooter(index))
1699 return ((IsHorizontal? (int)groupFooterSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1700 - groupFooterMargin.Start - groupFooterMargin.End,
1701 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)groupFooterSize)
1702 - groupFooterMargin.Top - groupFooterMargin.Bottom);
1706 return ((IsHorizontal? (int)StepCandidate : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1707 - CandidateMargin.Start - CandidateMargin.End,
1708 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)StepCandidate)
1709 - CandidateMargin.Top - CandidateMargin.Bottom);
1713 private void DelayedRequestLayout(float scrollPosition , bool force = true)
1715 if (requestLayoutTimer != null)
1717 requestLayoutTimer.Dispose();
1720 requestLayoutTimer = new Timer(1);
1721 requestLayoutTimer.Interval = 1;
1722 requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) =>
1724 RequestLayout(scrollPosition, force);
1727 requestLayoutTimer.Start();
1731 private (int, float) FindTopItemInScreen()
1734 float offset = 0.0F, Pos, Size;
1736 foreach(RecyclerViewItem item in VisibleItems)
1738 Pos = IsHorizontal ? item.PositionX : item.PositionY;
1739 Size = IsHorizontal ? item.SizeWidth : item.SizeHeight;
1740 if (PrevScrollPosition >= Pos && PrevScrollPosition < Pos + Size)
1743 offset = Pos - PrevScrollPosition;
1748 return (index, offset);
1752 private float GetItemStepSize(int index)
1754 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1756 return ItemSize[index];
1760 if (colView.InternalItemSource.IsHeader(index))
1762 else if (colView.InternalItemSource.IsFooter(index))
1764 else if (colView.InternalItemSource.IsGroupHeader(index))
1765 return groupHeaderSize;
1766 else if (colView.InternalItemSource.IsGroupFooter(index))
1767 return groupFooterSize;
1769 return StepCandidate;
1773 private void UpdatePosition(int index)
1775 bool IsGroup = (colView.InternalItemSource is IGroupableItemSource);
1777 if (index <= 0) return;
1778 if (index >= colView.InternalItemSource.Count)
1782 //IsGroupHeader = (colView.InternalItemSource as IGroupableItemSource).IsGroupHeader(index);
1783 //IsGroupFooter = (colView.InternalItemSource as IGroupableItemSource).IsGroupFooter(index);
1786 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1787 ItemPosition[index] = ItemPosition[index - 1] + GetItemStepSize(index - 1);
1790 private RecyclerViewItem GetVisibleItem(int index)
1792 foreach (RecyclerViewItem item in VisibleItems)
1794 if (item.Index == index) return item;
1799 private GroupInfo GetGroupInfo(int index)
1801 if (Visited != null)
1803 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1806 if (hasHeader && index == 0) return null;
1807 foreach (GroupInfo group in groups)
1809 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1819 private float GetGroupPosition(GroupInfo groupInfo, int index)
1821 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1822 return groupInfo.GroupPosition + groupInfo.ItemPosition[index - groupInfo.StartIndex];
1825 float pos = groupInfo.GroupPosition;
1826 if (groupInfo.StartIndex == index) return pos;
1828 pos = pos + groupHeaderSize + StepCandidate * (index - groupInfo.StartIndex - 1);
1834 private object GetGroupParent(int index)
1836 if (Visited != null)
1838 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1839 return Visited.GroupParent;
1841 if (hasHeader && index == 0) return null;
1842 foreach (GroupInfo group in groups)
1844 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1847 return group.GroupParent;
1856 public object GroupParent;
1857 public int StartIndex;
1859 public float GroupSize;
1860 public float GroupPosition;
1861 //Items relative position from the GroupPosition. Only use for MeasureAll.
1862 public List<float> ItemPosition = new List<float>();