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