[NUI] Fix some SVACE issues.
[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.Collections.Specialized;
21 using System.Windows.Input;
22 using System.ComponentModel;
23 using Tizen.NUI.BaseComponents;
24 using Tizen.NUI.Binding;
25
26 namespace Tizen.NUI.Components
27 {
28     /// <summary>
29     /// Selectable RecyclerView that presenting a collection of items with variable layouters.
30     /// </summary>
31     /// <since_tizen> 9 </since_tizen>
32     public partial class CollectionView : RecyclerView
33     {
34         /// <summary>
35         /// Binding Property of selected item in single selection.
36         /// </summary>
37         /// <since_tizen> 9 </since_tizen>
38         public static readonly BindableProperty SelectedItemProperty =
39             BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(CollectionView), null,
40                 propertyChanged: (bindable, oldValue, newValue) =>
41                 {
42                     var colView = bindable as CollectionView;
43                     if (colView == null)
44                     {
45                         throw new Exception("Bindable object is not CollectionView.");
46                     }
47
48                     oldValue = colView.selectedItem;
49                     colView.selectedItem = newValue;
50
51                     var args = new SelectionChangedEventArgs(oldValue, newValue);
52                     foreach (RecyclerViewItem item in colView.ContentContainer.Children.Where((item) => item is RecyclerViewItem))
53                     {
54                         if (item.BindingContext == null)
55                         {
56                             continue;
57                         }
58
59                         if (item.BindingContext == oldValue)
60                         {
61                             item.IsSelected = false;
62                         }
63                         else if (item.BindingContext == newValue)
64                         {
65                             item.IsSelected = true;
66                         }
67                     }
68
69                     SelectionPropertyChanged(colView, args);
70                 },
71                 defaultValueCreator: (bindable) =>
72                 {
73                     var colView = bindable as CollectionView;
74                     if (colView == null)
75                     {
76                         throw new Exception("Bindable object is not CollectionView.");
77                     }
78
79                     return colView.selectedItem;
80                 });
81
82         /// <summary>
83         /// Binding Property of selected items list in multiple selection.
84         /// </summary>
85         /// <since_tizen> 9 </since_tizen>
86         public static readonly BindableProperty SelectedItemsProperty =
87             BindableProperty.Create(nameof(SelectedItems), typeof(IList<object>), typeof(CollectionView), null,
88                 propertyChanged: (bindable, oldValue, newValue) =>
89                 {
90                     var colView = bindable as CollectionView;
91                     if (colView == null)
92                     {
93                         throw new Exception("Bindable object is not CollectionView.");
94                     }
95
96                     var oldSelection = colView.selectedItems ?? selectEmpty;
97                     //FIXME : CoerceSelectedItems calls only isCreatedByXaml
98                     var newSelection = (SelectionList)CoerceSelectedItems(colView, newValue);
99                     colView.selectedItems = newSelection;
100                     colView.SelectedItemsPropertyChanged(oldSelection, newSelection);
101                 },
102                 defaultValueCreator: (bindable) =>
103                 {
104                     var colView = bindable as CollectionView;
105                     if (colView == null)
106                     {
107                         throw new Exception("Bindable object is not CollectionView.");
108                     }
109
110                     colView.selectedItems = colView.selectedItems ?? new SelectionList(colView);
111                     return colView.selectedItems;
112                 });
113
114         /// <summary>
115         /// Binding Property of selected items list in multiple selection.
116         /// </summary>
117         /// <since_tizen> 9 </since_tizen>
118         public static readonly BindableProperty SelectionModeProperty =
119             BindableProperty.Create(nameof(SelectionMode), typeof(ItemSelectionMode), typeof(CollectionView), ItemSelectionMode.None,
120                 propertyChanged: (bindable, oldValue, newValue) =>
121                 {
122                     var colView = bindable as CollectionView;
123                     if (colView == null)
124                     {
125                         throw new Exception("Bindable object is not CollectionView.");
126                     }
127
128                     oldValue = colView.selectionMode;
129                     colView.selectionMode = (ItemSelectionMode)newValue;
130                     SelectionModePropertyChanged(colView, oldValue, newValue);
131                 },
132                 defaultValueCreator: (bindable) =>
133                 {
134                     var colView = bindable as CollectionView;
135                     if (colView == null)
136                     {
137                         throw new Exception("Bindable object is not CollectionView.");
138                     }
139
140                     return colView.selectionMode;
141                 });
142
143
144         private static readonly IList<object> selectEmpty = new List<object>(0);
145         private DataTemplate itemTemplate = null;
146         private IEnumerable itemsSource = null;
147         private ItemsLayouter itemsLayouter = null;
148         private DataTemplate groupHeaderTemplate;
149         private DataTemplate groupFooterTemplate;
150         private bool isGrouped;
151         private bool wasRelayouted = false;
152         private bool needInitalizeLayouter = false;
153         private object selectedItem;
154         private SelectionList selectedItems;
155         private bool suppressSelectionChangeNotification;
156         private ItemSelectionMode selectionMode = ItemSelectionMode.None;
157         private RecyclerViewItem header;
158         private RecyclerViewItem footer;
159         private List<RecyclerViewItem> recycleGroupHeaderCache { get; } = new List<RecyclerViewItem>();
160         private List<RecyclerViewItem> recycleGroupFooterCache { get; } = new List<RecyclerViewItem>();
161         private bool delayedScrollTo;
162         private (float position, bool anim) delayedScrollToParam;
163
164         private bool delayedIndexScrollTo;
165         private (int index, bool anim, ItemScrollTo scrollTo) delayedIndexScrollToParam;
166
167         private void Initialize()
168         {
169             FocusGroup = true;
170             SetKeyboardNavigationSupport(true);
171         }
172
173         /// <summary>
174         /// Base constructor.
175         /// </summary>
176         /// <since_tizen> 9 </since_tizen>
177         public CollectionView() : base()
178         {
179             Initialize();
180         }
181
182         /// <summary>
183         /// Base constructor with ItemsSource
184         /// </summary>
185         /// <param name="itemsSource">item's data source</param>
186         /// <since_tizen> 9 </since_tizen>
187         public CollectionView(IEnumerable itemsSource) : this()
188         {
189             ItemsSource = itemsSource;
190         }
191
192         /// <summary>
193         /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
194         /// </summary>
195         /// <param name="itemsSource">item's data source</param>
196         /// <param name="layouter">item's layout manager</param>
197         /// <param name="template">item's view template with data bindings</param>
198         [EditorBrowsable(EditorBrowsableState.Never)]
199         public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
200         {
201             ItemsSource = itemsSource;
202             ItemTemplate = template;
203             ItemsLayouter = layouter;
204         }
205
206         /// <summary>
207         /// Creates a new instance of a CollectionView with style.
208         /// </summary>
209         /// <param name="style">A style applied to the newly created CollectionView.</param>
210         [EditorBrowsable(EditorBrowsableState.Never)]
211         public CollectionView(ControlStyle style) : base(style)
212         {
213             Initialize();
214         }
215
216         /// <summary>
217         /// Event of Selection changed.
218         /// previous selection list and current selection will be provided.
219         /// </summary>
220         /// <since_tizen> 9 </since_tizen>
221         public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
222
223         /// <summary>
224         /// Align item in the viewport when ScrollTo() calls.
225         /// </summary>
226         /// <since_tizen> 9 </since_tizen>
227         public enum ItemScrollTo
228         {
229             /// <summary>
230             /// Scroll to show item in nearest viewport on scroll direction.
231             /// item is above the scroll viewport, item will be came into front,
232             /// item is under the scroll viewport, item will be came into end,
233             /// item is in the scroll viewport, no scroll.
234             /// </summary>
235             /// <since_tizen> 9 </since_tizen>
236             Nearest,
237             /// <summary>
238             /// Scroll to show item in start of the viewport.
239             /// </summary>
240             /// <since_tizen> 9 </since_tizen>
241             Start,
242             /// <summary>
243             /// Scroll to show item in center of the viewport.
244             /// </summary>
245             /// <since_tizen> 9 </since_tizen>
246             Center,
247             /// <summary>
248             /// Scroll to show item in end of the viewport.
249             /// </summary>
250             /// <since_tizen> 9 </since_tizen>
251             End,
252         }
253
254         /// <summary>
255         /// Item's source data in IEnumerable.
256         /// </summary>
257         /// <since_tizen> 9 </since_tizen>
258         public override IEnumerable ItemsSource
259         {
260             get => GetValue(RecyclerView.ItemsSourceProperty) as IEnumerable;
261             set => SetValue(RecyclerView.ItemsSourceProperty, value);
262         }
263
264         internal override IEnumerable InternalItemsSource
265         {
266             get
267             {
268                 return itemsSource;
269             }
270             set
271             {
272                 if (itemsSource != null)
273                 {
274                     // Clearing old data!
275                     if (itemsSource is INotifyCollectionChanged prevNotifyCollectionChanged)
276                     {
277                         prevNotifyCollectionChanged.CollectionChanged -= CollectionChanged;
278                     }
279                     if (selectedItem != null)
280                     {
281                         selectedItem = null;
282                     }
283                     selectedItems?.Clear();
284                 }
285
286                 itemsSource = value as IEnumerable;
287
288                 if (itemsSource == null)
289                 {
290                     InternalSource?.Dispose();
291                     InternalSource = null;
292                     itemsLayouter?.Clear();
293                     ClearCache();
294                     return;
295                 }
296                 if (itemsSource is INotifyCollectionChanged newNotifyCollectionChanged)
297                 {
298                     newNotifyCollectionChanged.CollectionChanged += CollectionChanged;
299                 }
300
301                 InternalSource?.Dispose();
302                 InternalSource = ItemsSourceFactory.Create(this);
303
304                 if (itemsLayouter == null) return;
305
306                 needInitalizeLayouter = true;
307                 ReinitializeLayout();
308
309             }
310         }
311
312         /// <summary>
313         /// DataTemplate for items.
314         /// Create visual contents and binding properties.
315         /// return object type is restricted RecyclerViewItem.
316         /// <seealso cref="Tizen.NUI.Binding.DataTemplate" />
317         /// </summary>
318         /// <since_tizen> 9 </since_tizen>
319         public override DataTemplate ItemTemplate
320         {
321             get
322             {
323                 return GetValue(ItemTemplateProperty) as DataTemplate;
324             }
325             set
326             {
327                 SetValue(ItemTemplateProperty, value);
328                 NotifyPropertyChanged();
329             }
330         }
331         internal override DataTemplate InternalItemTemplate
332         {
333             get
334             {
335                 return itemTemplate;
336             }
337             set
338             {
339                 itemTemplate = value;
340                 if (value == null)
341                 {
342                     return;
343                 }
344
345                 needInitalizeLayouter = true;
346                 ReinitializeLayout();
347             }
348         }
349
350         /// <summary>
351         /// Items Layouter.
352         /// Layouting items on the scroll ContentContainer.
353         /// <seealso cref="ItemsLayouter" />
354         /// <seealso cref="LinearLayouter" />
355         /// <seealso cref="GridLayouter" />
356         /// </summary>
357         /// <since_tizen> 9 </since_tizen>
358         public virtual ItemsLayouter ItemsLayouter
359         {
360             get
361             {
362                 return GetValue(ItemsLayouterProperty) as ItemsLayouter;
363             }
364             set
365             {
366                 SetValue(ItemsLayouterProperty, value);
367                 NotifyPropertyChanged();
368             }
369         }
370
371
372         /// <inheritdoc/>
373         [EditorBrowsable(EditorBrowsableState.Never)]
374         protected override ItemsLayouter InternalItemsLayouter
375         {
376             get
377             {
378                 return itemsLayouter;
379             }
380             set
381             {
382                 itemsLayouter?.Clear();
383                 ClearCache();
384
385                 itemsLayouter = value;
386                 base.InternalItemsLayouter = itemsLayouter;
387                 if (value == null)
388                 {
389                     needInitalizeLayouter = false;
390                     return;
391                 }
392
393                 needInitalizeLayouter = true;
394
395                 var styleName = "Tizen.NUI.Components." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
396                 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
397                 if (layouterStyle != null)
398                 {
399                     itemsLayouter.Padding = new Extents(layouterStyle.Padding);
400                 }
401                 ReinitializeLayout();
402             }
403         }
404
405         /// <summary>
406         /// Scrolling direction to display items layout.
407         /// </summary>
408         /// <since_tizen> 9 </since_tizen>
409         public new Direction ScrollingDirection
410         {
411             get
412             {
413                 return (Direction)GetValue(ScrollingDirectionProperty);
414             }
415             set
416             {
417                 SetValue(ScrollingDirectionProperty, value);
418                 NotifyPropertyChanged();
419             }
420         }
421         private Direction InternalScrollingDirection
422         {
423             get
424             {
425                 return base.ScrollingDirection;
426             }
427             set
428             {
429                 if (base.ScrollingDirection != value)
430                 {
431                     base.ScrollingDirection = value;
432                     needInitalizeLayouter = true;
433                     ReinitializeLayout();
434                 }
435             }
436         }
437
438         /// <summary>
439         /// Selected item in single selection.
440         /// </summary>
441         /// <since_tizen> 9 </since_tizen>
442         public object SelectedItem
443         {
444             get => GetValue(SelectedItemProperty);
445             set => SetValue(SelectedItemProperty, value);
446         }
447
448         /// <summary>
449         /// Selected items list in multiple selection.
450         /// </summary>
451         /// <since_tizen> 9 </since_tizen>
452         public IList<object> SelectedItems
453         {
454             get => GetValue(SelectedItemsProperty) as IList<object>;
455             // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
456         }
457
458         /// <summary>
459         /// Selection mode to handle items selection. See ItemSelectionMode for details.
460         /// </summary>
461         /// <since_tizen> 9 </since_tizen>
462         public ItemSelectionMode SelectionMode
463         {
464             get => (ItemSelectionMode)GetValue(SelectionModeProperty);
465             set => SetValue(SelectionModeProperty, value);
466         }
467
468         /// <summary>
469         /// Command of selection changed.
470         /// </summary>
471         [EditorBrowsable(EditorBrowsableState.Never)]
472         public ICommand SelectionChangedCommand
473         {
474             get
475             {
476                 return GetValue(SelectionChangedCommandProperty) as ICommand;
477             }
478             set
479             {
480                 SetValue(SelectionChangedCommandProperty, value);
481                 NotifyPropertyChanged();
482             }
483         }
484         private ICommand InternalSelectionChangedCommand { set; get; }
485
486         /// <summary>
487         /// Command parameter of selection changed.
488         /// </summary>
489         [EditorBrowsable(EditorBrowsableState.Never)]
490         public object SelectionChangedCommandParameter
491         {
492             get
493             {
494                 return GetValue(SelectionChangedCommandParameterProperty);
495             }
496             set
497             {
498                 SetValue(SelectionChangedCommandParameterProperty, value);
499                 NotifyPropertyChanged();
500             }
501         }
502         private object InternalSelectionChangedCommandParameter { set; get; }
503
504         /// <summary>
505         /// Header item placed in top-most position.
506         /// </summary>
507         /// <remarks>Please note that, internal index will be increased by header.</remarks>
508         /// <since_tizen> 9 </since_tizen>
509         public RecyclerViewItem Header
510         {
511             get
512             {
513                 return GetValue(HeaderProperty) as RecyclerViewItem;
514             }
515             set
516             {
517                 SetValue(HeaderProperty, value);
518                 NotifyPropertyChanged();
519             }
520         }
521         private RecyclerViewItem InternalHeader
522         {
523             get => header;
524             set
525             {
526                 if (header != null)
527                 {
528                     //ContentContainer.Remove(header);
529                     Utility.Dispose(header);
530                 }
531                 if (value != null)
532                 {
533                     value.Index = 0;
534                     value.ParentItemsView = this;
535                     value.IsHeader = true;
536                     ContentContainer.Add(value);
537                 }
538                 header = value;
539                 if (InternalSource != null)
540                 {
541                     InternalSource.HasHeader = (value != null);
542                 }
543                 needInitalizeLayouter = true;
544                 ReinitializeLayout();
545             }
546         }
547
548         /// <summary>
549         /// Footer item placed in bottom-most position.
550         /// </summary>
551         /// <remarks>Please note that, internal index will be increased by footer.</remarks>
552         /// <since_tizen> 9 </since_tizen>
553         public RecyclerViewItem Footer
554         {
555             get
556             {
557                 return GetValue(FooterProperty) as RecyclerViewItem;
558             }
559             set
560             {
561                 SetValue(FooterProperty, value);
562                 NotifyPropertyChanged();
563             }
564         }
565         private RecyclerViewItem InternalFooter
566         {
567             get => footer;
568             set
569             {
570                 if (footer != null)
571                 {
572                     //ContentContainer.Remove(footer);
573                     Utility.Dispose(footer);
574                 }
575                 if (value != null)
576                 {
577                     value.Index = InternalSource?.Count ?? 0;
578                     value.ParentItemsView = this;
579                     value.IsFooter = true;
580                     ContentContainer.Add(value);
581                 }
582                 footer = value;
583                 if (InternalSource != null)
584                 {
585                     InternalSource.HasFooter = (value != null);
586                 }
587                 needInitalizeLayouter = true;
588                 ReinitializeLayout();
589             }
590         }
591
592         /// <summary>
593         /// Enable groupable view.
594         /// </summary>
595         [EditorBrowsable(EditorBrowsableState.Never)]
596         public bool IsGrouped
597         {
598             get
599             {
600                 return (bool)GetValue(IsGroupedProperty);
601             }
602             set
603             {
604                 SetValue(IsGroupedProperty, value);
605                 NotifyPropertyChanged();
606             }
607         }
608         private bool InternalIsGrouped
609         {
610             get => isGrouped;
611             set
612             {
613                 isGrouped = value;
614                 needInitalizeLayouter = true;
615                 //Need to re-intialize Internal Item Source.
616                 if (InternalSource != null)
617                 {
618                     InternalSource.Dispose();
619                     InternalSource = null;
620                 }
621                 if (ItemsSource != null)
622                 {
623                     InternalSource = ItemsSourceFactory.Create(this);
624                 }
625
626                 ReinitializeLayout();
627             }
628         }
629
630         /// <summary>
631         ///  DataTemplate of group header.
632         /// </summary>
633         /// <remarks>Please note that, internal index will be increased by group header.
634         /// GroupHeaderTemplate is essential for groupable view.</remarks>
635         [EditorBrowsable(EditorBrowsableState.Never)]
636         public DataTemplate GroupHeaderTemplate
637         {
638             get
639             {
640                 return GetValue(GroupHeaderTemplateProperty) as DataTemplate;
641             }
642             set
643             {
644                 SetValue(GroupHeaderTemplateProperty, value);
645                 NotifyPropertyChanged();
646             }
647         }
648         private DataTemplate InternalGroupHeaderTemplate
649         {
650             get
651             {
652                 return groupHeaderTemplate;
653             }
654             set
655             {
656                 groupHeaderTemplate = value;
657                 needInitalizeLayouter = true;
658
659                 //Need to re-intialize Internal Item Source.
660                 if (InternalSource != null)
661                 {
662                     InternalSource.Dispose();
663                     InternalSource = null;
664                 }
665
666                 if (ItemsSource != null)
667                 {
668                     InternalSource = ItemsSourceFactory.Create(this);
669                 }
670
671                 ReinitializeLayout();
672             }
673         }
674
675         /// <summary>
676         /// DataTemplate of group footer. Group feature is not supported yet.
677         /// </summary>
678         /// <remarks>Please note that, internal index will be increased by group footer.</remarks>
679         [EditorBrowsable(EditorBrowsableState.Never)]
680         public DataTemplate GroupFooterTemplate
681         {
682             get
683             {
684                 return GetValue(GroupFooterTemplateProperty) as DataTemplate;
685             }
686             set
687             {
688                 SetValue(GroupFooterTemplateProperty, value);
689                 NotifyPropertyChanged();
690             }
691         }
692         private DataTemplate InternalGroupFooterTemplate
693         {
694             get
695             {
696                 return groupFooterTemplate;
697             }
698             set
699             {
700                 groupFooterTemplate = value;
701                 needInitalizeLayouter = true;
702
703                 //Need to re-intialize Internal Item Source.
704                 if (InternalSource != null)
705                 {
706                     InternalSource.Dispose();
707                     InternalSource = null;
708                 }
709
710                 if (ItemsSource != null)
711                 {
712                     InternalSource = ItemsSourceFactory.Create(this);
713                 }
714
715                 ReinitializeLayout();
716             }
717         }
718
719         /// <summary>
720         /// Internal encapsulated items data source.
721         /// </summary>
722         internal new IGroupableItemSource InternalSource
723         {
724             get
725             {
726                 return (base.InternalSource as IGroupableItemSource);
727             }
728             set
729             {
730                 base.InternalSource = value;
731             }
732         }
733
734         /// <summary>
735         /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
736         /// </summary>
737         [EditorBrowsable(EditorBrowsableState.Never)]
738         internal ItemSizingStrategy SizingStrategy { get; set; }
739
740         /// <inheritdoc/>
741         /// <since_tizen> 9 </since_tizen>
742         public override void OnRelayout(Vector2 size, RelayoutContainer container)
743         {
744             base.OnRelayout(size, container);
745
746             wasRelayouted = true;
747             if (needInitalizeLayouter)
748             {
749                 ReinitializeLayout();
750             }
751         }
752
753         /// <inheritdoc/>
754         [EditorBrowsable(EditorBrowsableState.Never)]
755         public override void NotifyDataSetChanged()
756         {
757             if (selectedItem != null)
758             {
759                 selectedItem = null;
760             }
761             if (selectedItems != null)
762             {
763                 selectedItems.Clear();
764             }
765
766             base.NotifyDataSetChanged();
767         }
768
769         /// <summary>
770         /// Update selected items list in multiple selection.
771         /// </summary>
772         /// <param name="newSelection">updated selection list by user</param>
773         /// <since_tizen> 9 </since_tizen>
774         public void UpdateSelectedItems(IList<object> newSelection)
775         {
776             if (SelectedItems != null)
777             {
778                 var oldSelection = new List<object>(SelectedItems);
779
780                 suppressSelectionChangeNotification = true;
781
782                 SelectedItems.Clear();
783
784                 if (newSelection?.Count > 0)
785                 {
786                     for (int n = 0; n < newSelection.Count; n++)
787                     {
788                         SelectedItems.Add(newSelection[n]);
789                     }
790                 }
791
792                 suppressSelectionChangeNotification = false;
793
794                 SelectedItemsPropertyChanged(oldSelection, newSelection);
795             }
796         }
797
798         /// <summary>
799         /// Scroll to specific position with or without animation.
800         /// </summary>
801         /// <param name="position">Destination.</param>
802         /// <param name="animate">Scroll with or without animation</param>
803         /// <since_tizen> 9 </since_tizen>
804         public new void ScrollTo(float position, bool animate)
805         {
806             if (ItemsLayouter == null)
807             {
808                 throw new Exception("Item Layouter must exist.");
809             }
810
811             if ((InternalSource == null) || needInitalizeLayouter)
812             {
813                 delayedScrollTo = true;
814                 delayedScrollToParam = (position, animate);
815                 return;
816             }
817
818             base.ScrollTo(position, animate);
819         }
820
821         /// <summary>
822         /// Scrolls to the item at the specified index.
823         /// </summary>
824         /// <param name="index">Index of item.</param>
825         [EditorBrowsable(EditorBrowsableState.Never)]
826         public new void ScrollToIndex(int index)
827         {
828             ScrollTo(index, true, ItemScrollTo.Start);
829         }
830
831         /// <summary>
832         /// Scroll to specific item's aligned position with or without animation.
833         /// </summary>
834         /// <param name="index">Target item index of dataset.</param>
835         /// <param name="animate">Boolean flag of animation.</param>
836         /// <param name="align">Align state of item. See details in <see cref="ItemScrollTo"/>.</param>
837         /// <since_tizen> 9 </since_tizen>
838         public virtual void ScrollTo(int index, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
839         {
840             if (ItemsLayouter == null)
841             {
842                 throw new Exception("Item Layouter must exist.");
843             }
844
845             if ((InternalSource == null) || needInitalizeLayouter)
846             {
847                 delayedIndexScrollTo = true;
848                 delayedIndexScrollToParam = (index, animate, align);
849                 return;
850             }
851
852             if (index < 0 || index >= InternalSource.Count)
853             {
854                 throw new Exception("index is out of boundary. index should be a value between (0, " + InternalSource.Count.ToString() + ").");
855             }
856
857             float scrollPos, curPos, curSize, curItemSize;
858             (float x, float y) = ItemsLayouter.GetItemPosition(index);
859             (float width, float height) = ItemsLayouter.GetItemSize(index);
860
861             if (ScrollingDirection == Direction.Horizontal)
862             {
863                 scrollPos = x;
864                 curPos = ScrollPosition.X;
865                 curSize = Size.Width;
866                 curItemSize = width;
867             }
868             else
869             {
870                 scrollPos = y;
871                 curPos = ScrollPosition.Y;
872                 curSize = Size.Height;
873                 curItemSize = height;
874             }
875
876             //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
877             switch (align)
878             {
879                 case ItemScrollTo.Start:
880                     //nothing necessary.
881                     break;
882                 case ItemScrollTo.Center:
883                     scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
884                     break;
885                 case ItemScrollTo.End:
886                     scrollPos = scrollPos - curSize + curItemSize;
887                     break;
888                 case ItemScrollTo.Nearest:
889                     if (scrollPos < curPos - curItemSize)
890                     {
891                         // item is placed before the current screen. scrollTo.Top
892                     }
893                     else if (scrollPos >= curPos + curSize + curItemSize)
894                     {
895                         // item is placed after the current screen. scrollTo.End
896                         scrollPos = scrollPos - curSize + curItemSize;
897                     }
898                     else
899                     {
900                         // item is in the scroller. ScrollTo() is ignored.
901                         return;
902                     }
903                     break;
904             }
905
906             //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
907             base.ScrollTo(scrollPos, animate);
908         }
909
910         /// <summary>
911         /// Apply style to CollectionView
912         /// </summary>
913         /// <param name="viewStyle">The style to apply.</param>
914         [EditorBrowsable(EditorBrowsableState.Never)]
915         public override void ApplyStyle(ViewStyle viewStyle)
916         {
917             base.ApplyStyle(viewStyle);
918             if (viewStyle != null)
919             {
920                 //Extension = RecyclerViewItemStyle.CreateExtension();
921             }
922             if (itemsLayouter != null)
923             {
924                 string styleName = "Tizen.NUI.Compoenents." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
925                 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
926                 if (layouterStyle != null)
927                 {
928                     itemsLayouter.Padding = new Extents(layouterStyle.Padding);
929                 }
930             }
931         }
932
933         /// <summary>
934         /// Initialize AT-SPI object.
935         /// </summary>
936         [EditorBrowsable(EditorBrowsableState.Never)]
937         public override void OnInitialize()
938         {
939             base.OnInitialize();
940             AccessibilityRole = Role.List;
941         }
942
943         /// <summary>
944         /// Scroll to specified item
945         /// </summary>
946         /// <remarks>
947         /// Make sure that the item that is about to receive the accessibility highlight is visible.
948         /// </remarks>
949         [EditorBrowsable(EditorBrowsableState.Never)]
950         protected override bool AccessibilityScrollToChild(View child)
951         {
952             if (ScrollingDirection == Direction.Horizontal)
953             {
954                 if (child.ScreenPosition.X + child.Size.Width <= this.ScreenPosition.X)
955                 {
956                     ScrollTo((float)(child.ScreenPosition.X - ContentContainer.ScreenPosition.X), false);
957                 }
958                 else if (child.ScreenPosition.X >= this.ScreenPosition.X + this.Size.Width)
959                 {
960                     ScrollTo((float)(child.ScreenPosition.X + child.Size.Width - ContentContainer.ScreenPosition.X - this.Size.Width), false);
961                 }
962             }
963             else
964             {
965                 if (child.ScreenPosition.Y + child.Size.Height <= this.ScreenPosition.Y)
966                 {
967                     ScrollTo((float)(child.ScreenPosition.Y - ContentContainer.ScreenPosition.Y), false);
968                 }
969                 else if (child.ScreenPosition.Y >= this.ScreenPosition.Y + this.Size.Height)
970                 {
971                     ScrollTo((float)(child.ScreenPosition.Y + child.Size.Height - ContentContainer.ScreenPosition.Y - this.Size.Height), false);
972                 }
973             }
974             return true;
975         }
976
977         /// <inheritdoc/>
978         [EditorBrowsable(EditorBrowsableState.Never)]
979         protected internal override RecyclerViewItem RealizeItem(int index)
980         {
981             RecyclerViewItem item;
982             if (index == 0 && Header != null)
983             {
984                 Header.Show();
985                 return Header;
986             }
987
988             var source = InternalSource;
989             if (source == null)
990                 return null;
991
992             if (index == source.Count - 1 && Footer != null)
993             {
994                 Footer.Show();
995                 return Footer;
996             }
997
998             if (isGrouped)
999             {
1000                 if (source.IsGroupHeader(index))
1001                 {
1002                     var context = source.GetItem(index);
1003                     item = RealizeGroupHeader(index, context);
1004                 }
1005                 else if (source.IsGroupFooter(index))
1006                 {
1007                     var context = source.GetItem(index);
1008                     //group selection?
1009                     item = RealizeGroupFooter(index, context);
1010                 }
1011                 else
1012                 {
1013                     item = base.RealizeItem(index);
1014                     if (item == null)
1015                     {
1016                         throw new Exception("Item realize failed by Null content return.");
1017                     }
1018                     item.ParentGroup = source.GetGroupParent(index);
1019                 }
1020             }
1021             else
1022             {
1023                 item = base.RealizeItem(index);
1024             }
1025
1026             if (item == null)
1027                 throw new Exception("Item realize failed by Null content return.");
1028
1029             switch (SelectionMode)
1030             {
1031                 case ItemSelectionMode.Single:
1032                 case ItemSelectionMode.SingleAlways:
1033                     if (item.BindingContext != null && item.BindingContext == SelectedItem)
1034                     {
1035                         item.IsSelected = true;
1036                     }
1037                     break;
1038
1039                 case ItemSelectionMode.Multiple:
1040                     if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
1041                     {
1042                         item.IsSelected = true;
1043                     }
1044                     break;
1045                 case ItemSelectionMode.None:
1046                     item.IsSelectable = false;
1047                     break;
1048             }
1049             return item;
1050         }
1051
1052         /// <inheritdoc/>
1053         [EditorBrowsable(EditorBrowsableState.Never)]
1054         protected internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
1055         {
1056             if (item == null)
1057             {
1058                 return;
1059             }
1060
1061             if (item == Header)
1062             {
1063                 item?.Hide();
1064                 return;
1065             }
1066             if (item == Footer)
1067             {
1068                 item.Hide();
1069                 return;
1070             }
1071
1072             if (item.IsGroupHeader || item.IsGroupFooter)
1073             {
1074                 item.Index = -1;
1075                 item.ParentItemsView = null;
1076                 item.BindingContext = null;
1077                 item.IsPressed = false;
1078                 item.IsSelected = false;
1079                 item.IsEnabled = true;
1080                 item.UpdateState();
1081                 //item.Relayout -= OnItemRelayout;
1082                 if (!recycle || !PushRecycleGroupCache(item))
1083                 {
1084                     Utility.Dispose(item);
1085                 }
1086                 return;
1087             }
1088
1089             base.UnrealizeItem(item, recycle);
1090         }
1091
1092         internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
1093         {
1094             if (suppressSelectionChangeNotification)
1095             {
1096                 return;
1097             }
1098
1099             foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
1100             {
1101                 if (item.BindingContext == null) continue;
1102                 if (newSelection.Contains(item.BindingContext))
1103                 {
1104                     if (!item.IsSelected)
1105                     {
1106                         item.IsSelected = true;
1107                     }
1108                 }
1109                 else
1110                 {
1111                     if (item.IsSelected)
1112                     {
1113                         item.IsSelected = false;
1114                     }
1115                 }
1116             }
1117             SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
1118
1119             OnPropertyChanged(SelectedItemsProperty.PropertyName);
1120         }
1121
1122         /// <summary>
1123         /// Internal selection callback.
1124         /// </summary>
1125         /// <since_tizen> 9 </since_tizen>
1126         protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
1127         {
1128             //Selection Callback
1129         }
1130
1131         /// <summary>
1132         /// Adjust scrolling position by own scrolling rules.
1133         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1134         /// </summary>
1135         /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
1136         /// <returns>Adjusted scroll destination</returns>
1137         [EditorBrowsable(EditorBrowsableState.Never)]
1138         protected override float AdjustTargetPositionOfScrollAnimation(float position)
1139         {
1140             // Destination is depending on implementation of layout manager.
1141             // Get destination from layout manager.
1142             return ItemsLayouter?.CalculateCandidateScrollPosition(position) ?? position;
1143         }
1144
1145         /// <inheritdoc/>
1146         [EditorBrowsable(EditorBrowsableState.Never)]
1147         protected override void ClearCache()
1148         {
1149             foreach (RecyclerViewItem item in recycleGroupHeaderCache)
1150             {
1151                 Utility.Dispose(item);
1152             }
1153             recycleGroupHeaderCache.Clear();
1154             foreach (RecyclerViewItem item in recycleGroupFooterCache)
1155             {
1156                 Utility.Dispose(item);
1157             }
1158             recycleGroupFooterCache.Clear();
1159             base.ClearCache();
1160         }
1161
1162
1163         /// <summary>
1164         /// OnScroll event callback. Requesting layout to the layouter with given scrollPosition.
1165         /// </summary>
1166         /// <param name="source">Scroll source object</param>
1167         /// <param name="args">Scroll event argument</param>
1168         /// <since_tizen> 9 </since_tizen>
1169         protected override void OnScrolling(object source, ScrollEventArgs args)
1170         {
1171             if (disposed) return;
1172
1173             if (needInitalizeLayouter && (ItemsLayouter != null))
1174             {
1175                 ItemsLayouter.Initialize(this);
1176                 needInitalizeLayouter = false;
1177             }
1178
1179             base.OnScrolling(source, args);
1180         }
1181
1182         /// <summary>
1183         /// Dispose ItemsView and all children on it.
1184         /// </summary>
1185         /// <param name="type">Dispose type.</param>
1186         /// <since_tizen> 9 </since_tizen>
1187         protected override void Dispose(DisposeTypes type)
1188         {
1189             if (disposed)
1190             {
1191                 return;
1192             }
1193
1194             if (type == DisposeTypes.Explicit)
1195             {
1196                 // From now on, no need to use this properties,
1197                 // so remove reference, to push it into garbage collector.
1198
1199                 // Arugable to disposing user-created members.
1200                 /*
1201                 if (Header != null)
1202                 {
1203                     Utility.Dispose(Header);
1204                     Header = null;
1205                 }
1206                 if (Footer != null)
1207                 {
1208                     Utility.Dispose(Footer);
1209                     Footer = null;
1210                 }
1211                 */
1212
1213                 groupHeaderTemplate = null;
1214                 groupFooterTemplate = null;
1215
1216                 if (selectedItem != null)
1217                 {
1218                     selectedItem = null;
1219                 }
1220                 if (selectedItems != null)
1221                 {
1222                     selectedItems.Clear();
1223                     selectedItems = null;
1224                 }
1225                 if (InternalSource != null)
1226                 {
1227                     InternalSource.Dispose();
1228                     InternalSource = null;
1229                 }
1230             }
1231
1232             base.Dispose(type);
1233         }
1234
1235         private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
1236         {
1237             var command = colView.SelectionChangedCommand;
1238
1239             if (command != null)
1240             {
1241                 var commandParameter = colView.SelectionChangedCommandParameter;
1242                 if (commandParameter == null)
1243                 {
1244                     commandParameter = args;
1245                 }
1246
1247                 if (command.CanExecute(commandParameter))
1248                 {
1249                     command.Execute(commandParameter);
1250                 }
1251             }
1252             colView.SelectionChanged?.Invoke(colView, args);
1253             colView.OnSelectionChanged(args);
1254         }
1255
1256         private static object CoerceSelectedItems(CollectionView colView, object value)
1257         {
1258             if (value == null)
1259             {
1260                 return new SelectionList(colView);
1261             }
1262
1263             if (value is SelectionList)
1264             {
1265                 return value;
1266             }
1267
1268             return new SelectionList(colView, value as IList<object>);
1269         }
1270
1271         private static void SelectionModePropertyChanged(CollectionView colView, object oldValue, object newValue)
1272         {
1273             var oldMode = (ItemSelectionMode)oldValue;
1274             var newMode = (ItemSelectionMode)newValue;
1275
1276             IList<object> previousSelection = new List<object>();
1277             IList<object> newSelection = new List<object>();
1278
1279             switch (oldMode)
1280             {
1281                 case ItemSelectionMode.None:
1282                     break;
1283                 case ItemSelectionMode.Single:
1284                     if (colView.SelectedItem != null)
1285                     {
1286                         previousSelection.Add(colView.SelectedItem);
1287                     }
1288                     break;
1289                 case ItemSelectionMode.Multiple:
1290                     previousSelection = colView.SelectedItems;
1291                     break;
1292             }
1293
1294             switch (newMode)
1295             {
1296                 case ItemSelectionMode.None:
1297                     break;
1298                 case ItemSelectionMode.Single:
1299                     if (colView.SelectedItem != null)
1300                     {
1301                         newSelection.Add(colView.SelectedItem);
1302                     }
1303                     break;
1304                 case ItemSelectionMode.Multiple:
1305                     newSelection = colView.SelectedItems;
1306                     break;
1307             }
1308
1309             if (previousSelection.Count == newSelection.Count)
1310             {
1311                 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
1312                 {
1313                     // Both selections are empty or have the same single item; no reason to signal a change
1314                     return;
1315                 }
1316             }
1317
1318             var args = new SelectionChangedEventArgs(previousSelection, newSelection);
1319             SelectionPropertyChanged(colView, args);
1320         }
1321
1322         private void ReinitializeLayout()
1323         {
1324             var localItemsLayouter = ItemsLayouter;
1325             if (ItemsSource == null || localItemsLayouter == null || ItemTemplate == null)
1326             {
1327                 return;
1328             }
1329
1330             if (disposed)
1331             {
1332                 return;
1333             }
1334
1335             if (!wasRelayouted)
1336             {
1337                 return;
1338             }
1339
1340             if (needInitalizeLayouter)
1341             {
1342                 if (InternalSource == null)
1343                 {
1344                     return;
1345                 }
1346
1347                 InternalSource.HasHeader = (header != null);
1348                 InternalSource.HasFooter = (footer != null);
1349
1350                 itemsLayouter.Clear();
1351                 ClearCache();
1352
1353                 localItemsLayouter.Initialize(this);
1354                 needInitalizeLayouter = false;
1355             }
1356
1357             localItemsLayouter.RequestLayout(0.0f, true);
1358
1359             if (delayedScrollTo)
1360             {
1361                 delayedScrollTo = false;
1362                 ScrollTo(delayedScrollToParam.position, delayedScrollToParam.anim);
1363             }
1364
1365             if (delayedIndexScrollTo)
1366             {
1367                 delayedIndexScrollTo = false;
1368                 ScrollTo(delayedIndexScrollToParam.index, delayedIndexScrollToParam.anim, delayedIndexScrollToParam.scrollTo);
1369             }
1370
1371             if (ScrollingDirection == Direction.Horizontal)
1372             {
1373                 ContentContainer.SizeWidth = (float)localItemsLayouter.CalculateLayoutOrientationSize();
1374             }
1375             else
1376             {
1377                 ContentContainer.SizeHeight = (float)localItemsLayouter.CalculateLayoutOrientationSize();
1378             }
1379         }
1380
1381         private bool PushRecycleGroupCache(RecyclerViewItem item)
1382         {
1383             if (item == null)
1384             {
1385                 throw new ArgumentNullException(nameof(item));
1386             }
1387
1388             if  (item.Template == null || RecycleCache.Count >= 20)
1389             {
1390                 return false;
1391             }
1392
1393             if (item.IsGroupHeader)
1394             {
1395                 recycleGroupHeaderCache.Add(item);
1396             }
1397             else if (item.IsGroupFooter)
1398             {
1399                 recycleGroupFooterCache.Add(item);
1400             }
1401             else
1402             {
1403                 return false;
1404             }
1405
1406             item.Hide();
1407             item.Index = -1;
1408
1409             return true;
1410         }
1411
1412         private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1413         {
1414             RecyclerViewItem viewItem = null;
1415
1416             var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1417             for (int i = 0; i < Cache.Count; i++)
1418             {
1419                 viewItem = Cache[i];
1420                 if (Template == viewItem.Template)
1421                 {
1422                     break;
1423                 }
1424             }
1425
1426             if (viewItem != null)
1427             {
1428                 Cache.Remove(viewItem);
1429                 viewItem.Show();
1430             }
1431
1432             return viewItem;
1433         }
1434
1435         private RecyclerViewItem RealizeGroupHeader(int index, object context)
1436         {
1437             DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
1438
1439             RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
1440
1441             if (groupHeader == null)
1442             {
1443                 groupHeader = DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this) as RecyclerViewItem;
1444                 if (groupHeader == null)
1445                 {
1446                     return null;
1447                 }
1448
1449                 groupHeader.Template = templ;
1450                 groupHeader.IsGroupHeader = true;
1451                 groupHeader.IsGroupFooter = false;
1452                 ContentContainer.Add(groupHeader);
1453             }
1454
1455             if (groupHeader != null)
1456             {
1457                 groupHeader.ParentItemsView = this;
1458                 groupHeader.Index = index;
1459                 groupHeader.ParentGroup = context;
1460                 groupHeader.BindingContext = context;
1461
1462                 return groupHeader;
1463             }
1464
1465             return null;
1466         }
1467
1468         private RecyclerViewItem RealizeGroupFooter(int index, object context)
1469         {
1470             DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
1471
1472             RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
1473
1474             if (groupFooter == null)
1475             {
1476                 groupFooter = DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this) as RecyclerViewItem;
1477                 if (groupFooter == null)
1478                 {
1479                     return null;
1480                 }
1481
1482                 groupFooter.Template = templ;
1483                 groupFooter.IsGroupHeader = false;
1484                 groupFooter.IsGroupFooter = true;
1485                 ContentContainer.Add(groupFooter);
1486             }
1487
1488             if (groupFooter != null)
1489             {
1490                 groupFooter.ParentItemsView = this;
1491                 groupFooter.Index = index;
1492                 groupFooter.ParentGroup = context;
1493                 groupFooter.BindingContext = context;
1494                 return groupFooter;
1495             }
1496
1497             return null;
1498         }
1499
1500         private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
1501         {
1502             switch (args.Action)
1503             {
1504                 case NotifyCollectionChangedAction.Add:
1505                     break;
1506                 case NotifyCollectionChangedAction.Remove:
1507                     // Clear removed items.
1508                     if (args.OldItems != null)
1509                     {
1510                         if (args.OldItems.Contains(selectedItem))
1511                         {
1512                             selectedItem = null;
1513                         }
1514
1515                         if (selectedItems != null)
1516                         {
1517                             foreach (object removed in args.OldItems)
1518                             {
1519                                 if (selectedItems.Contains(removed))
1520                                 {
1521                                     selectedItems.Remove(removed);
1522                                 }
1523                             }
1524                         }
1525                     }
1526                     break;
1527                 case NotifyCollectionChangedAction.Replace:
1528                     break;
1529                 case NotifyCollectionChangedAction.Move:
1530                     break;
1531                 case NotifyCollectionChangedAction.Reset:
1532                     break;
1533                 default:
1534                     throw new ArgumentOutOfRangeException(nameof(args));
1535             }
1536         }
1537
1538     }
1539 }