43f24ad1f279f12e46fc6ffca4de71a8e82d4366
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / RecyclerView / Layouter / GridLayouter.cs
1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
2  *
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
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
14  *
15  */
16 using System;
17 using System.Collections.Generic;
18 using System.ComponentModel;
19 using Tizen.NUI.BaseComponents;
20
21 namespace Tizen.NUI.Components
22 {
23     /// <summary>
24     /// Layouter for CollectionView to display items in grid layout.
25     /// </summary>
26     /// <since_tizen> 9 </since_tizen>
27     public class GridLayouter : ItemsLayouter
28     {
29         private CollectionView colView;
30         private (float Width, float Height) sizeCandidate;
31         private int spanSize = 1;
32         private float align = 0.5f;
33         private bool hasHeader;
34         private Extents headerMargin;
35         private float headerSize;
36         private Extents footerMargin;
37         private bool hasFooter;
38         private float footerSize;
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;
48
49         /// <summary>
50         /// Clean up ItemsLayouter.
51         /// </summary>
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)
56         {
57             colView = view as CollectionView;
58             if (colView == null)
59             {
60                 throw new ArgumentException("GridLayouter only can be applied CollectionView.", nameof(view));
61             }
62
63             // 1. Clean Up
64             foreach (RecyclerViewItem item in VisibleItems)
65             {
66                 colView.UnrealizeItem(item, false);
67             }
68             VisibleItems.Clear();
69             groups.Clear();
70
71             FirstVisible = 0;
72             LastVisible = 0;
73
74             IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
75
76             RecyclerViewItem header = colView?.Header;
77             RecyclerViewItem footer = colView?.Footer;
78             float width, height;
79             int count = colView.InternalItemSource.Count;
80             int pureCount = count - (header? 1 : 0) - (footer? 1 : 0);
81
82             // 2. Get the header / footer and size deligated item and measure the size.
83             if (header != null)
84             {
85                 MeasureChild(colView, header);
86
87                 width = header.Layout != null? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
88                 height = header.Layout != null? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
89
90                 Extents itemMargin = header.Margin;
91                 headerSize = IsHorizontal?
92                                 width + itemMargin.Start + itemMargin.End:
93                                 height + itemMargin.Top + itemMargin.Bottom;
94                 headerMargin = new Extents(itemMargin);
95                 hasHeader = true;
96
97                 colView.UnrealizeItem(header);
98             }
99
100             if (footer != null)
101             {
102                 MeasureChild(colView, footer);
103
104                 width = footer.Layout != null? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
105                 height = footer.Layout != null? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
106
107                 Extents itemMargin = footer.Margin;
108                 footerSize = IsHorizontal?
109                                 width + itemMargin.Start + itemMargin.End:
110                                 height + itemMargin.Top + itemMargin.Bottom;
111                 footerMargin = new Extents(itemMargin);
112                 footer.Index = count - 1;
113                 hasFooter = true;
114
115                 colView.UnrealizeItem(footer);
116             }
117
118             if (pureCount == 0)
119             {
120                 isSourceEmpty = true;
121                 base.Initialize(colView);
122                 return;
123             }
124             isSourceEmpty = false;
125
126             int firstIndex = header? 1 : 0;
127
128             if (colView.IsGrouped)
129             {
130                 isGrouped = true;
131
132                 if (colView.GroupHeaderTemplate != null)
133                 {
134                     while (!colView.InternalItemSource.IsGroupHeader(firstIndex)) firstIndex++;
135                     //must be always true
136                     if (colView.InternalItemSource.IsGroupHeader(firstIndex))
137                     {
138                         RecyclerViewItem groupHeader = colView.RealizeItem(firstIndex);
139                         firstIndex++;
140
141                         if (groupHeader == null) throw new Exception("[" + firstIndex + "] Group Header failed to realize!");
142
143                         // Need to Set proper height or width on scroll direction.
144                         if (groupHeader.Layout == null)
145                         {
146                             width = groupHeader.WidthSpecification;
147                             height = groupHeader.HeightSpecification;
148                         }
149                         else
150                         {
151                             MeasureChild(colView, groupHeader);
152
153                             width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
154                             height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
155                         }
156                         //Console.WriteLine("[NUI] GroupHeader Size {0} :{0}", width, height);
157                         // pick the StepCandidate.
158                         Extents itemMargin = groupHeader.Margin;
159                         groupHeaderSize = IsHorizontal?
160                                             width + itemMargin.Start + itemMargin.End:
161                                             height + itemMargin.Top + itemMargin.Bottom;
162                         groupHeaderMargin = new Extents(itemMargin);
163                         colView.UnrealizeItem(groupHeader);
164                     }
165                 }
166                 else
167                 {
168                     groupHeaderSize = 0F;
169                 }
170
171                 if (colView.GroupFooterTemplate != null)
172                 {
173                     int firstFooter = firstIndex;
174                     while (!colView.InternalItemSource.IsGroupFooter(firstFooter)) firstFooter++;
175                     //must be always true
176                     if (colView.InternalItemSource.IsGroupFooter(firstFooter))
177                     {
178                         RecyclerViewItem groupFooter = colView.RealizeItem(firstFooter);
179
180                         if (groupFooter == null) throw new Exception("[" + firstFooter + "] Group Footer failed to realize!");
181                         // Need to Set proper height or width on scroll direction.
182                         if (groupFooter.Layout == null)
183                         {
184                             width = groupFooter.WidthSpecification;
185                             height = groupFooter.HeightSpecification;
186                         }
187                         else
188                         {
189                             MeasureChild(colView, groupFooter);
190
191                             width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
192                             height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
193                         }
194                         // pick the StepCandidate.
195                         Extents itemMargin = groupFooter.Margin;
196                         groupFooterSize = IsHorizontal?
197                                             width + itemMargin.Start + itemMargin.End:
198                                             height + itemMargin.Top + itemMargin.Bottom;
199                         groupFooterMargin = new Extents(itemMargin);
200                     
201                         colView.UnrealizeItem(groupFooter);
202                     }
203                 }
204                 else
205                 {
206                     groupFooterSize = 0F;
207                 }
208             }
209             else isGrouped = false;
210
211             bool failed = false;
212             //Final Check of FirstIndex
213             while (colView.InternalItemSource.IsHeader(firstIndex) ||
214                     colView.InternalItemSource.IsGroupHeader(firstIndex) ||
215                     colView.InternalItemSource.IsGroupFooter(firstIndex))
216             {
217                 if (colView.InternalItemSource.IsFooter(firstIndex))
218                 {
219                     StepCandidate = 0F;
220                     failed = true;
221                     break;
222                 }
223                 firstIndex++;
224             }
225
226             sizeCandidate = (0, 0);
227             if (!failed)
228             {
229                 // Get Size Deligate. FIXME if group exist index must be changed.
230                 RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
231                 if (sizeDeligate == null)
232                 {
233                     throw new Exception("Cannot create content from DatTemplate.");
234                 }
235                 sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
236
237                 // Need to Set proper height or width on scroll direction.
238                 if (sizeDeligate.Layout == null)
239                 {
240                     width = sizeDeligate.WidthSpecification;
241                     height = sizeDeligate.HeightSpecification;
242                 }
243                 else
244                 {
245                     MeasureChild(colView, sizeDeligate);
246
247                     width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
248                     height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
249                 }
250                 //Console.WriteLine("[NUI] item Size {0} :{1}", width, height);
251
252                 // pick the StepCandidate.
253                 Extents itemMargin = sizeDeligate.Margin;
254                 width = width + itemMargin.Start + itemMargin.End;
255                 height = height + itemMargin.Top + itemMargin.Bottom;
256                 StepCandidate = IsHorizontal? width : height;
257                 CandidateMargin = new Extents(itemMargin);
258
259                 // Prevent zero division.
260                 if (width == 0) width = 1;
261                 if (height == 0) height = 1;
262                 spanSize = IsHorizontal?
263                             Convert.ToInt32(Math.Truncate((double)((colView.Size.Height - Padding.Top - Padding.Bottom) / height))) :
264                             Convert.ToInt32(Math.Truncate((double)((colView.Size.Width - Padding.Start - Padding.End) / width)));
265
266                 sizeCandidate = (width, height);
267
268                 colView.UnrealizeItem(sizeDeligate);
269             }
270
271             if (StepCandidate < 1) StepCandidate = 1;
272             if (spanSize < 1) spanSize = 1;
273
274             if (isGrouped)
275             {
276                 float Current = 0.0F;
277                 IGroupableItemSource source = colView.InternalItemSource;
278                 GroupInfo currentGroup = null;
279                 object currentParent = null;
280
281                 for (int i = 0; i < count; i++)
282                 {
283                     if (i == 0 && hasHeader)
284                     {
285                         Current += headerSize;
286                     }
287                     else if (i == count - 1 && hasFooter)
288                     {
289                         Current += footerSize;
290                     }
291                     else
292                     {
293                         if (source.GetGroupParent(i) != currentParent)
294                         {
295                             currentParent = source.GetGroupParent(i);
296                             float currentSize = (source.IsGroupHeader(i)? groupHeaderSize :
297                                                     (source.IsGroupFooter(i)? groupFooterSize: StepCandidate));
298                             currentGroup = new GroupInfo()
299                             {
300                                 GroupParent = source.GetGroupParent(i),
301                                 StartIndex = i,
302                                 Count = 1,
303                                 GroupSize = currentSize,
304                                 GroupPosition = Current
305                             };
306                             groups.Add(currentGroup);
307                             Current += currentSize;
308                         }
309                         //optional
310                         else if (source.IsGroupFooter(i))
311                         {
312                             //currentGroup.hasFooter = true;
313                             if (currentGroup != null)
314                             {
315                                 currentGroup.Count++;
316                                 currentGroup.GroupSize += groupFooterSize;
317                                 Current += groupFooterSize;
318                             }
319                         }
320                         else
321                         {
322                             if (currentGroup != null)
323                             {
324                                 currentGroup.Count++;
325                                 int index = i - currentGroup.StartIndex - ((colView.GroupHeaderTemplate != null) ? 1 : 0);
326                                 if ((index % spanSize) == 0)
327                                 {
328                                     currentGroup.GroupSize += StepCandidate;
329                                     Current += StepCandidate;
330                                 }
331                             }
332                         }
333                     }
334                 }
335                 ScrollContentSize = Current;
336             }
337             else
338             {
339                 // 3. Measure the scroller content size.
340                 ScrollContentSize = StepCandidate * Convert.ToInt32(Math.Ceiling((double)pureCount / (double)spanSize));
341                 if (hasHeader) ScrollContentSize += headerSize;
342                 if (hasFooter) ScrollContentSize += footerSize;
343             }
344
345             ScrollContentSize = IsHorizontal?
346                                 ScrollContentSize + Padding.Start + Padding.End:
347                                 ScrollContentSize + Padding.Top + Padding.Bottom;
348
349             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
350             else colView.ContentContainer.SizeHeight = ScrollContentSize;
351
352             base.Initialize(colView);
353             //Console.WriteLine("Init Done, StepCnadidate{0}, spanSize{1}, Scroll{2}", StepCandidate, spanSize, ScrollContentSize);
354         }
355
356         /// <summary>
357         /// This is called to find out where items are lain out according to current scroll position.
358         /// </summary>
359         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
360         /// <param name="force">boolean force flag to layouting forcely.</param>
361         /// <since_tizen> 9 </since_tizen>
362         public override void RequestLayout(float scrollPosition, bool force = false)
363         {
364             // Layouting is only possible after once it intialized.
365             if (!IsInitialized) return;
366             int LastIndex = colView.InternalItemSource.Count;
367
368             if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
369             PrevScrollPosition = Math.Abs(scrollPosition);
370
371             int prevFirstVisible = FirstVisible;
372             int prevLastVisible = LastVisible;
373             bool IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
374
375             (float X, float Y) visibleArea = (PrevScrollPosition,
376                 PrevScrollPosition + (IsHorizontal? colView.Size.Width : colView.Size.Height)
377             );
378
379             //Console.WriteLine("[NUI] itemsView [{0},{1}] [{2},{3}]", colView.Size.Width, colView.Size.Height, colView.ContentContainer.Size.Width, colView.ContentContainer.Size.Height);
380
381             // 1. Set First/Last Visible Item Index. 
382             (int start, int end) = FindVisibleItems(visibleArea);
383             FirstVisible = start;
384             LastVisible = end;
385
386             //Console.WriteLine("[NUI] {0} :visibleArea before [{1},{2}] after [{3},{4}]", scrollPosition, prevFirstVisible, prevLastVisible, FirstVisible, LastVisible);
387
388             // 2. Unrealize invisible items.
389             List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
390             foreach (RecyclerViewItem item in VisibleItems)
391             {
392                 if (item.Index < FirstVisible || item.Index > LastVisible)
393                 {
394                     //Console.WriteLine("[NUI] Unrealize{0}!", item.Index);
395                     unrealizedItems.Add(item);
396                     colView.UnrealizeItem(item);
397                 }
398             }
399             VisibleItems.RemoveAll(unrealizedItems.Contains);
400
401             //Console.WriteLine("Realize Begin [{0} to {1}]", FirstVisible, LastVisible);
402             // 3. Realize and placing visible items.
403             for (int i = FirstVisible; i <= LastVisible; i++)
404             {
405                 //Console.WriteLine("[NUI] Realize!");
406                 RecyclerViewItem item = null;
407                 // 4. Get item if visible or realize new.
408                 if (i >= prevFirstVisible && i <= prevLastVisible)
409                 {
410                     item = GetVisibleItem(i);
411                     if (item != null && !force) continue;
412                 }
413                 if (item == null)
414                 {
415                     item = colView.RealizeItem(i);
416                     if (item != null) VisibleItems.Add(item);
417                     else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]");
418                 }
419
420                 //item Position without Padding and Margin.
421                 (float x, float y) = GetItemPosition(i);
422                 // 5. Placing item with Padding and Margin.
423                 item.Position = new Position(x, y);
424
425                 //Linear Item need to be resized!
426                 if (item.IsHeader || item.IsFooter || item.isGroupHeader || item.isGroupFooter)
427                 {
428                     var size = (IsHorizontal? item.SizeWidth: item.SizeHeight);
429                     if (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst)
430                     {
431                         if (item.IsHeader) size = headerSize;
432                         else if (item.IsFooter) size = footerSize;
433                         else if (item.isGroupHeader) size = groupHeaderSize;
434                         else if (item.isGroupFooter) size = groupFooterSize;
435                     }
436                     if (IsHorizontal && item.HeightSpecification == LayoutParamPolicies.MatchParent)
437                     {
438                         item.Size = new Size(size, Container.Size.Height - Padding.Top - Padding.Bottom - item.Margin.Top - item.Margin.Bottom);
439                     }
440                     else if (!IsHorizontal && item.WidthSpecification == LayoutParamPolicies.MatchParent)
441                     {
442                         item.Size = new Size(Container.Size.Width - Padding.Start - Padding.End - item.Margin.Start - item.Margin.End, size);
443                     }
444                 }
445                 //Console.WriteLine("[NUI] ["+item.Index+"] ["+item.Position.X+", "+item.Position.Y+" ==== \n");
446             }
447             //Console.WriteLine("Realize Done");
448         }
449
450         /// <summary>
451         /// Clear the current screen and all properties.
452         /// </summary>
453         [EditorBrowsable(EditorBrowsableState.Never)]
454         public override void Clear()
455         {
456             // Clean Up
457             if (requestLayoutTimer != null)
458             {
459                 requestLayoutTimer.Dispose();
460             }
461             if (groups != null)
462             {
463                  /*
464                 foreach (GroupInfo group in groups)
465                 {
466                     //group.ItemPosition?.Clear();
467                     // if Disposable?
468                     //group.Dispose();
469                 }
470                 */
471                 groups.Clear();
472             }
473             if (headerMargin != null)
474             {
475                 headerMargin.Dispose();
476                 headerMargin = null;
477             }
478             if (footerMargin != null)
479             {
480                 footerMargin.Dispose();
481                 footerMargin = null;
482             }
483             if (groupHeaderMargin != null)
484             {
485                 groupHeaderMargin.Dispose();
486                 groupHeaderMargin = null;
487             }
488             if (groupFooterMargin != null)
489             {
490                 groupFooterMargin.Dispose();
491                 groupFooterMargin = null;
492             }
493
494             base.Clear();
495         }
496
497         /// <inheritdoc/>
498         public override void NotifyItemSizeChanged(RecyclerViewItem item)
499         {
500             // All Item size need to be same in grid!
501             // if you want to change item size, change dataTemplate to re-initing.
502             return;
503         }
504
505         /// <Inheritdoc/>
506         [EditorBrowsable(EditorBrowsableState.Never)]
507         public override void NotifyItemInserted(IItemSource source, int startIndex)
508         {
509             // Insert Single item.
510             if (source == null) throw new ArgumentNullException(nameof(source));
511             if (colView == null) return;
512             if (isSourceEmpty)
513             {
514                 Initialize(colView);
515             }
516
517             // Will be null if not a group.
518             float currentSize = 0;
519             IGroupableItemSource gSource = source as IGroupableItemSource;
520
521             // Get the first Visible Position to adjust.
522             /*
523             int topInScreenIndex = 0;
524             float offset = 0F;
525             (topInScreenIndex, offset) = FindTopItemInScreen();
526             */
527
528             //2. Handle Group Case.
529             if (isGrouped && gSource != null)
530             {
531                 GroupInfo groupInfo = null;
532                 object groupParent = gSource.GetGroupParent(startIndex);
533                 int parentIndex = gSource.GetPosition(groupParent);
534                 if (gSource.HasHeader) parentIndex--;
535
536                 // Check item is group parent or not
537                 // if group parent, add new gorupinfo
538                 if (gSource.IsHeader(startIndex))
539                 {
540                     // This is childless group.
541                     // create new groupInfo!
542                     groupInfo = new GroupInfo()
543                     {
544                         GroupParent = groupParent,
545                         StartIndex = startIndex,
546                         Count = 1,
547                         GroupSize = groupHeaderSize,
548                     };
549
550                     if (parentIndex >= groups.Count)
551                     {
552                         groupInfo.GroupPosition = ScrollContentSize;
553                         groups.Add(groupInfo);
554                     }
555                     else
556                     {
557                         groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
558                         groups.Insert(parentIndex, groupInfo);
559                     }
560
561                     currentSize = groupHeaderSize;
562                 }
563                 else
564                 {
565                     // If not group parent, add item into the groupinfo.
566                     if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts.");
567                     groupInfo = groups[parentIndex];//GetGroupInfo(groupParent);
568                     if (groupInfo == null) throw new Exception("Cannot find group information!");
569
570                     if (gSource.IsGroupFooter(startIndex))
571                     {
572                         // It doesn't make sence to adding footer by notify...
573                         // if GroupFooterTemplate is added,
574                         // need to implement on here.
575                     }
576                     else
577                     {
578                         if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
579                         {
580                             // Wrong! Grid Layouter do not support MeasureAll!
581                         }
582                         else
583                         {
584                             int pureCount = groupInfo.Count - 1 - (colView.GroupFooterTemplate == null? 0: 1);
585                             if (pureCount % spanSize == 0)
586                             {
587                                 currentSize = StepCandidate;
588                                 groupInfo.GroupSize += currentSize;
589                             }
590
591                         }
592                     }
593                     groupInfo.Count++;
594
595                 }
596
597                 if (parentIndex + 1 < groups.Count)
598                 {
599                     for(int i = parentIndex + 1; i < groups.Count; i++)
600                     {
601                         groups[i].GroupPosition += currentSize;
602                         groups[i].StartIndex++;
603                     }
604                 }
605             }
606             else
607             {
608                 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
609                 {
610                     // Wrong! Grid Layouter do not support MeasureAll!
611                 }
612                 int pureCount = colView.InternalItemSource.Count - (hasHeader? 1: 0) - (hasFooter? 1: 0);
613
614                 // Count comes after updated in ungrouped case!
615                 if (pureCount % spanSize == 1)
616                 {
617                     currentSize = StepCandidate;
618                 }
619             }
620
621             // 3. Update Scroll Content Size
622             ScrollContentSize += currentSize;
623
624             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
625             else colView.ContentContainer.SizeHeight = ScrollContentSize;
626
627             // 4. Update Visible Items.
628             foreach (RecyclerViewItem item in VisibleItems)
629             {
630                 if (item.Index >= startIndex)
631                 {
632                     item.Index++;
633                 }
634             }
635
636             float scrollPosition = PrevScrollPosition;
637
638             /*
639             // Position Adjust
640             // Insertion above Top Visible!
641             if (startIndex <= topInScreenIndex)
642             {
643                 scrollPosition = GetItemPosition(topInScreenIndex);
644                 scrollPosition -= offset;
645
646                 colView.ScrollTo(scrollPosition);
647             }
648             */
649
650             // Update Viewport in delay.
651             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
652             // but currently we do not have any accessor to pre-calculation so instead of this,
653             // using Timer temporarily.
654             DelayedRequestLayout(scrollPosition);
655         }
656
657         /// <Inheritdoc/>
658         [EditorBrowsable(EditorBrowsableState.Never)]
659         public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
660         {
661              // Insert Group
662             if (source == null) throw new ArgumentNullException(nameof(source));
663             if (colView == null) return;
664             if (isSourceEmpty)
665             {
666                 Initialize(colView);
667             }
668
669             float currentSize = StepCandidate;
670             // Will be null if not a group.
671             IGroupableItemSource gSource = source as IGroupableItemSource;
672
673             // Get the first Visible Position to adjust.
674             /*
675             int topInScreenIndex = 0;
676             float offset = 0F;
677             (topInScreenIndex, offset) = FindTopItemInScreen();
678             */
679
680             // 2. Handle Group Case
681             // Adding ranged items should all same new groups.
682             if (isGrouped && gSource != null)
683             {
684                 GroupInfo groupInfo = null;
685                 object groupParent = gSource.GetGroupParent(startIndex);
686                 int parentIndex = gSource.GetPosition(groupParent);
687                 if (gSource.HasHeader) parentIndex--;
688
689                 // We guess here that range inserted from GroupStartIndex.
690                 int groupStartIndex = startIndex;
691
692                 for (int current = startIndex; current - startIndex < count; current++)
693                 {
694                     // Check item is group parent or not
695                     // if group parent, add new gorupinfo
696                     if (groupStartIndex == current)
697                     {
698                         //create new groupInfo!
699                         currentSize = (gSource.IsGroupHeader(current)? groupHeaderSize :
700                                             (gSource.IsGroupFooter(current)? groupFooterSize: currentSize));
701                         groupInfo = new GroupInfo()
702                         {
703                             GroupParent = groupParent,
704                             StartIndex = current,
705                             Count = 1,
706                             GroupSize = StepCandidate,
707                         };
708                         currentSize += StepCandidate;
709
710                     }
711                     else
712                     {
713                         //if not group parent, add item into the groupinfo.
714                         //groupInfo = GetGroupInfo(groupStartIndex);
715                         if (groupInfo == null) throw new Exception("Cannot find group information!");
716                         groupInfo.Count++;
717
718                         if (gSource.IsGroupFooter(current))
719                         {
720                                 groupInfo.GroupSize += groupFooterSize;
721                                 currentSize += groupFooterSize;
722                         }
723                         else
724                         {
725                             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
726                             {
727                                 // Wrong! Grid Layouter do not support MeasureAll!
728                             }
729                             else
730                             {
731                                 int index = current - groupStartIndex - ((colView.GroupHeaderTemplate != null)? 1: 0);
732                                 if ((index % spanSize) == 0)
733                                 {
734                                     groupInfo.GroupSize += StepCandidate;
735                                     currentSize += StepCandidate;
736                                 }
737                             }
738                         }
739                     }
740                 }
741
742                 if (parentIndex >= groups.Count)
743                 {
744                     groupInfo.GroupPosition = ScrollContentSize;
745                     groups.Add(groupInfo);
746                 }
747                 else
748                 {
749                     groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
750                     groups.Insert(parentIndex, groupInfo);
751                 }
752
753                 // Update other below group's position
754                 if (parentIndex + 1 < groups.Count)
755                 {
756                     for(int i = parentIndex + 1; i < groups.Count; i++)
757                     {
758                         groups[i].GroupPosition += currentSize;
759                         groups[i].StartIndex += count;
760                     }
761                 }
762
763                 ScrollContentSize += currentSize;
764             }
765             else
766             {
767                 throw new Exception("Cannot insert ungrouped range items!");
768             }
769
770             // 3. Update Scroll Content Size
771             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
772             else colView.ContentContainer.SizeHeight = ScrollContentSize;
773
774             // 4. Update Visible Items.
775             foreach (RecyclerViewItem item in VisibleItems)
776             {
777                 if (item.Index >= startIndex)
778                 {
779                     item.Index += count;
780                 }
781             }
782
783             // Position Adjust
784             float scrollPosition = PrevScrollPosition;
785             /*
786             // Insertion above Top Visible!
787             if (startIndex + count <= topInScreenIndex)
788             {
789                 scrollPosition = GetItemPosition(topInScreenIndex);
790                 scrollPosition -= offset;
791
792                 colView.ScrollTo(scrollPosition);
793             }
794             */
795
796             // Update Viewport in delay.
797             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
798             // but currently we do not have any accessor to pre-calculation so instead of this,
799             // using Timer temporarily.
800             DelayedRequestLayout(scrollPosition);
801         }
802
803         /// <Inheritdoc/>
804         [EditorBrowsable(EditorBrowsableState.Never)]
805         public override void NotifyItemRemoved(IItemSource source, int startIndex)
806         {
807             // Remove Single
808             if (source == null) throw new ArgumentNullException(nameof(source));
809             if (colView == null) return;
810
811             // Will be null if not a group.
812             float currentSize = 0;
813             IGroupableItemSource gSource = source as IGroupableItemSource;
814
815             // Get the first Visible Position to adjust.
816             /*
817             int topInScreenIndex = 0;
818             float offset = 0F;
819             (topInScreenIndex, offset) = FindTopItemInScreen();
820             */
821
822             // 2. Handle Group Case
823             if (isGrouped && gSource != null)
824             {
825                 int parentIndex = 0;
826                 GroupInfo groupInfo = null;
827                 foreach(GroupInfo cur in groups)
828                 {
829                     if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex))
830                     {
831                         groupInfo = cur;
832                         break;
833                     }
834                     parentIndex++;
835                 }
836                 if (groupInfo == null) throw new Exception("Cannot find group information!");
837                 // Check item is group parent or not
838                 // if group parent, add new gorupinfo
839                 if (groupInfo.StartIndex == startIndex)
840                 {
841                     // This is empty group!
842                     // check group is empty.
843                     if (groupInfo.Count != 1)
844                     {
845                         throw new Exception("Cannot remove group parent");
846                     }
847                     currentSize = groupInfo.GroupSize;
848
849                     // Remove Group
850                     // groupInfo.Dispose();
851                     groups.Remove(groupInfo);
852                 }
853                 else
854                 {
855                     groupInfo.Count--;
856
857                     // Skip footer case as footer cannot exist alone without header.
858                     if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
859                     {
860                         // Wrong! Grid Layouter do not support MeasureAll!
861                     }
862                     else
863                     {
864                         int pureCount = groupInfo.Count - 1 - (colView.GroupFooterTemplate == null? 0: 1);
865                         if (pureCount % spanSize == 0)
866                         {
867                                 currentSize = StepCandidate;
868                                 groupInfo.GroupSize -= currentSize;
869                         }
870                     }
871                 }
872
873                 for (int i = parentIndex + 1; i < groups.Count; i++)
874                 {
875                     groups[i].GroupPosition -= currentSize;
876                     groups[i].StartIndex--;
877                 }
878             }
879             else
880             {
881                 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
882                 {
883                     // Wrong! Grid Layouter do not support MeasureAll!
884                 }
885                 int pureCount = colView.InternalItemSource.Count - (hasHeader? 1: 0) - (hasFooter? 1: 0);
886
887                 // Count comes after updated in ungrouped case!
888                 if (pureCount % spanSize == 0)
889                 {
890                     currentSize = StepCandidate;
891                 }
892                 
893             }
894
895             ScrollContentSize -= currentSize;
896
897             // 3. Update Scroll Content Size
898             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
899             else colView.ContentContainer.SizeHeight = ScrollContentSize;
900
901             // 4. Update Visible Items.
902             RecyclerViewItem targetItem = null;
903             foreach (RecyclerViewItem item in VisibleItems)
904             {
905                 if (item.Index == startIndex)
906                 {
907                     targetItem = item;
908                     colView.UnrealizeItem(item);
909                 }
910                 else if (item.Index > startIndex)
911                 {
912                     item.Index--;
913                 }
914             }
915             VisibleItems.Remove(targetItem);
916
917             // Position Adjust
918             float scrollPosition = PrevScrollPosition;
919             /*
920             // Insertion above Top Visible!
921             if (startIndex <= topInScreenIndex)
922             {
923                 scrollPosition = GetItemPosition(topInScreenIndex);
924                 scrollPosition -= offset;
925
926                 colView.ScrollTo(scrollPosition);
927             }
928             */
929
930             // Update Viewport in delay.
931             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
932             // but currently we do not have any accessor to pre-calculation so instead of this,
933             // using Timer temporarily.
934             DelayedRequestLayout(scrollPosition);
935         }
936
937         /// <Inheritdoc/>
938         [EditorBrowsable(EditorBrowsableState.Never)]
939         public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
940         {
941             // Remove Group
942             if (source == null) throw new ArgumentNullException(nameof(source));
943             if (colView == null) return;
944
945             // Will be null if not a group.
946             float currentSize = StepCandidate;
947             IGroupableItemSource gSource = source as IGroupableItemSource;
948
949             // Get the first Visible Position to adjust.
950             /*
951             int topInScreenIndex = 0;
952             float offset = 0F;
953             (topInScreenIndex, offset) = FindTopItemInScreen();
954             */
955
956             // 1. Handle Group Case
957             if (isGrouped && gSource != null)
958             {
959                 int parentIndex = 0;
960                 GroupInfo groupInfo = null;
961                 foreach(GroupInfo cur in groups)
962                 {
963                     if ((cur.StartIndex == startIndex) && (cur.Count == count))
964                     {
965                         groupInfo = cur;
966                         break;
967                     }
968                     parentIndex++;
969                 }
970                 if (groupInfo == null) throw new Exception("Cannot find group information!");
971                 // Check item is group parent or not
972                 // if group parent, add new gorupinfo
973                 currentSize = groupInfo.GroupSize;
974                 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
975                 {
976                     // Wrong! Grid Layouter do not support MeasureAll!
977                 }
978                 // Remove Group
979                 // groupInfo.Dispose();
980                 groups.Remove(groupInfo);
981
982                 for (int i = parentIndex; i < groups.Count; i++)
983                 {
984                     groups[i].GroupPosition -= currentSize;
985                     groups[i].StartIndex -= count;
986                 }
987             }
988             else
989             {
990                 // It must group case! throw exception!
991                 throw new Exception("Range remove must group remove!");
992             }
993
994             ScrollContentSize -= currentSize;
995
996             // 2. Update Scroll Content Size
997             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
998             else colView.ContentContainer.SizeHeight = ScrollContentSize;
999
1000             // 3. Update Visible Items.
1001             List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
1002             foreach (RecyclerViewItem item in VisibleItems)
1003             {
1004                 if ((item.Index >= startIndex)
1005                     && (item.Index < startIndex + count))
1006                 {
1007                     unrealizedItems.Add(item);
1008                     colView.UnrealizeItem(item);
1009                 }
1010                 else if (item.Index >= startIndex + count)
1011                 {
1012                     item.Index -= count;
1013                 }
1014             }
1015             VisibleItems.RemoveAll(unrealizedItems.Contains);
1016             unrealizedItems.Clear();
1017
1018             // Position Adjust
1019             float scrollPosition = PrevScrollPosition;
1020             /*
1021             // Insertion above Top Visible!
1022             if (startIndex <= topInScreenIndex)
1023             {
1024                 scrollPosition = GetItemPosition(topInScreenIndex);
1025                 scrollPosition -= offset;
1026
1027                 colView.ScrollTo(scrollPosition);
1028             }
1029             */
1030
1031             // Update Viewport in delay.
1032             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1033             // but currently we do not have any accessor to pre-calculation so instead of this,
1034             // using Timer temporarily.
1035             DelayedRequestLayout(scrollPosition);
1036         }
1037
1038         /// <Inheritdoc/>
1039         [EditorBrowsable(EditorBrowsableState.Never)]
1040         public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
1041         {
1042             // Reorder Single
1043             if (source == null) throw new ArgumentNullException(nameof(source));
1044             if (colView == null) return;
1045
1046             // Will be null if not a group.
1047             float currentSize = StepCandidate;
1048             int diff = toPosition - fromPosition;
1049
1050             // Get the first Visible Position to adjust.
1051             /*
1052             int topInScreenIndex = 0;
1053             float offset = 0F;
1054             (topInScreenIndex, offset) = FindTopItemInScreen();
1055             */
1056             
1057             // Move can only happen in it's own groups.
1058             // so there will be no changes in position, startIndex in ohter groups.
1059             // check visible item and update indexs.
1060             int startIndex = ( diff > 0 ? fromPosition: toPosition);
1061             int endIndex = (diff > 0 ? toPosition: fromPosition);
1062
1063             if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1064             {
1065                 foreach (RecyclerViewItem item in VisibleItems)
1066                 {
1067                     if ((item.Index >= startIndex)
1068                         && (item.Index <= endIndex))
1069                     {
1070                         if (item.Index == fromPosition) item.Index = toPosition;
1071                         else
1072                         {
1073                             if (diff > 0) item.Index--;
1074                             else item.Index++;
1075                         }
1076                     }
1077                 }
1078             }
1079
1080             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
1081             else colView.ContentContainer.SizeHeight = ScrollContentSize;
1082
1083             // Position Adjust
1084             float scrollPosition = PrevScrollPosition;
1085             /*
1086             // Insertion above Top Visible!
1087             if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1088                 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1089             {
1090                 scrollPosition = GetItemPosition(topInScreenIndex);
1091                 scrollPosition -= offset;
1092
1093                 colView.ScrollTo(scrollPosition);
1094             }
1095             */
1096
1097             // Update Viewport in delay.
1098             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1099             // but currently we do not have any accessor to pre-calculation so instead of this,
1100             // using Timer temporarily.
1101             DelayedRequestLayout(scrollPosition);
1102         }
1103
1104         /// <Inheritdoc/>
1105         [EditorBrowsable(EditorBrowsableState.Never)]
1106         public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
1107         {
1108             // Reorder Groups
1109             if (source == null) throw new ArgumentNullException(nameof(source));
1110             if (colView == null) return;
1111
1112             // Will be null if not a group.
1113             float currentSize = StepCandidate;
1114             int diff = toPosition - fromPosition;
1115
1116             int startIndex = ( diff > 0 ? fromPosition: toPosition);
1117             int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1);
1118
1119             // 2. Handle Group Case
1120             if (isGrouped)
1121             {
1122                 int fromParentIndex = 0;
1123                 int toParentIndex = 0;
1124                 bool findFrom = false;
1125                 bool findTo = false;
1126                 GroupInfo fromGroup = null;
1127                 GroupInfo toGroup = null;
1128
1129                 foreach(GroupInfo cur in groups)
1130                 {
1131                     if ((cur.StartIndex == fromPosition) && (cur.Count == count))
1132                     {
1133                         fromGroup = cur;
1134                         findFrom = true;
1135                         if (findFrom && findTo) break;
1136                     }
1137                     else if (cur.StartIndex == toPosition)
1138                     {
1139                         toGroup = cur;
1140                         findTo = true;
1141                         if (findFrom && findTo) break;
1142                     }
1143                     if (!findFrom) fromParentIndex++;
1144                     if (!findTo) toParentIndex++;
1145                 }
1146                 if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!");
1147
1148                 fromGroup.StartIndex = toGroup.StartIndex;
1149                 fromGroup.GroupPosition = toGroup.GroupPosition;
1150
1151                 endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1);
1152
1153                 groups.Remove(fromGroup);
1154                 groups.Insert(toParentIndex, fromGroup);
1155
1156                 int startGroup = (diff > 0? fromParentIndex: toParentIndex);
1157                 int endGroup =  (diff > 0? toParentIndex: fromParentIndex);
1158                 
1159                 for (int i = startGroup; i <= endGroup; i++)
1160                 {
1161                     if (i == toParentIndex) continue;
1162                     float prevPos = groups[i].GroupPosition;
1163                     int prevIdx = groups[i].StartIndex;
1164                     groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize;
1165                     groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count;
1166                 }
1167             }
1168             else
1169             {
1170                 //It must group case! throw exception!
1171                 throw new Exception("Range remove must group remove!");
1172             }
1173
1174             // Move can only happen in it's own groups.
1175             // so there will be no changes in position, startIndex in ohter groups.
1176             // check visible item and update indexs.
1177             if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1178             {
1179                 foreach (RecyclerViewItem item in VisibleItems)
1180                 {
1181                     if ((item.Index >= startIndex)
1182                         && (item.Index <= endIndex))
1183                     {
1184                         if ((item.Index >= fromPosition) && (item.Index < fromPosition + count))
1185                         {
1186                             item.Index = fromPosition - item.Index + toPosition;
1187                         }
1188                         else
1189                         {
1190                             if (diff > 0) item.Index -= count;
1191                             else item.Index += count;
1192                         }
1193                     }
1194                 }
1195             }
1196
1197             // Position Adjust
1198             float scrollPosition = PrevScrollPosition;
1199             /*
1200             // Insertion above Top Visible!
1201             if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1202                 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1203             {
1204                 scrollPosition = GetItemPosition(topInScreenIndex);
1205                 scrollPosition -= offset;
1206
1207                 colView.ScrollTo(scrollPosition);
1208             }
1209             */
1210
1211             // Update Viewport in delay.
1212             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1213             // but currently we do not have any accessor to pre-calculation so instead of this,
1214             // using Timer temporarily.
1215             DelayedRequestLayout(scrollPosition);
1216         }
1217
1218         /// <Inheritdoc/>
1219         [EditorBrowsable(EditorBrowsableState.Never)]
1220         public override float CalculateLayoutOrientationSize()
1221         {
1222             //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
1223             return ScrollContentSize;
1224         }
1225
1226         /// <Inheritdoc/>
1227         [EditorBrowsable(EditorBrowsableState.Never)]
1228         public override float CalculateCandidateScrollPosition(float scrollPosition)
1229         {
1230             //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
1231             return scrollPosition;
1232         }
1233
1234         /// <Inheritdoc/>
1235         [EditorBrowsable(EditorBrowsableState.Never)]
1236         public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
1237         {
1238             if (currentFocusedView == null)
1239                 throw new ArgumentNullException(nameof(currentFocusedView));
1240
1241             View nextFocusedView = null;
1242             int targetSibling = -1;
1243             bool IsHorizontal = colView.ScrollingDirection == ScrollableBase.Direction.Horizontal;
1244
1245             switch (direction)
1246             {
1247                 case View.FocusDirection.Left:
1248                     {
1249                         targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder - 1 : targetSibling;
1250                         break;
1251                     }
1252                 case View.FocusDirection.Right:
1253                     {
1254                         targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder + 1 : targetSibling;
1255                         break;
1256                     }
1257                 case View.FocusDirection.Up:
1258                     {
1259                         targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder - 1;
1260                         break;
1261                     }
1262                 case View.FocusDirection.Down:
1263                     {
1264                         targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder + 1;
1265                         break;
1266                     }
1267             }
1268
1269             if (targetSibling > -1 && targetSibling < Container.Children.Count)
1270             {
1271                 RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
1272                 if (candidate != null && candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
1273                 {
1274                     nextFocusedView = candidate;
1275                 }
1276             }
1277             return nextFocusedView;
1278         }
1279
1280         /// <inheritdoc/>
1281         [EditorBrowsable(EditorBrowsableState.Never)]
1282         protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
1283         {
1284             int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter ? 1 : 0);
1285             int adds = spanSize * 2;
1286             int skipGroup = -1;
1287             (int start, int end) found = (0, 0);
1288
1289             // Header is Showing
1290             if (hasHeader && visibleArea.X < headerSize + (IsHorizontal? Padding.Start : Padding.Top))
1291             {
1292                 found.start = 0;
1293             }
1294             else
1295             {
1296                 if (isGrouped)
1297                 {
1298                     bool failed = true;
1299                     foreach (GroupInfo gInfo in groups)
1300                     {
1301                         skipGroup++;
1302                         // in the Group
1303                         if (gInfo.GroupPosition <= visibleArea.X &&
1304                             gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
1305                         {
1306                             if (gInfo.GroupPosition + groupHeaderSize >= visibleArea.X)
1307                             {
1308                                 found.start = gInfo.StartIndex - adds;
1309                                 failed = false;
1310                                 break;
1311                             }
1312                             //can be step in spanSize...
1313                             for (int i = 1; i < gInfo.Count; i++)
1314                             {
1315                                 if (!failed) break;
1316                                 // Reach last index of group.
1317                                 if (i == (gInfo.Count - 1))
1318                                 {
1319                                     found.start = gInfo.StartIndex + i - adds;
1320                                     failed = false;
1321                                     break;
1322
1323                                 }
1324                                 else if ((((i - 1) / spanSize) * StepCandidate) + StepCandidate >= visibleArea.X - gInfo.GroupPosition - groupHeaderSize)
1325                                 {
1326                                     found.start = gInfo.StartIndex + i - adds;
1327                                     failed = false;
1328                                     break;
1329                                 }
1330                             }
1331                             if (!failed) break;
1332                         }
1333                     }
1334                     //footer only shows?
1335                     if (failed)
1336                     {
1337                         found.start = MaxIndex;
1338                     }
1339                 }
1340                 else
1341                 {
1342                     float visibleAreaX = visibleArea.X - (hasHeader ? headerSize : 0);
1343                     // Prevent zero division.
1344                     var itemSize = (StepCandidate != 0)? StepCandidate: 1f;
1345                     found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / itemSize)) - 1) * spanSize;
1346                     if (hasHeader) found.start += 1;
1347                 }
1348                 if (found.start < 0) found.start = 0;
1349             }
1350
1351             if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize - (IsHorizontal? Padding.End : Padding.Bottom))
1352             {
1353                 found.end = MaxIndex + 1;
1354             }
1355             else
1356             {
1357                 if (isGrouped)
1358                 {
1359                     bool failed = true;
1360                     // can it be start from founded group...?
1361                     //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
1362                     foreach (GroupInfo gInfo in groups)
1363                     {
1364                         // in the Group
1365                         if (gInfo.GroupPosition <= visibleArea.Y &&
1366                             gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
1367                         {
1368                             if (gInfo.GroupPosition + groupHeaderSize >= visibleArea.Y)
1369                             {
1370                                 found.end = gInfo.StartIndex + adds;
1371                                 failed = false;
1372                                 break;
1373                             }
1374                             //can be step in spanSize...
1375                             for (int i = 1; i < gInfo.Count; i++)
1376                             {
1377                                 if (!failed) break;
1378                                 // Reach last index of group.
1379                                 if (i == (gInfo.Count - 1))
1380                                 {
1381                                     found.end = gInfo.StartIndex + i + adds;
1382                                     failed = false;
1383                                     break;
1384                                 }
1385                                 else if ((((i - 1) / spanSize) * StepCandidate) + StepCandidate >= visibleArea.Y - gInfo.GroupPosition - groupHeaderSize)
1386                                 {
1387                                     found.end = gInfo.StartIndex + i + adds;
1388                                     failed = false;
1389                                     break;
1390                                 }
1391                             }
1392                             if (!failed) break;
1393                         }
1394                     }
1395                     //footer only shows?
1396                     if (failed)
1397                     {
1398                         found.end = MaxIndex;
1399                     }
1400                 }
1401                 else
1402                 {
1403                     float visibleAreaY = visibleArea.Y - (hasHeader ? headerSize : 0);
1404                     //Need to Consider GroupHeight!!!!
1405                     // Prevent zero division.
1406                     var itemSize = (StepCandidate != 0)? StepCandidate: 1f;
1407                     found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / itemSize)) + 1) * spanSize + adds;
1408                     if (hasHeader) found.end += 1;
1409                 }
1410                 if (found.end > (MaxIndex)) found.end = MaxIndex;
1411             }
1412             return found;
1413         }
1414
1415         internal override (float X, float Y) GetItemPosition(int index)
1416         {
1417             float xPos, yPos;
1418             int spaceStartX = Padding.Start;
1419             int spaceStartY = Padding.Top;
1420             int emptyArea = IsHorizontal?
1421                             (int)(colView.Size.Height - Padding.Top - Padding.Bottom - (sizeCandidate.Height * spanSize)) :
1422                             (int)(colView.Size.Width - Padding.Start - Padding.End - (sizeCandidate.Width * spanSize));
1423
1424             if (hasHeader && index == 0)
1425             {
1426                 return (spaceStartX + headerMargin.Start, spaceStartY + headerMargin.Top);
1427             }
1428             if (hasFooter && index == colView.InternalItemSource.Count - 1)
1429             {
1430                 xPos = IsHorizontal?
1431                         ScrollContentSize - Padding.End - footerSize + footerMargin.Start:
1432                         spaceStartX;
1433                 yPos = IsHorizontal?
1434                         spaceStartY:
1435                         ScrollContentSize - Padding.Bottom - footerSize + footerMargin.Top;
1436                 return (xPos, yPos);
1437             }
1438             if (isGrouped)
1439             {
1440                 GroupInfo myGroup = GetGroupInfo(index);
1441                 if (colView.InternalItemSource.IsGroupHeader(index))
1442                 {
1443                     spaceStartX+= groupHeaderMargin.Start;
1444                     spaceStartY+= groupHeaderMargin.Top;
1445                     xPos = IsHorizontal?
1446                             myGroup.GroupPosition + groupHeaderMargin.Start:
1447                             spaceStartX;
1448                     yPos = IsHorizontal?
1449                             spaceStartY:
1450                             myGroup.GroupPosition + groupHeaderMargin.Top;
1451                 }
1452                 else if (colView.InternalItemSource.IsGroupFooter(index))
1453                 {
1454                     spaceStartX+= groupFooterMargin.Start;
1455                     spaceStartY+= groupFooterMargin.Top;
1456                     xPos = IsHorizontal?
1457                             myGroup.GroupPosition + myGroup.GroupSize - groupFooterSize + groupFooterMargin.Start:
1458                             spaceStartX;
1459                     yPos = IsHorizontal?
1460                             spaceStartY:
1461                             myGroup.GroupPosition + myGroup.GroupSize - groupFooterSize + groupFooterMargin.Top;
1462                 }
1463                 else
1464                 {
1465                     int pureIndex = index - myGroup.StartIndex - ((colView.GroupHeaderTemplate != null)? 1: 0);
1466                     int division = pureIndex / spanSize;
1467                     int remainder = pureIndex % spanSize;
1468                     if (division < 0) division = 0;
1469                     if (remainder < 0) remainder = 0;
1470                     spaceStartX+= CandidateMargin.Start;
1471                     spaceStartY+= CandidateMargin.Top;
1472
1473                     xPos = IsHorizontal?
1474                             (division * sizeCandidate.Width) + myGroup.GroupPosition + groupHeaderSize + CandidateMargin.Start:
1475                             (emptyArea * align) + (remainder * sizeCandidate.Width) + spaceStartX;
1476                     yPos = IsHorizontal?
1477                             (emptyArea * align) + (remainder * sizeCandidate.Height) + spaceStartY:
1478                             (division * sizeCandidate.Height) + myGroup.GroupPosition + groupHeaderSize + CandidateMargin.Top;
1479                 }
1480             }
1481             else
1482             {
1483                 int pureIndex = index - (colView.Header ? 1 : 0);
1484                 // int convert must be truncate value.
1485                 int division = pureIndex / spanSize;
1486                 int remainder = pureIndex % spanSize;
1487                 if (division < 0) division = 0;
1488                 if (remainder < 0) remainder = 0;
1489                 spaceStartX+= CandidateMargin.Start;
1490                 spaceStartY+= CandidateMargin.Top;
1491
1492                 xPos = IsHorizontal?
1493                         (division * sizeCandidate.Width) + (hasHeader? headerSize : 0) + spaceStartX:
1494                         (emptyArea * align) + (remainder * sizeCandidate.Width) + spaceStartX;
1495                 yPos = IsHorizontal?
1496                         (emptyArea * align) + (remainder * sizeCandidate.Height) + spaceStartY:
1497                         (division * sizeCandidate.Height) + (hasHeader? headerSize : 0) + spaceStartY;
1498             }
1499
1500             return (xPos, yPos);
1501         }
1502
1503         internal override (float Width, float Height) GetItemSize(int index)
1504         {            
1505             return (sizeCandidate.Width - CandidateMargin.Start - CandidateMargin.End,
1506                     sizeCandidate.Height - CandidateMargin.Top - CandidateMargin.Bottom);
1507         }
1508         private void DelayedRequestLayout(float scrollPosition , bool force = true)
1509         {
1510             if (requestLayoutTimer != null)
1511             {
1512                 requestLayoutTimer.Dispose();
1513             }
1514
1515             requestLayoutTimer = new Timer(1);
1516             requestLayoutTimer.Interval = 1;
1517             requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) =>
1518             {
1519                 RequestLayout(scrollPosition, force);
1520                 return false;
1521             });
1522         }
1523
1524         private RecyclerViewItem GetVisibleItem(int index)
1525         {
1526             foreach (RecyclerViewItem item in VisibleItems)
1527             {
1528                 if (item.Index == index) return item;
1529             }
1530
1531             return null;
1532         }
1533
1534         private GroupInfo GetGroupInfo(int index)
1535         {
1536             if (Visited != null)
1537             {
1538                 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1539                     return Visited;
1540             }
1541             if (hasHeader && index == 0) return null;
1542             foreach (GroupInfo group in groups)
1543             {
1544                 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1545                 {
1546                     Visited = group;
1547                     return group;
1548                 }
1549             }
1550             Visited = null;
1551             return null;
1552         }
1553
1554         /*
1555         private object GetGroupParent(int index)
1556         {
1557             if (Visited != null)
1558             {
1559                 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1560                 return Visited.GroupParent;
1561             }
1562             if (hasHeader && index == 0) return null;
1563             foreach (GroupInfo group in groups)
1564             {
1565                 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1566                 {
1567                     Visited = group;
1568                     return group.GroupParent;
1569                 }
1570             }
1571             Visited = null;
1572             return null;
1573         }
1574         */
1575
1576         class GroupInfo
1577         {
1578             public object GroupParent;
1579             public int StartIndex;
1580             public int Count;
1581             public float GroupSize;
1582             public float GroupPosition;
1583             //Items relative position from the GroupPosition
1584         }
1585     }
1586 }