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