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