95c41dc5d367fe8a1988f3c61389a1f123e765f1
[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         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                     InternalItemSource?.Dispose();
291                     InternalItemSource = null;
292                     itemsLayouter?.Clear();
293                     ClearCache();
294                     return;
295                 }
296                 if (itemsSource is INotifyCollectionChanged newNotifyCollectionChanged)
297                 {
298                     newNotifyCollectionChanged.CollectionChanged += CollectionChanged;
299                 }
300
301                 InternalItemSource?.Dispose();
302                 InternalItemSource = 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 (InternalItemSource != null)
540                 {
541                     InternalItemSource.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 = InternalItemSource?.Count ?? 0;
578                     value.ParentItemsView = this;
579                     value.IsFooter = true;
580                     ContentContainer.Add(value);
581                 }
582                 footer = value;
583                 if (InternalItemSource != null)
584                 {
585                     InternalItemSource.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 (InternalItemSource != null)
617                 {
618                     InternalItemSource.Dispose();
619                     InternalItemSource = null;
620                 }
621                 if (ItemsSource != null)
622                 {
623                     InternalItemSource = 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 (InternalItemSource != null)
661                 {
662                     InternalItemSource.Dispose();
663                     InternalItemSource = null;
664                 }
665
666                 if (ItemsSource != null)
667                 {
668                     InternalItemSource = 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 (InternalItemSource != null)
705                 {
706                     InternalItemSource.Dispose();
707                     InternalItemSource = null;
708                 }
709
710                 if (ItemsSource != null)
711                 {
712                     InternalItemSource = ItemsSourceFactory.Create(this);
713                 }
714
715                 ReinitializeLayout();
716             }
717         }
718
719         /// <summary>
720         /// Internal encapsulated items data source.
721         /// </summary>
722         internal new IGroupableItemSource InternalItemSource
723         {
724             get
725             {
726                 return (base.InternalItemSource as IGroupableItemSource);
727             }
728             set
729             {
730                 base.InternalItemSource = 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 ((InternalItemSource == 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 ((InternalItemSource == null) || needInitalizeLayouter)
846             {
847                 delayedIndexScrollTo = true;
848                 delayedIndexScrollToParam = (index, animate, align);
849                 return;
850             }
851
852             if (index < 0 || index >= InternalItemSource.Count)
853             {
854                 throw new Exception("index is out of boundary. index should be a value between (0, " + InternalItemSource.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}", InternalItemSource.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         // Realize and Decorate the item.
978
979         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             if (index == InternalItemSource.Count - 1 && Footer != null)
989             {
990                 Footer.Show();
991                 return Footer;
992             }
993
994             if (isGrouped)
995             {
996                 var context = InternalItemSource.GetItem(index);
997                 if (InternalItemSource.IsGroupHeader(index))
998                 {
999                     item = RealizeGroupHeader(index, context);
1000                 }
1001                 else if (InternalItemSource.IsGroupFooter(index))
1002                 {
1003
1004                     //group selection?
1005                     item = RealizeGroupFooter(index, context);
1006                 }
1007                 else
1008                 {
1009                     item = base.RealizeItem(index);
1010                     if (item == null)
1011                     {
1012                         throw new Exception("Item realize failed by Null content return.");
1013                     }
1014                     item.ParentGroup = InternalItemSource.GetGroupParent(index);
1015                 }
1016             }
1017             else
1018             {
1019                 item = base.RealizeItem(index);
1020             }
1021
1022             if (item == null)
1023                 throw new Exception("Item realize failed by Null content return.");
1024
1025             switch (SelectionMode)
1026             {
1027                 case ItemSelectionMode.Single:
1028                 case ItemSelectionMode.SingleAlways:
1029                     if (item.BindingContext != null && item.BindingContext == SelectedItem)
1030                     {
1031                         item.IsSelected = true;
1032                     }
1033                     break;
1034
1035                 case ItemSelectionMode.Multiple:
1036                     if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
1037                     {
1038                         item.IsSelected = true;
1039                     }
1040                     break;
1041                 case ItemSelectionMode.None:
1042                     item.IsSelectable = false;
1043                     break;
1044             }
1045             return item;
1046         }
1047
1048         // Unrealize and caching the item.
1049         internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
1050         {
1051             if (item == null)
1052             {
1053                 return;
1054             }
1055
1056             if (item == Header)
1057             {
1058                 item?.Hide();
1059                 return;
1060             }
1061             if (item == Footer)
1062             {
1063                 item.Hide();
1064                 return;
1065             }
1066
1067             if (item.isGroupHeader || item.isGroupFooter)
1068             {
1069                 item.Index = -1;
1070                 item.ParentItemsView = null;
1071                 item.BindingContext = null;
1072                 item.IsPressed = false;
1073                 item.IsSelected = false;
1074                 item.IsEnabled = true;
1075                 item.UpdateState();
1076                 //item.Relayout -= OnItemRelayout;
1077                 if (!recycle || !PushRecycleGroupCache(item))
1078                 {
1079                     Utility.Dispose(item);
1080                 }
1081                 return;
1082             }
1083
1084             base.UnrealizeItem(item, recycle);
1085         }
1086
1087         internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
1088         {
1089             if (suppressSelectionChangeNotification)
1090             {
1091                 return;
1092             }
1093
1094             foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
1095             {
1096                 if (item.BindingContext == null) continue;
1097                 if (newSelection.Contains(item.BindingContext))
1098                 {
1099                     if (!item.IsSelected)
1100                     {
1101                         item.IsSelected = true;
1102                     }
1103                 }
1104                 else
1105                 {
1106                     if (item.IsSelected)
1107                     {
1108                         item.IsSelected = false;
1109                     }
1110                 }
1111             }
1112             SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
1113
1114             OnPropertyChanged(SelectedItemsProperty.PropertyName);
1115         }
1116
1117         /// <summary>
1118         /// Internal selection callback.
1119         /// </summary>
1120         /// <since_tizen> 9 </since_tizen>
1121         protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
1122         {
1123             //Selection Callback
1124         }
1125
1126         /// <summary>
1127         /// Adjust scrolling position by own scrolling rules.
1128         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1129         /// </summary>
1130         /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
1131         /// <returns>Adjusted scroll destination</returns>
1132         [EditorBrowsable(EditorBrowsableState.Never)]
1133         protected override float AdjustTargetPositionOfScrollAnimation(float position)
1134         {
1135             // Destination is depending on implementation of layout manager.
1136             // Get destination from layout manager.
1137             return ItemsLayouter?.CalculateCandidateScrollPosition(position) ?? position;
1138         }
1139
1140         /// <inheritdoc/>
1141         [EditorBrowsable(EditorBrowsableState.Never)]
1142         protected override void ClearCache()
1143         {
1144             foreach (RecyclerViewItem item in recycleGroupHeaderCache)
1145             {
1146                 Utility.Dispose(item);
1147             }
1148             recycleGroupHeaderCache.Clear();
1149             foreach (RecyclerViewItem item in recycleGroupFooterCache)
1150             {
1151                 Utility.Dispose(item);
1152             }
1153             recycleGroupFooterCache.Clear();
1154             base.ClearCache();
1155         }
1156
1157
1158         /// <summary>
1159         /// OnScroll event callback. Requesting layout to the layouter with given scrollPosition.
1160         /// </summary>
1161         /// <param name="source">Scroll source object</param>
1162         /// <param name="args">Scroll event argument</param>
1163         /// <since_tizen> 9 </since_tizen>
1164         protected override void OnScrolling(object source, ScrollEventArgs args)
1165         {
1166             if (disposed) return;
1167
1168             if (needInitalizeLayouter && (ItemsLayouter != null))
1169             {
1170                 ItemsLayouter.Initialize(this);
1171                 needInitalizeLayouter = false;
1172             }
1173
1174             base.OnScrolling(source, args);
1175         }
1176
1177         /// <summary>
1178         /// Dispose ItemsView and all children on it.
1179         /// </summary>
1180         /// <param name="type">Dispose type.</param>
1181         /// <since_tizen> 9 </since_tizen>
1182         protected override void Dispose(DisposeTypes type)
1183         {
1184             if (disposed)
1185             {
1186                 return;
1187             }
1188
1189             if (type == DisposeTypes.Explicit)
1190             {
1191                 // From now on, no need to use this properties,
1192                 // so remove reference, to push it into garbage collector.
1193
1194                 // Arugable to disposing user-created members.
1195                 /*
1196                 if (Header != null)
1197                 {
1198                     Utility.Dispose(Header);
1199                     Header = null;
1200                 }
1201                 if (Footer != null)
1202                 {
1203                     Utility.Dispose(Footer);
1204                     Footer = null;
1205                 }
1206                 */
1207
1208                 groupHeaderTemplate = null;
1209                 groupFooterTemplate = null;
1210
1211                 if (selectedItem != null)
1212                 {
1213                     selectedItem = null;
1214                 }
1215                 if (selectedItems != null)
1216                 {
1217                     selectedItems.Clear();
1218                     selectedItems = null;
1219                 }
1220                 if (InternalItemSource != null)
1221                 {
1222                     InternalItemSource.Dispose();
1223                     InternalItemSource = null;
1224                 }
1225             }
1226
1227             base.Dispose(type);
1228         }
1229
1230         private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
1231         {
1232             var command = colView.SelectionChangedCommand;
1233
1234             if (command != null)
1235             {
1236                 var commandParameter = colView.SelectionChangedCommandParameter;
1237
1238                 if (command.CanExecute(commandParameter))
1239                 {
1240                     command.Execute(commandParameter);
1241                 }
1242             }
1243             colView.SelectionChanged?.Invoke(colView, args);
1244             colView.OnSelectionChanged(args);
1245         }
1246
1247         private static object CoerceSelectedItems(CollectionView colView, object value)
1248         {
1249             if (value == null)
1250             {
1251                 return new SelectionList(colView);
1252             }
1253
1254             if (value is SelectionList)
1255             {
1256                 return value;
1257             }
1258
1259             return new SelectionList(colView, value as IList<object>);
1260         }
1261
1262         private static void SelectionModePropertyChanged(CollectionView colView, object oldValue, object newValue)
1263         {
1264             var oldMode = (ItemSelectionMode)oldValue;
1265             var newMode = (ItemSelectionMode)newValue;
1266
1267             IList<object> previousSelection = new List<object>();
1268             IList<object> newSelection = new List<object>();
1269
1270             switch (oldMode)
1271             {
1272                 case ItemSelectionMode.None:
1273                     break;
1274                 case ItemSelectionMode.Single:
1275                     if (colView.SelectedItem != null)
1276                     {
1277                         previousSelection.Add(colView.SelectedItem);
1278                     }
1279                     break;
1280                 case ItemSelectionMode.Multiple:
1281                     previousSelection = colView.SelectedItems;
1282                     break;
1283             }
1284
1285             switch (newMode)
1286             {
1287                 case ItemSelectionMode.None:
1288                     break;
1289                 case ItemSelectionMode.Single:
1290                     if (colView.SelectedItem != null)
1291                     {
1292                         newSelection.Add(colView.SelectedItem);
1293                     }
1294                     break;
1295                 case ItemSelectionMode.Multiple:
1296                     newSelection = colView.SelectedItems;
1297                     break;
1298             }
1299
1300             if (previousSelection.Count == newSelection.Count)
1301             {
1302                 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
1303                 {
1304                     // Both selections are empty or have the same single item; no reason to signal a change
1305                     return;
1306                 }
1307             }
1308
1309             var args = new SelectionChangedEventArgs(previousSelection, newSelection);
1310             SelectionPropertyChanged(colView, args);
1311         }
1312
1313         private void ReinitializeLayout()
1314         {
1315             if (ItemsSource == null || ItemsLayouter == null || ItemTemplate == null)
1316             {
1317                 return;
1318             }
1319
1320             if (disposed)
1321             {
1322                 return;
1323             }
1324
1325             if (!wasRelayouted)
1326             {
1327                 return;
1328             }
1329
1330             if (needInitalizeLayouter)
1331             {
1332                 if (InternalItemSource == null)
1333                 {
1334                     return;
1335                 }
1336
1337                 InternalItemSource.HasHeader = (header != null);
1338                 InternalItemSource.HasFooter = (footer != null);
1339
1340                 itemsLayouter.Clear();
1341                 ClearCache();
1342
1343                 ItemsLayouter.Initialize(this);
1344                 needInitalizeLayouter = false;
1345             }
1346
1347             ItemsLayouter.RequestLayout(0.0f, true);
1348
1349             if (delayedScrollTo)
1350             {
1351                 delayedScrollTo = false;
1352                 ScrollTo(delayedScrollToParam.position, delayedScrollToParam.anim);
1353             }
1354
1355             if (delayedIndexScrollTo)
1356             {
1357                 delayedIndexScrollTo = false;
1358                 ScrollTo(delayedIndexScrollToParam.index, delayedIndexScrollToParam.anim, delayedIndexScrollToParam.scrollTo);
1359             }
1360
1361             if (ScrollingDirection == Direction.Horizontal)
1362             {
1363                 ContentContainer.SizeWidth = (float)ItemsLayouter?.CalculateLayoutOrientationSize();
1364             }
1365             else
1366             {
1367                 ContentContainer.SizeHeight = (float)ItemsLayouter?.CalculateLayoutOrientationSize();
1368             }
1369         }
1370
1371         private bool PushRecycleGroupCache(RecyclerViewItem item)
1372         {
1373             if (item == null)
1374             {
1375                 throw new ArgumentNullException(nameof(item));
1376             }
1377
1378             if  (item.Template == null || RecycleCache.Count >= 20)
1379             {
1380                 return false;
1381             }
1382
1383             if (item.isGroupHeader)
1384             {
1385                 recycleGroupHeaderCache.Add(item);
1386             }
1387             else if (item.isGroupFooter)
1388             {
1389                 recycleGroupFooterCache.Add(item);
1390             }
1391             else
1392             {
1393                 return false;
1394             }
1395
1396             item.Hide();
1397             item.Index = -1;
1398
1399             return true;
1400         }
1401
1402         private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1403         {
1404             RecyclerViewItem viewItem = null;
1405
1406             var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1407             for (int i = 0; i < Cache.Count; i++)
1408             {
1409                 viewItem = Cache[i];
1410                 if (Template == viewItem.Template)
1411                 {
1412                     break;
1413                 }
1414             }
1415
1416             if (viewItem != null)
1417             {
1418                 Cache.Remove(viewItem);
1419                 viewItem.Show();
1420             }
1421
1422             return viewItem;
1423         }
1424
1425         private RecyclerViewItem RealizeGroupHeader(int index, object context)
1426         {
1427             DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
1428
1429             RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
1430
1431             if (groupHeader == null)
1432             {
1433                 groupHeader = DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this) as RecyclerViewItem;
1434                 if (groupHeader == null)
1435                 {
1436                     return null;
1437                 }
1438
1439                 groupHeader.Template = templ;
1440                 groupHeader.isGroupHeader = true;
1441                 groupHeader.isGroupFooter = false;
1442                 ContentContainer.Add(groupHeader);
1443             }
1444
1445             if (groupHeader != null)
1446             {
1447                 groupHeader.ParentItemsView = this;
1448                 groupHeader.Index = index;
1449                 groupHeader.ParentGroup = context;
1450                 groupHeader.BindingContext = context;
1451
1452                 return groupHeader;
1453             }
1454
1455             return null;
1456         }
1457
1458         private RecyclerViewItem RealizeGroupFooter(int index, object context)
1459         {
1460             DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
1461
1462             RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
1463
1464             if (groupFooter == null)
1465             {
1466                 groupFooter = DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this) as RecyclerViewItem;
1467                 if (groupFooter == null)
1468                 {
1469                     return null;
1470                 }
1471
1472                 groupFooter.Template = templ;
1473                 groupFooter.isGroupHeader = false;
1474                 groupFooter.isGroupFooter = true;
1475                 ContentContainer.Add(groupFooter);
1476             }
1477
1478             if (groupFooter != null)
1479             {
1480                 groupFooter.ParentItemsView = this;
1481                 groupFooter.Index = index;
1482                 groupFooter.ParentGroup = context;
1483                 groupFooter.BindingContext = context;
1484                 return groupFooter;
1485             }
1486
1487             return null;
1488         }
1489
1490         private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
1491         {
1492             switch (args.Action)
1493             {
1494                 case NotifyCollectionChangedAction.Add:
1495                     break;
1496                 case NotifyCollectionChangedAction.Remove:
1497                     // Clear removed items.
1498                     if (args.OldItems != null)
1499                     {
1500                         if (args.OldItems.Contains(selectedItem))
1501                         {
1502                             selectedItem = null;
1503                         }
1504
1505                         if (selectedItems != null)
1506                         {
1507                             foreach (object removed in args.OldItems)
1508                             {
1509                                 if (selectedItems.Contains(removed))
1510                                 {
1511                                     selectedItems.Remove(removed);
1512                                 }
1513                             }
1514                         }
1515                     }
1516                     break;
1517                 case NotifyCollectionChangedAction.Replace:
1518                     break;
1519                 case NotifyCollectionChangedAction.Move:
1520                     break;
1521                 case NotifyCollectionChangedAction.Reset:
1522                     break;
1523                 default:
1524                     throw new ArgumentOutOfRangeException(nameof(args));
1525             }
1526         }
1527
1528     }
1529 }