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