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