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