[NUI] Fix NUI Svace issue (#2806)
[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                 Init();
267             }
268         }
269
270         /// <summary>
271         /// Scrolling direction to display items layout.
272         /// </summary>
273         [EditorBrowsable(EditorBrowsableState.Never)]
274         public new Direction ScrollingDirection
275         {
276             get
277             {
278                 return base.ScrollingDirection;
279             }
280             set
281             {
282                 base.ScrollingDirection = value;
283
284                 if (ScrollingDirection == Direction.Horizontal)
285                 {
286                     ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
287                 }
288                 else
289                 {
290                     ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
291                 }
292             }
293         }
294
295         /// <summary>
296         /// Selected item in single selection.
297         /// </summary>
298         [EditorBrowsable(EditorBrowsableState.Never)]
299         public object SelectedItem
300         {
301             get => GetValue(SelectedItemProperty);
302             set => SetValue(SelectedItemProperty, value);
303         }
304
305         /// <summary>
306         /// Selected items list in multiple selection.
307         /// </summary>
308         [EditorBrowsable(EditorBrowsableState.Never)]
309         public IList<object> SelectedItems
310         {
311             get => (IList<object>)GetValue(SelectedItemsProperty);
312             // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
313         }
314
315         /// <summary>
316         /// Selection mode to handle items selection. See ItemSelectionMode for details.
317         /// </summary>
318         [EditorBrowsable(EditorBrowsableState.Never)]
319         public ItemSelectionMode SelectionMode
320         {
321             get => (ItemSelectionMode)GetValue(SelectionModeProperty);
322             set => SetValue(SelectionModeProperty, value);
323         }
324
325         /// <summary>
326         /// Command of selection changed.
327         /// </summary>
328         [EditorBrowsable(EditorBrowsableState.Never)]
329         public ICommand SelectionChangedCommand { set; get; }
330
331         /// <summary>
332         /// Command parameter of selection changed.
333         /// </summary>
334         [EditorBrowsable(EditorBrowsableState.Never)]
335         public object SelectionChangedCommandParameter { set; get; }
336
337         /// <summary>
338         /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
339         /// </summary>
340         [EditorBrowsable(EditorBrowsableState.Never)]
341         public ItemSizingStrategy SizingStrategy { get; set; }
342
343         /// <summary>
344         /// Header item which placed in top-most position.
345         /// note : internal index and count will be increased.
346         /// </summary>
347         [EditorBrowsable(EditorBrowsableState.Never)]
348         public RecyclerViewItem Header
349         {
350             get => header;
351             set
352             {
353                 if (header != null)
354                 {
355                     //ContentContainer.Remove(header);
356                     Utility.Dispose(header);
357                 }
358                 if (value != null)
359                 {
360                     value.Index = 0;
361                     value.ParentItemsView = this;
362                     value.IsHeader = true;
363                     ContentContainer.Add(value);
364                 }
365                 header = value;
366                 needInitalizeLayouter = true;
367                 Init();
368             }
369         }
370
371         /// <summary>
372         /// Footer item which placed in bottom-most position.
373         /// note : internal count will be increased.
374         /// </summary>
375         [EditorBrowsable(EditorBrowsableState.Never)]
376         public RecyclerViewItem Footer
377         {
378             get => footer;
379             set
380             {
381                 if (footer != null)
382                 {
383                     //ContentContainer.Remove(footer);
384                     Utility.Dispose(footer);
385                 }
386                 if (value != null)
387                 {
388                     value.Index = InternalItemSource?.Count ?? 0;
389                     value.ParentItemsView = this;
390                     value.IsFooter = true;
391                     ContentContainer.Add(value);
392                 }
393                 footer = value;
394                 needInitalizeLayouter = true;
395                 Init();
396             }
397         }
398
399         /// <summary>
400         /// Boolean flag of group feature existence.
401         /// </summary>
402         [EditorBrowsable(EditorBrowsableState.Never)]
403         public bool IsGrouped
404         {
405             get => isGrouped;
406             set
407             {
408                 isGrouped = value;
409                 needInitalizeLayouter = true;
410                 //Need to re-intialize Internal Item Source.
411                 if (InternalItemSource != null)
412                 {
413                     InternalItemSource.Dispose();
414                     InternalItemSource = null;
415                 }
416                 if (ItemsSource != null)
417                     InternalItemSource = ItemsSourceFactory.Create(this);
418                 Init();
419             }
420         }
421
422         /// <summary>
423         ///  DataTemplate of group header. Group feature is not supported yet.
424         /// </summary>
425         [EditorBrowsable(EditorBrowsableState.Never)]
426         public DataTemplate GroupHeaderTemplate
427         {
428             get
429             {
430                 return groupHeaderTemplate;
431             }
432             set
433             {
434                 groupHeaderTemplate = value;
435                 needInitalizeLayouter = true;
436                 Init();
437             }
438         }
439
440         /// <summary>
441         /// DataTemplate of group footer. Group feature is not supported yet.
442         /// </summary>
443         [EditorBrowsable(EditorBrowsableState.Never)]
444         public DataTemplate GroupFooterTemplate
445         {
446             get
447             {
448                 return groupFooterTemplate;
449             }
450             set
451             {
452                 groupFooterTemplate = value;
453                 needInitalizeLayouter = true;
454                 Init();
455             }
456         }
457
458
459         /// <summary>
460         /// Internal encapsulated items data source.
461         /// </summary>
462         internal new IGroupableItemSource InternalItemSource
463         {
464             get
465             {
466                 return (base.InternalItemSource as IGroupableItemSource);
467             }
468             set
469             {
470                 base.InternalItemSource = value;
471             }
472         }
473         /// <inheritdoc/>
474         [EditorBrowsable(EditorBrowsableState.Never)]
475         public override void OnRelayout(Vector2 size, RelayoutContainer container)
476         {
477             base.OnRelayout(size, container);
478
479             wasRelayouted = true;
480             if (needInitalizeLayouter) Init();
481         }
482
483         /// <inheritdoc/>
484         [EditorBrowsable(EditorBrowsableState.Never)]
485         public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
486         {
487             View nextFocusedView = null;
488
489             if (focusedView == null)
490             {
491                 // If focusedView is null, find child which has previous data index
492                 if (ContentContainer.Children.Count > 0 && InternalItemSource.Count > 0)
493                 {
494                     for (int i = 0; i < ContentContainer.Children.Count; i++)
495                     {
496                         RecyclerViewItem item = Children[i] as RecyclerViewItem;
497                         if (item?.Index == prevFocusedDataIndex)
498                         {
499                             nextFocusedView = item;
500                             break;
501                         }
502                     }
503                 }
504             }
505             else
506             {
507                 // If this is not first focus, request next focus to Layouter
508                 nextFocusedView = ItemsLayouter.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
509             }
510
511             if (nextFocusedView != null)
512             {
513                 // Check next focused view is inside of visible area.
514                 // If it is not, move scroll position to make it visible.
515                 Position scrollPosition = ContentContainer.CurrentPosition;
516                 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
517
518                 float left = nextFocusedView.Position.X;
519                 float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
520                 float top = nextFocusedView.Position.Y;
521                 float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
522
523                 float visibleRectangleLeft = -scrollPosition.X;
524                 float visibleRectangleRight = -scrollPosition.X + Size.Width;
525                 float visibleRectangleTop = -scrollPosition.Y;
526                 float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
527
528                 if (ScrollingDirection == Direction.Horizontal)
529                 {
530                     if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
531                     {
532                         targetPosition = left;
533                     }
534                     else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
535                     {
536                         targetPosition = right - Size.Width;
537                     }
538                 }
539                 else
540                 {
541                     if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
542                     {
543                         targetPosition = top;
544                     }
545                     else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
546                     {
547                         targetPosition = bottom - Size.Height;
548                     }
549                 }
550
551                 focusedView = nextFocusedView;
552                 prevFocusedDataIndex = (nextFocusedView as RecyclerViewItem)?.Index ?? -1;
553
554                 ScrollTo(targetPosition, true);
555             }
556             else
557             {
558                 // If nextView is null, it means that we should move focus to outside of Control.
559                 // Return FocusableView depending on direction.
560                 switch (direction)
561                 {
562                     case View.FocusDirection.Left:
563                         {
564                             nextFocusedView = LeftFocusableView;
565                             break;
566                         }
567                     case View.FocusDirection.Right:
568                         {
569                             nextFocusedView = RightFocusableView;
570                             break;
571                         }
572                     case View.FocusDirection.Up:
573                         {
574                             nextFocusedView = UpFocusableView;
575                             break;
576                         }
577                     case View.FocusDirection.Down:
578                         {
579                             nextFocusedView = DownFocusableView;
580                             break;
581                         }
582                 }
583
584                 if (nextFocusedView != null)
585                 {
586                     focusedView = null;
587                 }
588                 else
589                 {
590                     //If FocusableView doesn't exist, not move focus.
591                     nextFocusedView = focusedView;
592                 }
593             }
594
595             return nextFocusedView;
596         }
597
598         /// <summary>
599         /// Update selected items list in multiple selection.
600         /// </summary>
601         /// <param name="newSelection">updated selection list by user</param>
602         [EditorBrowsable(EditorBrowsableState.Never)]
603         public void UpdateSelectedItems(IList<object> newSelection)
604         {
605             var oldSelection = new List<object>(SelectedItems);
606
607             suppressSelectionChangeNotification = true;
608
609             SelectedItems.Clear();
610
611             if (newSelection?.Count > 0)
612             {
613                 for (int n = 0; n < newSelection.Count; n++)
614                 {
615                     SelectedItems.Add(newSelection[n]);
616                 }
617             }
618
619             suppressSelectionChangeNotification = false;
620
621             SelectedItemsPropertyChanged(oldSelection, newSelection);
622         }
623
624         /// <summary>
625         /// Scroll to specific position with or without animation.
626         /// </summary>
627         /// <param name="position">Destination.</param>
628         /// <param name="animate">Scroll with or without animation</param>
629         [EditorBrowsable(EditorBrowsableState.Never)]
630         public new void ScrollTo(float position, bool animate) => base.ScrollTo(position, animate);
631
632         /// <summary>
633         /// Scroll to specific item's aligned position with or without animation.
634         /// </summary>
635         /// <param name="item">Target item of dataset.</param>
636         /// <param name="animate">Boolean flag of animation.</param>
637         /// <param name="align">Align state of item. see details in ItemScrollTo.</param>
638         [EditorBrowsable(EditorBrowsableState.Never)]
639         public virtual void ScrollTo(object item, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
640         {
641             if (item == null) throw new ArgumentNullException(nameof(item));
642             if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
643
644             if (InternalItemSource.GetPosition(item) == -1)
645             {
646                 throw new Exception("ScrollTo parameter item is not a member of ItemsSource");
647             }
648
649             float scrollPos, curPos, curSize, curItemSize;
650             (float x, float y) = ItemsLayouter.GetItemPosition(item);
651             (float width, float height) = ItemsLayouter.GetItemSize(item);
652             if (ScrollingDirection == Direction.Horizontal)
653             {
654                 scrollPos = x;
655                 curPos = ScrollPosition.X;
656                 curSize = Size.Width;
657                 curItemSize = width;
658             }
659             else
660             {
661                 scrollPos = y;
662                 curPos = ScrollPosition.Y;
663                 curSize = Size.Height;
664                 curItemSize = height;
665             }
666
667             //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalItemSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
668             switch (align)
669             {
670                 case ItemScrollTo.Start:
671                     //nothing necessary.
672                     break;
673                 case ItemScrollTo.Center:
674                     scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
675                     break;
676                 case ItemScrollTo.End:
677                     scrollPos = scrollPos - curSize + curItemSize;
678                     break;
679                 case ItemScrollTo.Nearest:
680                     if (scrollPos < curPos - curItemSize)
681                     {
682                         // item is placed before the current screen. scrollTo.Top
683                     }
684                     else if (scrollPos >= curPos + curSize + curItemSize)
685                     {
686                         // item is placed after the current screen. scrollTo.End
687                         scrollPos = scrollPos - curSize + curItemSize;
688                     }
689                     else
690                     {
691                         // item is in the scroller. ScrollTo() is ignored.
692                         return;
693                     }
694                     break;
695             }
696
697             //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
698             base.ScrollTo(scrollPos, animate);
699         }
700
701         // Realize and Decorate the item.
702         internal override RecyclerViewItem RealizeItem(int index)
703         {
704             if (index == 0 && Header != null)
705             {
706                 Header.Show();
707                 return Header;
708             }
709
710             if (index == InternalItemSource.Count - 1 && Footer != null)
711             {
712                 Footer.Show();
713                 return Footer;
714             }
715
716             if (isGrouped)
717             {
718                 var context = InternalItemSource.GetItem(index);
719                 if (InternalItemSource.IsGroupHeader(index))
720                 {
721                     DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
722
723                     RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
724                     if (groupHeader == null)
725                     {
726                         groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
727
728                         groupHeader.ParentItemsView = this;
729                         groupHeader.Template = templ;
730                         groupHeader.isGroupHeader = true;
731                         groupHeader.isGroupFooter = false;
732                         ContentContainer.Add(groupHeader);
733                     }
734                     groupHeader.Index = index;
735                     groupHeader.ParentGroup = context;
736                     groupHeader.BindingContext = context;
737                     //group selection?
738                     return groupHeader;
739                 }
740                 else if (InternalItemSource.IsGroupFooter(index))
741                 {
742                     DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
743
744                     RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
745                     if (groupFooter == null)
746                     {
747                         groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this);
748
749                         groupFooter.ParentItemsView = this;
750                         groupFooter.Template = templ;
751                         groupFooter.isGroupHeader = false;
752                         groupFooter.isGroupFooter = true;
753                         ContentContainer.Add(groupFooter);
754                     }
755                     groupFooter.Index = index;
756                     groupFooter.ParentGroup = context;
757                     groupFooter.BindingContext = context;
758
759                     //group selection?
760                     return groupFooter;
761                 }
762             }
763
764             RecyclerViewItem item = base.RealizeItem(index);
765             if (item != null)
766             {
767                 if (isGrouped)
768                 {
769                     item.ParentGroup = InternalItemSource.GetGroupParent(index);
770                 }
771
772                 switch (SelectionMode)
773                 {
774                     case ItemSelectionMode.SingleSelection:
775                         if (item.BindingContext == SelectedItem) item.IsSelected = true;
776                         break;
777
778                     case ItemSelectionMode.MultipleSelections:
779                         if (SelectedItems?.Contains(item.BindingContext) ?? false) item.IsSelected = true;
780                         break;
781                     case ItemSelectionMode.None:
782                         item.IsSelectable = false;
783                         break;
784                 }
785             }
786             return item;
787         }
788
789         // Unrealize and caching the item.
790         internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
791         {
792             if (item == Header)
793             {
794                 item.Hide();
795                 return;
796             }
797             if (item == Footer)
798             {
799                 item.Hide();
800                 return;
801             }
802             if (item.isGroupHeader || item.isGroupFooter)
803             {
804                 if (!recycle || !PushRecycleGroupCache(item))
805                     Utility.Dispose(item);
806                 return;
807             }
808
809             item.IsSelected = false;
810             base.UnrealizeItem(item, recycle);
811         }
812
813         internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
814         {
815             if (suppressSelectionChangeNotification)
816             {
817                 return;
818             }
819
820             foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
821             {
822                 if (item.BindingContext == null) continue;
823                 if (newSelection.Contains(item.BindingContext))
824                 {
825                     if (!item.IsSelected) item.IsSelected = true;
826                 }
827                 else
828                 {
829                     if (item.IsSelected) item.IsSelected = false;
830                 }
831             }
832             SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
833
834             OnPropertyChanged(SelectedItemsProperty.PropertyName);
835         }
836
837         /// <summary>
838         /// Internal selection callback.
839         /// </summary>
840         [EditorBrowsable(EditorBrowsableState.Never)]
841         protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
842         {
843             //Selection Callback
844         }
845
846         /// <summary>
847         /// Adjust scrolling position by own scrolling rules.
848         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
849         /// </summary>
850         /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
851         /// <returns>Adjusted scroll destination</returns>
852         [EditorBrowsable(EditorBrowsableState.Never)]
853         protected override float AdjustTargetPositionOfScrollAnimation(float position)
854         {
855             // Destination is depending on implementation of layout manager.
856             // Get destination from layout manager.
857             return ItemsLayouter.CalculateCandidateScrollPosition(position);
858         }
859
860         /// <summary>
861         /// OnScroll event callback.
862         /// </summary>
863         /// <param name="source">Scroll source object</param>
864         /// <param name="args">Scroll event argument</param>
865         [EditorBrowsable(EditorBrowsableState.Never)]
866         protected override void OnScrolling(object source, ScrollEventArgs args)
867         {
868             if (disposed) return;
869
870             if (needInitalizeLayouter)
871             {
872                 ItemsLayouter.Initialize(this);
873                 needInitalizeLayouter = false;
874             }
875             base.OnScrolling(source, args);
876         }
877
878         /// <summary>
879         /// Dispose ItemsView and all children on it.
880         /// </summary>
881         /// <param name="type">Dispose type.</param>
882         protected override void Dispose(DisposeTypes type)
883         {
884             if (disposed)
885             {
886                 return;
887             }
888
889             if (type == DisposeTypes.Explicit)
890             {
891                 disposed = true;
892                 if (InternalItemSource != null)
893                 {
894                     InternalItemSource.Dispose();
895                     InternalItemSource = null;
896                 }
897                 if (Header != null)
898                 {
899                     Utility.Dispose(Header);
900                     Header = null;
901                 }
902                 if (Footer != null)
903                 {
904                     Utility.Dispose(Footer);
905                     Footer = null;
906                 }
907                 groupHeaderTemplate = null;
908                 groupFooterTemplate = null;
909                 //
910             }
911
912             base.Dispose(type);
913         }
914
915         private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
916         {
917             var command = colView.SelectionChangedCommand;
918
919             if (command != null)
920             {
921                 var commandParameter = colView.SelectionChangedCommandParameter;
922
923                 if (command.CanExecute(commandParameter))
924                 {
925                     command.Execute(commandParameter);
926                 }
927             }
928             colView.SelectionChanged?.Invoke(colView, args);
929             colView.OnSelectionChanged(args);
930         }
931
932         private static object CoerceSelectedItems(BindableObject bindable, object value)
933         {
934             if (value == null)
935             {
936                 return new SelectionList((CollectionView)bindable);
937             }
938
939             if (value is SelectionList)
940             {
941                 return value;
942             }
943
944             return new SelectionList((CollectionView)bindable, value as IList<object>);
945         }
946
947         private static void SelectionModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
948         {
949             var colView = (CollectionView)bindable;
950
951             var oldMode = (ItemSelectionMode)oldValue;
952             var newMode = (ItemSelectionMode)newValue;
953
954             IList<object> previousSelection = new List<object>();
955             IList<object> newSelection = new List<object>();
956
957             switch (oldMode)
958             {
959                 case ItemSelectionMode.None:
960                     break;
961                 case ItemSelectionMode.SingleSelection:
962                     if (colView.SelectedItem != null)
963                     {
964                         previousSelection.Add(colView.SelectedItem);
965                     }
966                     break;
967                 case ItemSelectionMode.MultipleSelections:
968                     previousSelection = colView.SelectedItems;
969                     break;
970             }
971
972             switch (newMode)
973             {
974                 case ItemSelectionMode.None:
975                     break;
976                 case ItemSelectionMode.SingleSelection:
977                     if (colView.SelectedItem != null)
978                     {
979                         newSelection.Add(colView.SelectedItem);
980                     }
981                     break;
982                 case ItemSelectionMode.MultipleSelections:
983                     newSelection = colView.SelectedItems;
984                     break;
985             }
986
987             if (previousSelection.Count == newSelection.Count)
988             {
989                 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
990                 {
991                     // Both selections are empty or have the same single item; no reason to signal a change
992                     return;
993                 }
994             }
995
996             var args = new SelectionChangedEventArgs(previousSelection, newSelection);
997             SelectionPropertyChanged(colView, args);
998         }
999
1000         private void Init()
1001         {
1002             if (ItemsSource == null) return;
1003             if (ItemsLayouter == null) return;
1004             if (ItemTemplate == null) return;
1005
1006             if (disposed) return;
1007             if (needInitalizeLayouter)
1008             {
1009                 if (InternalItemSource == null) return;
1010
1011                 InternalItemSource.HasHeader = (header != null);
1012                 InternalItemSource.HasFooter = (footer != null);
1013             }
1014
1015             if (!wasRelayouted) return;
1016
1017             if (needInitalizeLayouter)
1018             {
1019                 ItemsLayouter.Initialize(this);
1020                 needInitalizeLayouter = false;
1021             }
1022             ItemsLayouter.RequestLayout(0.0f, true);
1023
1024             if (ScrollingDirection == Direction.Horizontal)
1025             {
1026                 ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
1027             }
1028             else
1029             {
1030                 ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
1031             }
1032         }
1033
1034         private bool PushRecycleGroupCache(RecyclerViewItem item)
1035         {
1036             if (item == null) throw new ArgumentNullException(nameof(item));
1037             if (RecycleCache.Count >= 20) return false;
1038             if (item.Template == null) return false;
1039             if (item.isGroupHeader)
1040             {
1041                 recycleGroupHeaderCache.Add(item);
1042             }
1043             else if (item.isGroupFooter)
1044             {
1045                 recycleGroupFooterCache.Add(item);
1046             }
1047             else return false;
1048             item.Hide();
1049             item.Index = -1;
1050             return true;
1051         }
1052
1053         private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1054         {
1055             RecyclerViewItem viewItem = null;
1056
1057             var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1058             for (int i = 0; i < Cache.Count; i++)
1059             {
1060                 viewItem = Cache[i];
1061                 if (Template == viewItem.Template) break;
1062             }
1063
1064             if (viewItem != null)
1065             {
1066                 Cache.Remove(viewItem);
1067                 viewItem.Show();
1068             }
1069             return viewItem;
1070         }
1071
1072     }
1073 }