51105ecab229e5921d3c1c7fdd358305a8b1b5c8
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / RecyclerView / CollectionView.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 System.Collections;
19 using System.Collections.Generic;
20 using System.Windows.Input;
21 using System.ComponentModel;
22 using Tizen.NUI.BaseComponents;
23 using Tizen.NUI.Binding;
24
25 namespace Tizen.NUI.Components
26 {
27     /// <summary>
28     /// This class provides a View that can layouting items in list and grid with high performance.
29     /// </summary>
30     [EditorBrowsable(EditorBrowsableState.Never)]
31     public class CollectionView : RecyclerView
32     {
33         /// <summary>
34         /// Binding Property of selected item in single selection.
35         /// </summary>
36         [EditorBrowsable(EditorBrowsableState.Never)]
37         public static readonly BindableProperty SelectedItemProperty =
38             BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(CollectionView), null,
39                 propertyChanged: (bindable, oldValue, newValue) =>
40                 {
41                     var colView = (CollectionView)bindable;
42                     oldValue = colView.selectedItem;
43                     colView.selectedItem = newValue;
44                     var args = new SelectionChangedEventArgs(oldValue, newValue);
45
46                     foreach (RecyclerViewItem item in colView.ContentContainer.Children.Where((item) => item is RecyclerViewItem))
47                     {
48                         if (item.BindingContext == null) continue;
49                         if (item.BindingContext == oldValue) item.IsSelected = false;
50                         else if (item.BindingContext == newValue) item.IsSelected = true;
51                     }
52
53                     SelectionPropertyChanged(colView, args);
54                 },
55                 defaultValueCreator: (bindable) =>
56                 {
57                     var colView = (CollectionView)bindable;
58                     return colView.selectedItem;
59                 });
60
61         /// <summary>
62         /// Binding Property of selected items list in multiple selection.
63         /// </summary>
64         [EditorBrowsable(EditorBrowsableState.Never)]
65         public static readonly BindableProperty SelectedItemsProperty =
66             BindableProperty.Create(nameof(SelectedItems), typeof(IList<object>), typeof(CollectionView), null,
67                 propertyChanged: (bindable, oldValue, newValue) =>
68                 {
69                     var colView = (CollectionView)bindable;
70                     var oldSelection = colView.selectedItems ?? selectEmpty;
71                     //FIXME : CoerceSelectedItems calls only isCreatedByXaml
72                     var newSelection = (SelectionList)CoerceSelectedItems(colView, newValue);
73                     colView.selectedItems = newSelection;
74                     colView.SelectedItemsPropertyChanged(oldSelection, newSelection);
75                 },
76                 defaultValueCreator: (bindable) =>
77                 {
78                     var colView = (CollectionView)bindable;
79                     colView.selectedItems = colView.selectedItems ?? new SelectionList(colView);
80                     return colView.selectedItems;
81                 });
82
83         /// <summary>
84         /// Binding Property of selected items list in multiple selection.
85         /// </summary>
86         [EditorBrowsable(EditorBrowsableState.Never)]
87         public static readonly BindableProperty SelectionModeProperty =
88             BindableProperty.Create(nameof(SelectionMode), typeof(ItemSelectionMode), typeof(CollectionView), ItemSelectionMode.None,
89                 propertyChanged: (bindable, oldValue, newValue) =>
90                 {
91                     var colView = (CollectionView)bindable;
92                     oldValue = colView.selectionMode;
93                     colView.selectionMode = (ItemSelectionMode)newValue;
94                     SelectionModePropertyChanged(colView, oldValue, newValue);
95                 },
96                 defaultValueCreator: (bindable) =>
97                 {
98                     var colView = (CollectionView)bindable;
99                     return colView.selectionMode;
100                 });
101
102
103         private static readonly IList<object> selectEmpty = new List<object>(0);
104         private DataTemplate itemTemplate = null;
105         private IEnumerable itemsSource = null;
106         private ItemsLayouter itemsLayouter = null;
107         private DataTemplate groupHeaderTemplate;
108         private DataTemplate groupFooterTemplate;
109         private bool isGrouped;
110         private bool wasRelayouted = false;
111         private bool needInitalizeLayouter = false;
112         private object selectedItem;
113         private SelectionList selectedItems;
114         private bool suppressSelectionChangeNotification;
115         private ItemSelectionMode selectionMode = ItemSelectionMode.None;
116         private RecyclerViewItem header;
117         private RecyclerViewItem footer;
118         private View focusedView;
119         private int prevFocusedDataIndex = 0;
120         private List<RecyclerViewItem> recycleGroupHeaderCache { get; } = new List<RecyclerViewItem>();
121         private List<RecyclerViewItem> recycleGroupFooterCache { get; } = new List<RecyclerViewItem>();
122
123         /// <summary>
124         /// Base constructor.
125         /// </summary>
126         [EditorBrowsable(EditorBrowsableState.Never)]
127         public CollectionView() : base()
128         {
129             FocusGroup = true;
130             SetKeyboardNavigationSupport(true);
131         }
132
133         /// <summary>
134         /// Base constructor with ItemsSource
135         /// </summary>
136         /// <param name="itemsSource">item's data source</param>
137         [EditorBrowsable(EditorBrowsableState.Never)]
138         public CollectionView(IEnumerable itemsSource) : this()
139         {
140             ItemsSource = itemsSource;
141         }
142
143         /// <summary>
144         /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
145         /// </summary>
146         /// <param name="itemsSource">item's data source</param>
147         /// <param name="layouter">item's layout manager</param>
148         /// <param name="template">item's view template with data bindings</param>
149         [EditorBrowsable(EditorBrowsableState.Never)]
150         public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
151         {
152             ItemsSource = itemsSource;
153             ItemTemplate = template;
154             ItemsLayouter = layouter;
155         }
156
157         /// <summary>
158         /// Event of Selection changed.
159         /// old selection list and new selection will be provided.
160         /// </summary>
161         [EditorBrowsable(EditorBrowsableState.Never)]
162         public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
163
164         /// <summary>
165         /// Align item in the viewport when ScrollTo() calls.
166         /// </summary>
167         [EditorBrowsable(EditorBrowsableState.Never)]
168         public enum ItemScrollTo
169         {
170             /// <summary>
171             /// Scroll to show item in nearest viewport on scroll direction.
172             /// item is above the scroll viewport, item will be came into front,
173             /// item is under the scroll viewport, item will be came into end,
174             /// item is in the scroll viewport, no scroll.
175             /// </summary>
176             Nearest,
177             /// <summary>
178             /// Scroll to show item in start of the viewport.
179             /// </summary>
180             Start,
181             /// <summary>
182             /// Scroll to show item in center of the viewport.
183             /// </summary>
184             Center,
185             /// <summary>
186             /// Scroll to show item in end of the viewport.
187             /// </summary>
188             End,
189         }
190
191         /// <summary>
192         /// Item's source data.
193         /// </summary>
194         [EditorBrowsable(EditorBrowsableState.Never)]
195         public override IEnumerable ItemsSource
196         {
197             get
198             {
199                 return itemsSource;
200             }
201             set
202             {
203
204                 itemsSource = value;
205                 if (value == null)
206                 {
207                     if (InternalItemSource != null) InternalItemSource.Dispose();
208                     //layouter.Clear()
209                     return;
210                 }
211                 if (InternalItemSource != null) InternalItemSource.Dispose();
212                 InternalItemSource = ItemsSourceFactory.Create(this);
213
214                 if (itemsLayouter == null) return;
215
216                 needInitalizeLayouter = true;
217                 Init();
218             }
219         }
220
221         /// <summary>
222         /// DataTemplate for items.
223         /// </summary>
224         [EditorBrowsable(EditorBrowsableState.Never)]
225         public override DataTemplate ItemTemplate
226         {
227             get
228             {
229                 return itemTemplate;
230             }
231             set
232             {
233                 itemTemplate = value;
234                 if (value == null)
235                 {
236                     //layouter.clear()
237                     return;
238                 }
239
240                 needInitalizeLayouter = true;
241                 Init();
242             }
243         }
244
245         /// <summary>
246         /// Items Layouter.
247         /// </summary>
248         [EditorBrowsable(EditorBrowsableState.Never)]
249         public virtual ItemsLayouter ItemsLayouter
250         {
251             get
252             {
253                 return itemsLayouter;
254             }
255             set
256             {
257                 itemsLayouter = value;
258                 base.InternalItemsLayouter = ItemsLayouter;
259                 if (value == null)
260                 {
261                     needInitalizeLayouter = false;
262                     return;
263                 }
264
265                 needInitalizeLayouter = true;
266
267                 var styleName = "Tizen.NUI.Components." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
268                 using (ViewStyle layouterStyle = ThemeManager.GetStyle(styleName))
269                 {
270                     if (layouterStyle != null)
271                     {
272                         itemsLayouter.Padding = new Extents(layouterStyle.Padding);
273                     }
274                 }
275                 Init();
276             }
277         }
278
279         /// <summary>
280         /// Scrolling direction to display items layout.
281         /// </summary>
282         [EditorBrowsable(EditorBrowsableState.Never)]
283         public new Direction ScrollingDirection
284         {
285             get
286             {
287                 return base.ScrollingDirection;
288             }
289             set
290             {
291                 base.ScrollingDirection = value;
292
293                 if (ScrollingDirection == Direction.Horizontal)
294                 {
295                     ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
296                 }
297                 else
298                 {
299                     ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
300                 }
301             }
302         }
303
304         /// <summary>
305         /// Selected item in single selection.
306         /// </summary>
307         [EditorBrowsable(EditorBrowsableState.Never)]
308         public object SelectedItem
309         {
310             get => GetValue(SelectedItemProperty);
311             set => SetValue(SelectedItemProperty, value);
312         }
313
314         /// <summary>
315         /// Selected items list in multiple selection.
316         /// </summary>
317         [EditorBrowsable(EditorBrowsableState.Never)]
318         public IList<object> SelectedItems
319         {
320             get => (IList<object>)GetValue(SelectedItemsProperty);
321             // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
322         }
323
324         /// <summary>
325         /// Selection mode to handle items selection. See ItemSelectionMode for details.
326         /// </summary>
327         [EditorBrowsable(EditorBrowsableState.Never)]
328         public ItemSelectionMode SelectionMode
329         {
330             get => (ItemSelectionMode)GetValue(SelectionModeProperty);
331             set => SetValue(SelectionModeProperty, value);
332         }
333
334         /// <summary>
335         /// Command of selection changed.
336         /// </summary>
337         [EditorBrowsable(EditorBrowsableState.Never)]
338         public ICommand SelectionChangedCommand { set; get; }
339
340         /// <summary>
341         /// Command parameter of selection changed.
342         /// </summary>
343         [EditorBrowsable(EditorBrowsableState.Never)]
344         public object SelectionChangedCommandParameter { set; get; }
345
346         /// <summary>
347         /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
348         /// </summary>
349         [EditorBrowsable(EditorBrowsableState.Never)]
350         public ItemSizingStrategy SizingStrategy { get; set; }
351
352         /// <summary>
353         /// Header item which placed in top-most position.
354         /// note : internal index and count will be increased.
355         /// </summary>
356         [EditorBrowsable(EditorBrowsableState.Never)]
357         public RecyclerViewItem Header
358         {
359             get => header;
360             set
361             {
362                 if (header != null)
363                 {
364                     //ContentContainer.Remove(header);
365                     Utility.Dispose(header);
366                 }
367                 if (value != null)
368                 {
369                     value.Index = 0;
370                     value.ParentItemsView = this;
371                     value.IsHeader = true;
372                     ContentContainer.Add(value);
373                 }
374                 header = value;
375                 needInitalizeLayouter = true;
376                 Init();
377             }
378         }
379
380         /// <summary>
381         /// Footer item which placed in bottom-most position.
382         /// note : internal count will be increased.
383         /// </summary>
384         [EditorBrowsable(EditorBrowsableState.Never)]
385         public RecyclerViewItem Footer
386         {
387             get => footer;
388             set
389             {
390                 if (footer != null)
391                 {
392                     //ContentContainer.Remove(footer);
393                     Utility.Dispose(footer);
394                 }
395                 if (value != null)
396                 {
397                     value.Index = InternalItemSource?.Count ?? 0;
398                     value.ParentItemsView = this;
399                     value.IsFooter = true;
400                     ContentContainer.Add(value);
401                 }
402                 footer = value;
403                 needInitalizeLayouter = true;
404                 Init();
405             }
406         }
407
408         /// <summary>
409         /// Boolean flag of group feature existence.
410         /// </summary>
411         [EditorBrowsable(EditorBrowsableState.Never)]
412         public bool IsGrouped
413         {
414             get => isGrouped;
415             set
416             {
417                 isGrouped = value;
418                 needInitalizeLayouter = true;
419                 //Need to re-intialize Internal Item Source.
420                 if (InternalItemSource != null)
421                 {
422                     InternalItemSource.Dispose();
423                     InternalItemSource = null;
424                 }
425                 if (ItemsSource != null)
426                     InternalItemSource = ItemsSourceFactory.Create(this);
427                 Init();
428             }
429         }
430
431         /// <summary>
432         ///  DataTemplate of group header. Group feature is not supported yet.
433         /// </summary>
434         [EditorBrowsable(EditorBrowsableState.Never)]
435         public DataTemplate GroupHeaderTemplate
436         {
437             get
438             {
439                 return groupHeaderTemplate;
440             }
441             set
442             {
443                 groupHeaderTemplate = value;
444                 needInitalizeLayouter = true;
445                 Init();
446             }
447         }
448
449         /// <summary>
450         /// DataTemplate of group footer. Group feature is not supported yet.
451         /// </summary>
452         [EditorBrowsable(EditorBrowsableState.Never)]
453         public DataTemplate GroupFooterTemplate
454         {
455             get
456             {
457                 return groupFooterTemplate;
458             }
459             set
460             {
461                 groupFooterTemplate = value;
462                 needInitalizeLayouter = true;
463                 Init();
464             }
465         }
466
467
468         /// <summary>
469         /// Internal encapsulated items data source.
470         /// </summary>
471         internal new IGroupableItemSource InternalItemSource
472         {
473             get
474             {
475                 return (base.InternalItemSource as IGroupableItemSource);
476             }
477             set
478             {
479                 base.InternalItemSource = value;
480             }
481         }
482         /// <inheritdoc/>
483         [EditorBrowsable(EditorBrowsableState.Never)]
484         public override void OnRelayout(Vector2 size, RelayoutContainer container)
485         {
486             base.OnRelayout(size, container);
487
488             wasRelayouted = true;
489             if (needInitalizeLayouter) Init();
490         }
491
492         /// <inheritdoc/>
493         [EditorBrowsable(EditorBrowsableState.Never)]
494         public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
495         {
496             View nextFocusedView = null;
497
498             if (focusedView == null)
499             {
500                 // If focusedView is null, find child which has previous data index
501                 if (ContentContainer.Children.Count > 0 && InternalItemSource.Count > 0)
502                 {
503                     for (int i = 0; i < ContentContainer.Children.Count; i++)
504                     {
505                         RecyclerViewItem item = Children[i] as RecyclerViewItem;
506                         if (item?.Index == prevFocusedDataIndex)
507                         {
508                             nextFocusedView = item;
509                             break;
510                         }
511                     }
512                 }
513             }
514             else
515             {
516                 // If this is not first focus, request next focus to Layouter
517                 nextFocusedView = ItemsLayouter.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
518             }
519
520             if (nextFocusedView != null)
521             {
522                 // Check next focused view is inside of visible area.
523                 // If it is not, move scroll position to make it visible.
524                 Position scrollPosition = ContentContainer.CurrentPosition;
525                 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
526
527                 float left = nextFocusedView.Position.X;
528                 float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
529                 float top = nextFocusedView.Position.Y;
530                 float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
531
532                 float visibleRectangleLeft = -scrollPosition.X;
533                 float visibleRectangleRight = -scrollPosition.X + Size.Width;
534                 float visibleRectangleTop = -scrollPosition.Y;
535                 float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
536
537                 if (ScrollingDirection == Direction.Horizontal)
538                 {
539                     if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
540                     {
541                         targetPosition = left;
542                     }
543                     else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
544                     {
545                         targetPosition = right - Size.Width;
546                     }
547                 }
548                 else
549                 {
550                     if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
551                     {
552                         targetPosition = top;
553                     }
554                     else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
555                     {
556                         targetPosition = bottom - Size.Height;
557                     }
558                 }
559
560                 focusedView = nextFocusedView;
561                 prevFocusedDataIndex = (nextFocusedView as RecyclerViewItem)?.Index ?? -1;
562
563                 ScrollTo(targetPosition, true);
564             }
565             else
566             {
567                 // If nextView is null, it means that we should move focus to outside of Control.
568                 // Return FocusableView depending on direction.
569                 switch (direction)
570                 {
571                     case View.FocusDirection.Left:
572                         {
573                             nextFocusedView = LeftFocusableView;
574                             break;
575                         }
576                     case View.FocusDirection.Right:
577                         {
578                             nextFocusedView = RightFocusableView;
579                             break;
580                         }
581                     case View.FocusDirection.Up:
582                         {
583                             nextFocusedView = UpFocusableView;
584                             break;
585                         }
586                     case View.FocusDirection.Down:
587                         {
588                             nextFocusedView = DownFocusableView;
589                             break;
590                         }
591                 }
592
593                 if (nextFocusedView != null)
594                 {
595                     focusedView = null;
596                 }
597                 else
598                 {
599                     //If FocusableView doesn't exist, not move focus.
600                     nextFocusedView = focusedView;
601                 }
602             }
603
604             return nextFocusedView;
605         }
606
607         /// <summary>
608         /// Update selected items list in multiple selection.
609         /// </summary>
610         /// <param name="newSelection">updated selection list by user</param>
611         [EditorBrowsable(EditorBrowsableState.Never)]
612         public void UpdateSelectedItems(IList<object> newSelection)
613         {
614             var oldSelection = new List<object>(SelectedItems);
615
616             suppressSelectionChangeNotification = true;
617
618             SelectedItems.Clear();
619
620             if (newSelection?.Count > 0)
621             {
622                 for (int n = 0; n < newSelection.Count; n++)
623                 {
624                     SelectedItems.Add(newSelection[n]);
625                 }
626             }
627
628             suppressSelectionChangeNotification = false;
629
630             SelectedItemsPropertyChanged(oldSelection, newSelection);
631         }
632
633         /// <summary>
634         /// Scroll to specific position with or without animation.
635         /// </summary>
636         /// <param name="position">Destination.</param>
637         /// <param name="animate">Scroll with or without animation</param>
638         [EditorBrowsable(EditorBrowsableState.Never)]
639         public new void ScrollTo(float position, bool animate) => base.ScrollTo(position, animate);
640
641         /// <summary>
642         /// Scroll to specific item's aligned position with or without animation.
643         /// </summary>
644         /// <param name="index">Target item index of dataset.</param>
645         /// <param name="animate">Boolean flag of animation.</param>
646         /// <param name="align">Align state of item. see details in ItemScrollTo.</param>
647         [EditorBrowsable(EditorBrowsableState.Never)]
648         public virtual void ScrollTo(int index, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
649         {
650             if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
651
652             float scrollPos, curPos, curSize, curItemSize;
653             (float x, float y) = ItemsLayouter.GetItemPosition(index);
654             (float width, float height) = ItemsLayouter.GetItemSize(index);
655             if (ScrollingDirection == Direction.Horizontal)
656             {
657                 scrollPos = x;
658                 curPos = ScrollPosition.X;
659                 curSize = Size.Width;
660                 curItemSize = width;
661             }
662             else
663             {
664                 scrollPos = y;
665                 curPos = ScrollPosition.Y;
666                 curSize = Size.Height;
667                 curItemSize = height;
668             }
669
670             //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalItemSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
671             switch (align)
672             {
673                 case ItemScrollTo.Start:
674                     //nothing necessary.
675                     break;
676                 case ItemScrollTo.Center:
677                     scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
678                     break;
679                 case ItemScrollTo.End:
680                     scrollPos = scrollPos - curSize + curItemSize;
681                     break;
682                 case ItemScrollTo.Nearest:
683                     if (scrollPos < curPos - curItemSize)
684                     {
685                         // item is placed before the current screen. scrollTo.Top
686                     }
687                     else if (scrollPos >= curPos + curSize + curItemSize)
688                     {
689                         // item is placed after the current screen. scrollTo.End
690                         scrollPos = scrollPos - curSize + curItemSize;
691                     }
692                     else
693                     {
694                         // item is in the scroller. ScrollTo() is ignored.
695                         return;
696                     }
697                     break;
698             }
699
700             //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
701             base.ScrollTo(scrollPos, animate);
702         }
703
704         /// <summary>
705         /// Apply style to CollectionView
706         /// </summary>
707         /// <param name="viewStyle">The style to apply.</param>
708         [EditorBrowsable(EditorBrowsableState.Never)]
709         public override void ApplyStyle(ViewStyle viewStyle)
710         {
711             base.ApplyStyle(viewStyle);
712             if (viewStyle != null)
713             {
714                 //Extension = RecyclerViewItemStyle.CreateExtension();
715             }
716             if (itemsLayouter != null)
717             {
718                 string styleName = "Tizen.NUI.Compoenents." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
719                 using (ViewStyle layouterStyle = ThemeManager.GetStyle(styleName))
720                 {
721                     itemsLayouter.Padding = new Extents(layouterStyle.Padding);
722                 }
723             }
724         }
725
726         // Realize and Decorate the item.
727         internal override RecyclerViewItem RealizeItem(int index)
728         {
729             if (index == 0 && Header != null)
730             {
731                 Header.Show();
732                 return Header;
733             }
734
735             if (index == InternalItemSource.Count - 1 && Footer != null)
736             {
737                 Footer.Show();
738                 return Footer;
739             }
740
741             if (isGrouped)
742             {
743                 var context = InternalItemSource.GetItem(index);
744                 if (InternalItemSource.IsGroupHeader(index))
745                 {
746                     DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
747
748                     RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
749                     if (groupHeader == null)
750                     {
751                         groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
752
753                         groupHeader.ParentItemsView = this;
754                         groupHeader.Template = templ;
755                         groupHeader.isGroupHeader = true;
756                         groupHeader.isGroupFooter = false;
757                         ContentContainer.Add(groupHeader);
758                     }
759                     groupHeader.Index = index;
760                     groupHeader.ParentGroup = context;
761                     groupHeader.BindingContext = context;
762                     //group selection?
763                     return groupHeader;
764                 }
765                 else if (InternalItemSource.IsGroupFooter(index))
766                 {
767                     DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
768
769                     RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
770                     if (groupFooter == null)
771                     {
772                         groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this);
773
774                         groupFooter.ParentItemsView = this;
775                         groupFooter.Template = templ;
776                         groupFooter.isGroupHeader = false;
777                         groupFooter.isGroupFooter = true;
778                         ContentContainer.Add(groupFooter);
779                     }
780                     groupFooter.Index = index;
781                     groupFooter.ParentGroup = context;
782                     groupFooter.BindingContext = context;
783
784                     //group selection?
785                     return groupFooter;
786                 }
787             }
788
789             RecyclerViewItem item = base.RealizeItem(index);
790             if (item != null)
791             {
792                 if (isGrouped)
793                 {
794                     item.ParentGroup = InternalItemSource.GetGroupParent(index);
795                 }
796
797                 switch (SelectionMode)
798                 {
799                     case ItemSelectionMode.SingleSelection:
800                         if (item.BindingContext != null && item.BindingContext == SelectedItem)
801                         {
802                             item.IsSelected = true;
803                         }
804                         break;
805
806                     case ItemSelectionMode.MultipleSelections:
807                         if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
808                         {
809                             item.IsSelected = true;
810                         }
811                         break;
812                     case ItemSelectionMode.None:
813                         item.IsSelectable = false;
814                         break;
815                 }
816             }
817             return item;
818         }
819
820         // Unrealize and caching the item.
821         internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
822         {
823             if (item == Header)
824             {
825                 item.Hide();
826                 return;
827             }
828             if (item == Footer)
829             {
830                 item.Hide();
831                 return;
832             }
833             if (item.isGroupHeader || item.isGroupFooter)
834             {
835                 if (!recycle || !PushRecycleGroupCache(item))
836                     Utility.Dispose(item);
837                 return;
838             }
839
840             item.IsSelected = false;
841             base.UnrealizeItem(item, recycle);
842         }
843
844         internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
845         {
846             if (suppressSelectionChangeNotification)
847             {
848                 return;
849             }
850
851             foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
852             {
853                 if (item.BindingContext == null) continue;
854                 if (newSelection.Contains(item.BindingContext))
855                 {
856                     if (!item.IsSelected) item.IsSelected = true;
857                 }
858                 else
859                 {
860                     if (item.IsSelected) item.IsSelected = false;
861                 }
862             }
863             SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
864
865             OnPropertyChanged(SelectedItemsProperty.PropertyName);
866         }
867
868         /// <summary>
869         /// Internal selection callback.
870         /// </summary>
871         [EditorBrowsable(EditorBrowsableState.Never)]
872         protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
873         {
874             //Selection Callback
875         }
876
877         /// <summary>
878         /// Adjust scrolling position by own scrolling rules.
879         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
880         /// </summary>
881         /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
882         /// <returns>Adjusted scroll destination</returns>
883         [EditorBrowsable(EditorBrowsableState.Never)]
884         protected override float AdjustTargetPositionOfScrollAnimation(float position)
885         {
886             // Destination is depending on implementation of layout manager.
887             // Get destination from layout manager.
888             return ItemsLayouter.CalculateCandidateScrollPosition(position);
889         }
890
891         /// <summary>
892         /// OnScroll event callback.
893         /// </summary>
894         /// <param name="source">Scroll source object</param>
895         /// <param name="args">Scroll event argument</param>
896         [EditorBrowsable(EditorBrowsableState.Never)]
897         protected override void OnScrolling(object source, ScrollEventArgs args)
898         {
899             if (disposed) return;
900
901             if (needInitalizeLayouter)
902             {
903                 ItemsLayouter.Initialize(this);
904                 needInitalizeLayouter = false;
905             }
906             base.OnScrolling(source, args);
907         }
908
909         /// <summary>
910         /// Dispose ItemsView and all children on it.
911         /// </summary>
912         /// <param name="type">Dispose type.</param>
913         protected override void Dispose(DisposeTypes type)
914         {
915             if (disposed)
916             {
917                 return;
918             }
919
920             if (type == DisposeTypes.Explicit)
921             {
922                 disposed = true;
923                 if (InternalItemSource != null)
924                 {
925                     InternalItemSource.Dispose();
926                     InternalItemSource = null;
927                 }
928                 if (Header != null)
929                 {
930                     Utility.Dispose(Header);
931                     Header = null;
932                 }
933                 if (Footer != null)
934                 {
935                     Utility.Dispose(Footer);
936                     Footer = null;
937                 }
938                 groupHeaderTemplate = null;
939                 groupFooterTemplate = null;
940                 //
941             }
942
943             base.Dispose(type);
944         }
945
946         private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
947         {
948             var command = colView.SelectionChangedCommand;
949
950             if (command != null)
951             {
952                 var commandParameter = colView.SelectionChangedCommandParameter;
953
954                 if (command.CanExecute(commandParameter))
955                 {
956                     command.Execute(commandParameter);
957                 }
958             }
959             colView.SelectionChanged?.Invoke(colView, args);
960             colView.OnSelectionChanged(args);
961         }
962
963         private static object CoerceSelectedItems(BindableObject bindable, object value)
964         {
965             if (value == null)
966             {
967                 return new SelectionList((CollectionView)bindable);
968             }
969
970             if (value is SelectionList)
971             {
972                 return value;
973             }
974
975             return new SelectionList((CollectionView)bindable, value as IList<object>);
976         }
977
978         private static void SelectionModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
979         {
980             var colView = (CollectionView)bindable;
981
982             var oldMode = (ItemSelectionMode)oldValue;
983             var newMode = (ItemSelectionMode)newValue;
984
985             IList<object> previousSelection = new List<object>();
986             IList<object> newSelection = new List<object>();
987
988             switch (oldMode)
989             {
990                 case ItemSelectionMode.None:
991                     break;
992                 case ItemSelectionMode.SingleSelection:
993                     if (colView.SelectedItem != null)
994                     {
995                         previousSelection.Add(colView.SelectedItem);
996                     }
997                     break;
998                 case ItemSelectionMode.MultipleSelections:
999                     previousSelection = colView.SelectedItems;
1000                     break;
1001             }
1002
1003             switch (newMode)
1004             {
1005                 case ItemSelectionMode.None:
1006                     break;
1007                 case ItemSelectionMode.SingleSelection:
1008                     if (colView.SelectedItem != null)
1009                     {
1010                         newSelection.Add(colView.SelectedItem);
1011                     }
1012                     break;
1013                 case ItemSelectionMode.MultipleSelections:
1014                     newSelection = colView.SelectedItems;
1015                     break;
1016             }
1017
1018             if (previousSelection.Count == newSelection.Count)
1019             {
1020                 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
1021                 {
1022                     // Both selections are empty or have the same single item; no reason to signal a change
1023                     return;
1024                 }
1025             }
1026
1027             var args = new SelectionChangedEventArgs(previousSelection, newSelection);
1028             SelectionPropertyChanged(colView, args);
1029         }
1030
1031         private void Init()
1032         {
1033             if (ItemsSource == null) return;
1034             if (ItemsLayouter == null) return;
1035             if (ItemTemplate == null) return;
1036
1037             if (disposed) return;
1038             if (needInitalizeLayouter)
1039             {
1040                 if (InternalItemSource == null) return;
1041
1042                 InternalItemSource.HasHeader = (header != null);
1043                 InternalItemSource.HasFooter = (footer != null);
1044             }
1045
1046             if (!wasRelayouted) return;
1047
1048             if (needInitalizeLayouter)
1049             {
1050                 ItemsLayouter.Initialize(this);
1051                 needInitalizeLayouter = false;
1052             }
1053             ItemsLayouter.RequestLayout(0.0f, true);
1054
1055             if (ScrollingDirection == Direction.Horizontal)
1056             {
1057                 ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
1058             }
1059             else
1060             {
1061                 ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
1062             }
1063         }
1064
1065         private bool PushRecycleGroupCache(RecyclerViewItem item)
1066         {
1067             if (item == null) throw new ArgumentNullException(nameof(item));
1068             if (RecycleCache.Count >= 20) return false;
1069             if (item.Template == null) return false;
1070             if (item.isGroupHeader)
1071             {
1072                 recycleGroupHeaderCache.Add(item);
1073             }
1074             else if (item.isGroupFooter)
1075             {
1076                 recycleGroupFooterCache.Add(item);
1077             }
1078             else return false;
1079             item.Hide();
1080             item.Index = -1;
1081             return true;
1082         }
1083
1084         private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1085         {
1086             RecyclerViewItem viewItem = null;
1087
1088             var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1089             for (int i = 0; i < Cache.Count; i++)
1090             {
1091                 viewItem = Cache[i];
1092                 if (Template == viewItem.Template) break;
1093             }
1094
1095             if (viewItem != null)
1096             {
1097                 Cache.Remove(viewItem);
1098                 viewItem.Show();
1099             }
1100             return viewItem;
1101         }
1102
1103     }
1104 }