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