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