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