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