[NUI] Apply Padding and Margin on CollectionView and Layouter with styles (#2883)
[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     /// This class implements a grid box layout.
25     /// </summary>
26     [EditorBrowsable(EditorBrowsableState.Never)]
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
47         /// <summary>
48         /// Clean up ItemsLayouter.
49         /// </summary>
50         /// <param name="view"> ItemsView of layouter. </param>
51         [EditorBrowsable(EditorBrowsableState.Never)]
52         public override void Initialize(RecyclerView view)
53         {
54             colView = view as CollectionView;
55             if (colView == null)
56             {
57                 throw new ArgumentException("GridLayouter only can be applied CollectionView.", nameof(view));
58             }
59
60             // 1. Clean Up
61             foreach (RecyclerViewItem item in VisibleItems)
62             {
63                 colView.UnrealizeItem(item, false);
64             }
65             VisibleItems.Clear();
66             groups.Clear();
67
68             FirstVisible = 0;
69             LastVisible = 0;
70
71             IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
72
73             RecyclerViewItem header = colView?.Header;
74             RecyclerViewItem footer = colView?.Footer;
75             float width, height;
76             int count = colView.InternalItemSource.Count;
77             int pureCount = count - (header? 1 : 0) - (footer? 1 : 0);
78
79             // 2. Get the header / footer and size deligated item and measure the size.
80             if (header != null)
81             {
82                 MeasureChild(colView, header);
83
84                 width = header.Layout != null? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
85                 height = header.Layout != null? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
86
87                 Extents itemMargin = header.Margin;
88                 headerSize = IsHorizontal?
89                                 width + itemMargin.Start + itemMargin.End:
90                                 height + itemMargin.Top + itemMargin.Bottom;
91                 headerMargin = new Extents(itemMargin);
92                 hasHeader = true;
93
94                 colView.UnrealizeItem(header);
95             }
96
97             if (footer != null)
98             {
99                 MeasureChild(colView, footer);
100
101                 width = footer.Layout != null? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
102                 height = footer.Layout != null? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
103
104                 Extents itemMargin = footer.Margin;
105                 footerSize = IsHorizontal?
106                                 width + itemMargin.Start + itemMargin.End:
107                                 height + itemMargin.Top + itemMargin.Bottom;
108                 footerMargin = new Extents(itemMargin);
109                 footer.Index = count - 1;
110                 hasFooter = true;
111
112                 colView.UnrealizeItem(footer);
113             }
114
115             int firstIndex = header? 1 : 0;
116
117             if (colView.IsGrouped)
118             {
119                 isGrouped = true;
120
121                 if (colView.GroupHeaderTemplate != null)
122                 {
123                     while (!colView.InternalItemSource.IsGroupHeader(firstIndex)) firstIndex++;
124                     //must be always true
125                     if (colView.InternalItemSource.IsGroupHeader(firstIndex))
126                     {
127                         RecyclerViewItem groupHeader = colView.RealizeItem(firstIndex);
128                         firstIndex++;
129
130                         if (groupHeader == null) throw new Exception("[" + firstIndex + "] Group Header failed to realize!");
131
132                         // Need to Set proper height or width on scroll direction.
133                         if (groupHeader.Layout == null)
134                         {
135                             width = groupHeader.WidthSpecification;
136                             height = groupHeader.HeightSpecification;
137                         }
138                         else
139                         {
140                             MeasureChild(colView, groupHeader);
141
142                             width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
143                             height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
144                         }
145                         //Console.WriteLine("[NUI] GroupHeader Size {0} :{0}", width, height);
146                         // pick the StepCandidate.
147                         Extents itemMargin = groupHeader.Margin;
148                         groupHeaderSize = IsHorizontal?
149                                             width + itemMargin.Start + itemMargin.End:
150                                             height + itemMargin.Top + itemMargin.Bottom;
151                         groupHeaderMargin = new Extents(itemMargin);
152                         colView.UnrealizeItem(groupHeader);
153                     }
154                 }
155                 else
156                 {
157                     groupHeaderSize = 0F;
158                 }
159
160                 if (colView.GroupFooterTemplate != null)
161                 {
162                     int firstFooter = firstIndex;
163                     while (!colView.InternalItemSource.IsGroupFooter(firstFooter)) firstFooter++;
164                     //must be always true
165                     if (colView.InternalItemSource.IsGroupFooter(firstFooter))
166                     {
167                         RecyclerViewItem groupFooter = colView.RealizeItem(firstFooter);
168
169                         if (groupFooter == null) throw new Exception("[" + firstFooter + "] Group Footer failed to realize!");
170                         // Need to Set proper height or width on scroll direction.
171                         if (groupFooter.Layout == null)
172                         {
173                             width = groupFooter.WidthSpecification;
174                             height = groupFooter.HeightSpecification;
175                         }
176                         else
177                         {
178                             MeasureChild(colView, groupFooter);
179
180                             width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
181                             height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
182                         }
183                         // pick the StepCandidate.
184                         Extents itemMargin = groupFooter.Margin;
185                         groupFooterSize = IsHorizontal?
186                                             width + itemMargin.Start + itemMargin.End:
187                                             height + itemMargin.Top + itemMargin.Bottom;
188                         groupFooterMargin = new Extents(itemMargin);
189                     
190                         colView.UnrealizeItem(groupFooter);
191                     }
192                 }
193                 else
194                 {
195                     groupFooterSize = 0F;
196                 }
197             }
198             else isGrouped = false;
199
200             bool failed = false;
201             //Final Check of FirstIndex
202             while (colView.InternalItemSource.IsHeader(firstIndex) ||
203                     colView.InternalItemSource.IsGroupHeader(firstIndex) ||
204                     colView.InternalItemSource.IsGroupFooter(firstIndex))
205             {
206                 if (colView.InternalItemSource.IsFooter(firstIndex))
207                 {
208                     StepCandidate = 0F;
209                     failed = true;
210                     break;
211                 }
212                 firstIndex++;
213             }
214
215             sizeCandidate = (0, 0);
216             if (!failed)
217             {
218                 // Get Size Deligate. FIXME if group exist index must be changed.
219                 RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
220                 if (sizeDeligate == null)
221                 {
222                     throw new Exception("Cannot create content from DatTemplate.");
223                 }
224                 sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
225
226                 // Need to Set proper height or width on scroll direction.
227                 if (sizeDeligate.Layout == null)
228                 {
229                     width = sizeDeligate.WidthSpecification;
230                     height = sizeDeligate.HeightSpecification;
231                 }
232                 else
233                 {
234                     MeasureChild(colView, sizeDeligate);
235
236                     width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
237                     height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
238                 }
239                 //Console.WriteLine("[NUI] item Size {0} :{1}", width, height);
240
241                 // pick the StepCandidate.
242                 Extents itemMargin = sizeDeligate.Margin;
243                 width = width + itemMargin.Start + itemMargin.End;
244                 height = height + itemMargin.Top + itemMargin.Bottom;
245                 StepCandidate = IsHorizontal? width : height;
246                 CandidateMargin = new Extents(itemMargin);
247                 spanSize = IsHorizontal?
248                             Convert.ToInt32(Math.Truncate((double)((colView.Size.Height - Padding.Top - Padding.Bottom) / height))) :
249                             Convert.ToInt32(Math.Truncate((double)((colView.Size.Width - Padding.Start - Padding.End) / width)));
250
251                 sizeCandidate = (width, height);
252
253                 colView.UnrealizeItem(sizeDeligate);
254             }
255
256             if (StepCandidate < 1) StepCandidate = 1;
257             if (spanSize < 1) spanSize = 1;
258
259             if (isGrouped)
260             {
261                 float Current = 0.0F;
262                 IGroupableItemSource source = colView.InternalItemSource;
263                 GroupInfo currentGroup = null;
264
265                 for (int i = 0; i < count; i++)
266                 {
267                     if (i == 0 && hasHeader)
268                     {
269                         Current += headerSize;
270                     }
271                     else if (i == count - 1 && hasFooter)
272                     {
273                         Current += footerSize;
274                     }
275                     else
276                     {
277                         //GroupHeader must always exist in group usage.
278                         if (source.IsGroupHeader(i))
279                         {
280                             currentGroup = new GroupInfo()
281                             {
282                                 GroupParent = source.GetGroupParent(i),
283                                 StartIndex = i,
284                                 Count = 1,
285                                 GroupSize = groupHeaderSize,
286                                 GroupPosition = Current
287                             };
288                             groups.Add(currentGroup);
289                             Current += groupHeaderSize;
290                         }
291                         //optional
292                         else if (source.IsGroupFooter(i))
293                         {
294                             //currentGroup.hasFooter = true;
295                             currentGroup.Count++;
296                             currentGroup.GroupSize += groupFooterSize;
297                             Current += groupFooterSize;
298                         }
299                         else
300                         {
301                             currentGroup.Count++;
302                             int index = i - currentGroup.StartIndex - 1; // groupHeader must always exist.
303                             if ((index % spanSize) == 0)
304                             {
305                                 currentGroup.GroupSize += StepCandidate;
306                                 Current += StepCandidate;
307                             }
308                         }
309                     }
310                 }
311                 ScrollContentSize = Current;
312             }
313             else
314             {
315                 // 3. Measure the scroller content size.
316                 ScrollContentSize = StepCandidate * Convert.ToInt32(Math.Ceiling((double)pureCount / (double)spanSize));
317                 if (hasHeader) ScrollContentSize += headerSize;
318                 if (hasFooter) ScrollContentSize += footerSize;
319             }
320
321             ScrollContentSize = IsHorizontal?
322                                 ScrollContentSize + Padding.Start + Padding.End:
323                                 ScrollContentSize + Padding.Top + Padding.Bottom;
324
325             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
326             else colView.ContentContainer.SizeHeight = ScrollContentSize;
327
328             base.Initialize(colView);
329             //Console.WriteLine("Init Done, StepCnadidate{0}, spanSize{1}, Scroll{2}", StepCandidate, spanSize, ScrollContentSize);
330         }
331
332         /// <summary>
333         /// This is called to find out where items are lain out according to current scroll position.
334         /// </summary>
335         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
336         /// <param name="force">boolean force flag to layouting forcely.</param>
337         public override void RequestLayout(float scrollPosition, bool force = false)
338         {
339             // Layouting is only possible after once it intialized.
340             if (!IsInitialized) return;
341             int LastIndex = colView.InternalItemSource.Count;
342
343             if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
344             PrevScrollPosition = Math.Abs(scrollPosition);
345
346             int prevFirstVisible = FirstVisible;
347             int prevLastVisible = LastVisible;
348             bool IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
349
350             (float X, float Y) visibleArea = (PrevScrollPosition,
351                 PrevScrollPosition + (IsHorizontal? colView.Size.Width : colView.Size.Height)
352             );
353
354             //Console.WriteLine("[NUI] itemsView [{0},{1}] [{2},{3}]", colView.Size.Width, colView.Size.Height, colView.ContentContainer.Size.Width, colView.ContentContainer.Size.Height);
355
356             // 1. Set First/Last Visible Item Index. 
357             (int start, int end) = FindVisibleItems(visibleArea);
358             FirstVisible = start;
359             LastVisible = end;
360
361             //Console.WriteLine("[NUI] {0} :visibleArea before [{1},{2}] after [{3},{4}]", scrollPosition, prevFirstVisible, prevLastVisible, FirstVisible, LastVisible);
362
363             // 2. Unrealize invisible items.
364             List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
365             foreach (RecyclerViewItem item in VisibleItems)
366             {
367                 if (item.Index < FirstVisible || item.Index > LastVisible)
368                 {
369                     //Console.WriteLine("[NUI] Unrealize{0}!", item.Index);
370                     unrealizedItems.Add(item);
371                     colView.UnrealizeItem(item);
372                 }
373             }
374             VisibleItems.RemoveAll(unrealizedItems.Contains);
375
376             //Console.WriteLine("Realize Begin [{0} to {1}]", FirstVisible, LastVisible);
377             // 3. Realize and placing visible items.
378             for (int i = FirstVisible; i <= LastVisible; i++)
379             {
380                 //Console.WriteLine("[NUI] Realize!");
381                 RecyclerViewItem item = null;
382                 // 4. Get item if visible or realize new.
383                 if (i >= prevFirstVisible && i <= prevLastVisible)
384                 {
385                     item = GetVisibleItem(i);
386                     if (item) continue;
387                 }
388                 if (item == null) item = colView.RealizeItem(i);
389                 VisibleItems.Add(item);
390
391                 //item Position without Padding and Margin.
392                 (float x, float y) = GetItemPosition(i);
393                 // 5. Placing item with Padding and Margin.
394                 item.Position = new Position(x, y);
395                 
396                 //Linear Item need to be resized!
397                 if (item.IsHeader || item.IsFooter || item.isGroupHeader || item.isGroupFooter)
398                 {
399                     if (IsHorizontal && item.HeightSpecification == LayoutParamPolicies.MatchParent)
400                     {
401                         item.Size = new Size(item.Size.Width, Container.Size.Height - Padding.Top - Padding.Bottom - item.Margin.Top - item.Margin.Bottom);
402                     }
403                     else if (!IsHorizontal && item.WidthSpecification == LayoutParamPolicies.MatchParent)
404                     {
405                         item.Size = new Size(Container.Size.Width - Padding.Start - Padding.End - item.Margin.Start - item.Margin.End, item.Size.Height);
406                     }
407                 }
408                 //Console.WriteLine("[NUI] ["+item.Index+"] ["+item.Position.X+", "+item.Position.Y+" ==== \n");
409             }
410             //Console.WriteLine("Realize Done");
411         }
412
413         /// <inheritdoc/>
414         public override void NotifyItemSizeChanged(RecyclerViewItem item)
415         {
416             // All Item size need to be same in grid!
417             // if you want to change item size, change dataTemplate to re-initing.
418             return;
419         }
420
421         /// <Inheritdoc/>
422         [EditorBrowsable(EditorBrowsableState.Never)]
423         public override float CalculateLayoutOrientationSize()
424         {
425             //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
426             return ScrollContentSize;
427         }
428
429         /// <Inheritdoc/>
430         [EditorBrowsable(EditorBrowsableState.Never)]
431         public override float CalculateCandidateScrollPosition(float scrollPosition)
432         {
433             //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
434             return scrollPosition;
435         }
436
437         /// <Inheritdoc/>
438         [EditorBrowsable(EditorBrowsableState.Never)]
439         public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
440         {
441             if (currentFocusedView == null)
442                 throw new ArgumentNullException(nameof(currentFocusedView));
443
444             View nextFocusedView = null;
445             int targetSibling = -1;
446             bool IsHorizontal = colView.ScrollingDirection == ScrollableBase.Direction.Horizontal;
447
448             switch (direction)
449             {
450                 case View.FocusDirection.Left:
451                     {
452                         targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder - 1 : targetSibling;
453                         break;
454                     }
455                 case View.FocusDirection.Right:
456                     {
457                         targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder + 1 : targetSibling;
458                         break;
459                     }
460                 case View.FocusDirection.Up:
461                     {
462                         targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder - 1;
463                         break;
464                     }
465                 case View.FocusDirection.Down:
466                     {
467                         targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder + 1;
468                         break;
469                     }
470             }
471
472             if (targetSibling > -1 && targetSibling < Container.Children.Count)
473             {
474                 RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
475                 if (candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
476                 {
477                     nextFocusedView = candidate;
478                 }
479             }
480             return nextFocusedView;
481         }
482
483         /// <inheritdoc/>
484         [EditorBrowsable(EditorBrowsableState.Never)]
485         protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
486         {
487             int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter ? 1 : 0);
488             int adds = spanSize * 2;
489             int skipGroup = -1;
490             (int start, int end) found = (0, 0);
491
492             // Header is Showing
493             if (hasHeader && visibleArea.X < headerSize + (IsHorizontal? Padding.Start : Padding.Top))
494             {
495                 found.start = 0;
496             }
497             else
498             {
499                 if (isGrouped)
500                 {
501                     bool failed = true;
502                     foreach (GroupInfo gInfo in groups)
503                     {
504                         skipGroup++;
505                         // in the Group
506                         if (gInfo.GroupPosition <= visibleArea.X &&
507                             gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
508                         {
509                             if (gInfo.GroupPosition + groupHeaderSize >= visibleArea.X)
510                             {
511                                 found.start = gInfo.StartIndex - adds;
512                                 failed = false;
513                             }
514                             //can be step in spanSize...
515                             for (int i = 1; i < gInfo.Count; i++)
516                             {
517                                 if (!failed) break;
518                                 // Reach last index of group.
519                                 if (i == (gInfo.Count - 1))
520                                 {
521                                     found.start = gInfo.StartIndex + i - adds;
522                                     failed = false;
523                                     break;
524
525                                 }
526                                 else if ((((i - 1) / spanSize) * StepCandidate) + StepCandidate >= visibleArea.X - gInfo.GroupPosition - groupHeaderSize)
527                                 {
528                                     found.start = gInfo.StartIndex + i - adds;
529                                     failed = false;
530                                     break;
531                                 }
532                             }
533                         }
534                     }
535                     //footer only shows?
536                     if (failed)
537                     {
538                         found.start = MaxIndex;
539                     }
540                 }
541                 else
542                 {
543                     float visibleAreaX = visibleArea.X - (hasHeader ? headerSize : 0);
544                     found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / StepCandidate)) - 1) * spanSize;
545                     if (hasHeader) found.start += 1;
546                 }
547                 if (found.start < 0) found.start = 0;
548             }
549
550             if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize - (IsHorizontal? Padding.End : Padding.Bottom))
551             {
552                 found.end = MaxIndex + 1;
553             }
554             else
555             {
556                 if (isGrouped)
557                 {
558                     bool failed = true;
559                     // can it be start from founded group...?
560                     //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
561                     foreach (GroupInfo gInfo in groups)
562                     {
563                         // in the Group
564                         if (gInfo.GroupPosition <= visibleArea.Y &&
565                             gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
566                         {
567                             if (gInfo.GroupPosition + groupHeaderSize >= visibleArea.Y)
568                             {
569                                 found.end = gInfo.StartIndex + adds;
570                                 failed = false;
571                             }
572                             //can be step in spanSize...
573                             for (int i = 1; i < gInfo.Count; i++)
574                             {
575                                 if (!failed) break;
576                                 // Reach last index of group.
577                                 if (i == (gInfo.Count - 1))
578                                 {
579                                     found.end = gInfo.StartIndex + i + adds;
580                                     failed = false;
581                                     break;
582                                 }
583                                 else if ((((i - 1) / spanSize) * StepCandidate) + StepCandidate >= visibleArea.Y - gInfo.GroupPosition - groupHeaderSize)
584                                 {
585                                     found.end = gInfo.StartIndex + i + adds;
586                                     failed = false;
587                                     break;
588                                 }
589                             }
590                         }
591                     }
592                     //footer only shows?
593                     if (failed)
594                     {
595                         found.start = MaxIndex;
596                     }
597                 }
598                 else
599                 {
600                     float visibleAreaY = visibleArea.Y - (hasHeader ? headerSize : 0);
601                     //Need to Consider GroupHeight!!!!
602                     found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / StepCandidate)) + 1) * spanSize + adds;
603                     if (hasHeader) found.end += 1;
604                 }
605                 if (found.end > (MaxIndex)) found.end = MaxIndex;
606             }
607             return found;
608         }
609
610         internal override (float X, float Y) GetItemPosition(int index)
611         {
612             float xPos, yPos;
613             int spaceStartX = Padding.Start;
614             int spaceStartY = Padding.Top;
615             int emptyArea = IsHorizontal?
616                             (int)(colView.Size.Height - Padding.Top - Padding.Bottom - (sizeCandidate.Height * spanSize)) :
617                             (int)(colView.Size.Width - Padding.Start - Padding.End - (sizeCandidate.Width * spanSize));
618
619             if (hasHeader && index == 0)
620             {
621                 return (spaceStartX + headerMargin.Start, spaceStartY + headerMargin.Top);
622             }
623             if (hasFooter && index == colView.InternalItemSource.Count - 1)
624             {
625                 xPos = IsHorizontal?
626                         ScrollContentSize - Padding.End - footerSize + footerMargin.Start:
627                         spaceStartX;
628                 yPos = IsHorizontal?
629                         spaceStartY:
630                         ScrollContentSize - Padding.Bottom - footerSize + footerMargin.Top;
631                 return (xPos, yPos);
632             }
633             if (isGrouped)
634             {
635                 GroupInfo myGroup = GetGroupInfo(index);
636                 if (colView.InternalItemSource.IsGroupHeader(index))
637                 {
638                     spaceStartX+= groupHeaderMargin.Start;
639                     spaceStartY+= groupHeaderMargin.Top;
640                     xPos = IsHorizontal?
641                             myGroup.GroupPosition + groupHeaderMargin.Start:
642                             spaceStartX;
643                     yPos = IsHorizontal?
644                             spaceStartY:
645                             myGroup.GroupPosition + groupHeaderMargin.Top;
646                 }
647                 else if (colView.InternalItemSource.IsGroupFooter(index))
648                 {
649                     spaceStartX+= groupFooterMargin.Start;
650                     spaceStartY+= groupFooterMargin.Top;
651                     xPos = IsHorizontal?
652                             myGroup.GroupPosition + myGroup.GroupSize - groupFooterSize + groupFooterMargin.Start:
653                             spaceStartX;
654                     yPos = IsHorizontal?
655                             spaceStartY:
656                             myGroup.GroupPosition + myGroup.GroupSize - groupFooterSize + groupFooterMargin.Top;
657                 }
658                 else
659                 {
660                     int pureIndex = index - myGroup.StartIndex - 1;
661                     int division = pureIndex / spanSize;
662                     int remainder = pureIndex % spanSize;
663                     if (division < 0) division = 0;
664                     if (remainder < 0) remainder = 0;
665                     spaceStartX+= CandidateMargin.Start;
666                     spaceStartY+= CandidateMargin.Top;
667
668                     xPos = IsHorizontal?
669                             (division * sizeCandidate.Width) + myGroup.GroupPosition + groupHeaderSize + CandidateMargin.Start:
670                             (emptyArea * align) + (remainder * sizeCandidate.Width) + spaceStartX;
671                     yPos = IsHorizontal?
672                             (emptyArea * align) + (remainder * sizeCandidate.Height) + spaceStartY:
673                             (division * sizeCandidate.Height) + myGroup.GroupPosition + groupHeaderSize + CandidateMargin.Top;
674                 }
675             }
676             else
677             {
678                 int pureIndex = index - (colView.Header ? 1 : 0);
679                 // int convert must be truncate value.
680                 int division = pureIndex / spanSize;
681                 int remainder = pureIndex % spanSize;
682                 if (division < 0) division = 0;
683                 if (remainder < 0) remainder = 0;
684                 spaceStartX+= CandidateMargin.Start;
685                 spaceStartY+= CandidateMargin.Top;
686
687                 xPos = IsHorizontal?
688                         (division * sizeCandidate.Width) + (hasHeader? headerSize : 0) + spaceStartX:
689                         (emptyArea * align) + (remainder * sizeCandidate.Width) + spaceStartX;
690                 yPos = IsHorizontal?
691                         (emptyArea * align) + (remainder * sizeCandidate.Height) + spaceStartY:
692                         (division * sizeCandidate.Height) + (hasHeader? headerSize : 0) + spaceStartY;
693             }
694
695             return (xPos, yPos);
696         }
697
698         internal override (float Width, float Height) GetItemSize(int index)
699         {            
700             return (sizeCandidate.Width - CandidateMargin.Start - CandidateMargin.End,
701                     sizeCandidate.Height - CandidateMargin.Top - CandidateMargin.Bottom);
702         }
703
704         private RecyclerViewItem GetVisibleItem(int index)
705         {
706             foreach (RecyclerViewItem item in VisibleItems)
707             {
708                 if (item.Index == index) return item;
709             }
710
711             return null;
712         }
713
714         private GroupInfo GetGroupInfo(int index)
715         {
716             if (Visited != null)
717             {
718                 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
719                     return Visited;
720             }
721             if (hasHeader && index == 0) return null;
722             foreach (GroupInfo group in groups)
723             {
724                 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
725                 {
726                     Visited = group;
727                     return group;
728                 }
729             }
730             Visited = null;
731             return null;
732         }
733
734         /*
735         private object GetGroupParent(int index)
736         {
737             if (Visited != null)
738             {
739                 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
740                 return Visited.GroupParent;
741             }
742             if (hasHeader && index == 0) return null;
743             foreach (GroupInfo group in groups)
744             {
745                 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
746                 {
747                     Visited = group;
748                     return group.GroupParent;
749                 }
750             }
751             Visited = null;
752             return null;
753         }
754         */
755
756         class GroupInfo
757         {
758             public object GroupParent;
759             public int StartIndex;
760             public int Count;
761             public float GroupSize;
762             public float GroupPosition;
763             //Items relative position from the GroupPosition
764         }
765     }
766 }