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