1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
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;
26 namespace Tizen.NUI.Components
29 /// Selectable RecyclerView that presenting a collection of items with variable layouters.
31 /// <since_tizen> 9 </since_tizen>
32 public class CollectionView : RecyclerView
35 /// Binding Property of selected item in single selection.
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) =>
42 var colView = (CollectionView)bindable;
43 oldValue = colView.selectedItem;
44 colView.selectedItem = newValue;
45 var args = new SelectionChangedEventArgs(oldValue, newValue);
47 foreach (RecyclerViewItem item in colView.ContentContainer.Children.Where((item) => item is RecyclerViewItem))
49 if (item.BindingContext == null) continue;
50 if (item.BindingContext == oldValue) item.IsSelected = false;
51 else if (item.BindingContext == newValue) item.IsSelected = true;
54 SelectionPropertyChanged(colView, args);
56 defaultValueCreator: (bindable) =>
58 var colView = (CollectionView)bindable;
59 return colView.selectedItem;
63 /// Binding Property of selected items list in multiple selection.
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) =>
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);
77 defaultValueCreator: (bindable) =>
79 var colView = (CollectionView)bindable;
80 colView.selectedItems = colView.selectedItems ?? new SelectionList(colView);
81 return colView.selectedItems;
85 /// Binding Property of selected items list in multiple selection.
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) =>
92 var colView = (CollectionView)bindable;
93 oldValue = colView.selectionMode;
94 colView.selectionMode = (ItemSelectionMode)newValue;
95 SelectionModePropertyChanged(colView, oldValue, newValue);
97 defaultValueCreator: (bindable) =>
99 var colView = (CollectionView)bindable;
100 return colView.selectionMode;
104 private static readonly IList<object> selectEmpty = new List<object>(0);
105 private DataTemplate itemTemplate = null;
106 private IEnumerable itemsSource = null;
107 private ItemsLayouter itemsLayouter = null;
108 private DataTemplate groupHeaderTemplate;
109 private DataTemplate groupFooterTemplate;
110 private bool isGrouped;
111 private bool wasRelayouted = false;
112 private bool needInitalizeLayouter = false;
113 private object selectedItem;
114 private SelectionList selectedItems;
115 private bool suppressSelectionChangeNotification;
116 private ItemSelectionMode selectionMode = ItemSelectionMode.None;
117 private RecyclerViewItem header;
118 private RecyclerViewItem footer;
119 private View focusedView;
120 private int prevFocusedDataIndex = 0;
121 private List<RecyclerViewItem> recycleGroupHeaderCache { get; } = new List<RecyclerViewItem>();
122 private List<RecyclerViewItem> recycleGroupFooterCache { get; } = new List<RecyclerViewItem>();
125 /// Base constructor.
127 /// <since_tizen> 9 </since_tizen>
128 public CollectionView() : base()
131 SetKeyboardNavigationSupport(true);
135 /// Base constructor with ItemsSource
137 /// <param name="itemsSource">item's data source</param>
138 /// <since_tizen> 9 </since_tizen>
139 public CollectionView(IEnumerable itemsSource) : this()
141 ItemsSource = itemsSource;
145 /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
147 /// <param name="itemsSource">item's data source</param>
148 /// <param name="layouter">item's layout manager</param>
149 /// <param name="template">item's view template with data bindings</param>
150 [EditorBrowsable(EditorBrowsableState.Never)]
151 public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
153 ItemsSource = itemsSource;
154 ItemTemplate = template;
155 ItemsLayouter = layouter;
159 /// Event of Selection changed.
160 /// previous selection list and current selection will be provided.
162 /// <since_tizen> 9 </since_tizen>
163 public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
166 /// Align item in the viewport when ScrollTo() calls.
168 /// <since_tizen> 9 </since_tizen>
169 public enum ItemScrollTo
172 /// Scroll to show item in nearest viewport on scroll direction.
173 /// item is above the scroll viewport, item will be came into front,
174 /// item is under the scroll viewport, item will be came into end,
175 /// item is in the scroll viewport, no scroll.
177 /// <since_tizen> 9 </since_tizen>
180 /// Scroll to show item in start of the viewport.
182 /// <since_tizen> 9 </since_tizen>
185 /// Scroll to show item in center of the viewport.
187 /// <since_tizen> 9 </since_tizen>
190 /// Scroll to show item in end of the viewport.
192 /// <since_tizen> 9 </since_tizen>
197 /// Item's source data in IEnumerable.
199 /// <since_tizen> 9 </since_tizen>
200 public override IEnumerable ItemsSource
208 if (itemsSource != null)
210 // Clearing old data!
211 if (itemsSource is INotifyCollectionChanged prevNotifyCollectionChanged)
213 prevNotifyCollectionChanged.CollectionChanged -= CollectionChanged;
215 itemsLayouter.Clear();
216 if (selectedItem != null) selectedItem = null;
217 if (selectedItems != null)
219 selectedItems.Clear();
226 if (InternalItemSource != null) InternalItemSource.Dispose();
230 if (itemsSource is INotifyCollectionChanged newNotifyCollectionChanged)
232 newNotifyCollectionChanged.CollectionChanged += CollectionChanged;
235 if (InternalItemSource != null) InternalItemSource.Dispose();
236 InternalItemSource = ItemsSourceFactory.Create(this);
238 if (itemsLayouter == null) return;
240 needInitalizeLayouter = true;
246 /// DataTemplate for items.
247 /// Create visual contents and binding properties.
248 /// return object type is restricted RecyclerViewItem.
249 /// <seealso cref="Tizen.NUI.Binding.DataTemplate" />
251 /// <since_tizen> 9 </since_tizen>
252 public override DataTemplate ItemTemplate
260 itemTemplate = value;
267 needInitalizeLayouter = true;
274 /// Layouting items on the scroll ContentContainer.
275 /// <seealso cref="ItemsLayouter" />
276 /// <seealso cref="LinearLayouter" />
277 /// <seealso cref="GridLayouter" />
279 /// <since_tizen> 9 </since_tizen>
280 public virtual ItemsLayouter ItemsLayouter
284 return itemsLayouter;
288 itemsLayouter = value;
289 base.InternalItemsLayouter = ItemsLayouter;
292 needInitalizeLayouter = false;
296 needInitalizeLayouter = true;
298 var styleName = "Tizen.NUI.Components." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
299 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
300 if (layouterStyle != null)
302 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
309 /// Scrolling direction to display items layout.
311 /// <since_tizen> 9 </since_tizen>
312 public new Direction ScrollingDirection
316 return base.ScrollingDirection;
320 if (base.ScrollingDirection != value)
322 base.ScrollingDirection = value;
323 needInitalizeLayouter = true;
330 /// Selected item in single selection.
332 /// <since_tizen> 9 </since_tizen>
333 public object SelectedItem
335 get => GetValue(SelectedItemProperty);
336 set => SetValue(SelectedItemProperty, value);
340 /// Selected items list in multiple selection.
342 /// <since_tizen> 9 </since_tizen>
343 public IList<object> SelectedItems
345 get => (IList<object>)GetValue(SelectedItemsProperty);
346 // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
350 /// Selection mode to handle items selection. See ItemSelectionMode for details.
352 /// <since_tizen> 9 </since_tizen>
353 public ItemSelectionMode SelectionMode
355 get => (ItemSelectionMode)GetValue(SelectionModeProperty);
356 set => SetValue(SelectionModeProperty, value);
360 /// Command of selection changed.
362 [EditorBrowsable(EditorBrowsableState.Never)]
363 public ICommand SelectionChangedCommand { set; get; }
366 /// Command parameter of selection changed.
368 [EditorBrowsable(EditorBrowsableState.Never)]
369 public object SelectionChangedCommandParameter { set; get; }
372 /// Header item placed in top-most position.
374 /// <remarks>Please note that, internal index will be increased by header.</remarks>
375 /// <since_tizen> 9 </since_tizen>
376 public RecyclerViewItem Header
383 //ContentContainer.Remove(header);
384 Utility.Dispose(header);
389 value.ParentItemsView = this;
390 value.IsHeader = true;
391 ContentContainer.Add(value);
394 needInitalizeLayouter = true;
400 /// Footer item placed in bottom-most position.
402 /// <remarks>Please note that, internal index will be increased by footer.</remarks>
403 /// <since_tizen> 9 </since_tizen>
404 public RecyclerViewItem Footer
411 //ContentContainer.Remove(footer);
412 Utility.Dispose(footer);
416 value.Index = InternalItemSource?.Count ?? 0;
417 value.ParentItemsView = this;
418 value.IsFooter = true;
419 ContentContainer.Add(value);
422 needInitalizeLayouter = true;
428 /// Enable groupable view.
430 [EditorBrowsable(EditorBrowsableState.Never)]
431 public bool IsGrouped
437 needInitalizeLayouter = true;
438 //Need to re-intialize Internal Item Source.
439 if (InternalItemSource != null)
441 InternalItemSource.Dispose();
442 InternalItemSource = null;
444 if (ItemsSource != null)
445 InternalItemSource = ItemsSourceFactory.Create(this);
451 /// DataTemplate of group header.
453 /// <remarks>Please note that, internal index will be increased by group header.
454 /// GroupHeaderTemplate is essential for groupable view.</remarks>
455 [EditorBrowsable(EditorBrowsableState.Never)]
456 public DataTemplate GroupHeaderTemplate
460 return groupHeaderTemplate;
464 groupHeaderTemplate = value;
465 needInitalizeLayouter = true;
471 /// DataTemplate of group footer. Group feature is not supported yet.
473 /// <remarks>Please note that, internal index will be increased by group footer.</remarks>
474 [EditorBrowsable(EditorBrowsableState.Never)]
475 public DataTemplate GroupFooterTemplate
479 return groupFooterTemplate;
483 groupFooterTemplate = value;
484 needInitalizeLayouter = true;
490 /// Internal encapsulated items data source.
492 internal new IGroupableItemSource InternalItemSource
496 return (base.InternalItemSource as IGroupableItemSource);
500 base.InternalItemSource = value;
505 /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
507 [EditorBrowsable(EditorBrowsableState.Never)]
508 internal ItemSizingStrategy SizingStrategy { get; set; }
511 /// <since_tizen> 9 </since_tizen>
512 public override void OnRelayout(Vector2 size, RelayoutContainer container)
514 base.OnRelayout(size, container);
516 wasRelayouted = true;
517 if (needInitalizeLayouter) Init();
521 [EditorBrowsable(EditorBrowsableState.Never)]
522 public override void NotifyDataSetChanged()
524 if (selectedItem != null)
528 if (selectedItems != null)
530 selectedItems.Clear();
533 base.NotifyDataSetChanged();
537 [EditorBrowsable(EditorBrowsableState.Never)]
538 public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
540 View nextFocusedView = null;
542 if (focusedView == null)
544 // If focusedView is null, find child which has previous data index
545 if (ContentContainer.Children.Count > 0 && InternalItemSource.Count > 0)
547 for (int i = 0; i < ContentContainer.Children.Count; i++)
549 RecyclerViewItem item = Children[i] as RecyclerViewItem;
550 if (item?.Index == prevFocusedDataIndex)
552 nextFocusedView = item;
560 // If this is not first focus, request next focus to Layouter
561 nextFocusedView = ItemsLayouter.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
564 if (nextFocusedView != null)
566 // Check next focused view is inside of visible area.
567 // If it is not, move scroll position to make it visible.
568 Position scrollPosition = ContentContainer.CurrentPosition;
569 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
571 float left = nextFocusedView.Position.X;
572 float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
573 float top = nextFocusedView.Position.Y;
574 float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
576 float visibleRectangleLeft = -scrollPosition.X;
577 float visibleRectangleRight = -scrollPosition.X + Size.Width;
578 float visibleRectangleTop = -scrollPosition.Y;
579 float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
581 if (ScrollingDirection == Direction.Horizontal)
583 if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
585 targetPosition = left;
587 else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
589 targetPosition = right - Size.Width;
594 if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
596 targetPosition = top;
598 else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
600 targetPosition = bottom - Size.Height;
604 focusedView = nextFocusedView;
605 prevFocusedDataIndex = (nextFocusedView as RecyclerViewItem)?.Index ?? -1;
607 ScrollTo(targetPosition, true);
611 // If nextView is null, it means that we should move focus to outside of Control.
612 // Return FocusableView depending on direction.
615 case View.FocusDirection.Left:
617 nextFocusedView = LeftFocusableView;
620 case View.FocusDirection.Right:
622 nextFocusedView = RightFocusableView;
625 case View.FocusDirection.Up:
627 nextFocusedView = UpFocusableView;
630 case View.FocusDirection.Down:
632 nextFocusedView = DownFocusableView;
637 if (nextFocusedView != null)
643 //If FocusableView doesn't exist, not move focus.
644 nextFocusedView = focusedView;
648 return nextFocusedView;
652 /// Update selected items list in multiple selection.
654 /// <param name="newSelection">updated selection list by user</param>
655 /// <since_tizen> 9 </since_tizen>
656 public void UpdateSelectedItems(IList<object> newSelection)
658 var oldSelection = new List<object>(SelectedItems);
660 suppressSelectionChangeNotification = true;
662 SelectedItems.Clear();
664 if (newSelection?.Count > 0)
666 for (int n = 0; n < newSelection.Count; n++)
668 SelectedItems.Add(newSelection[n]);
672 suppressSelectionChangeNotification = false;
674 SelectedItemsPropertyChanged(oldSelection, newSelection);
678 /// Scroll to specific position with or without animation.
680 /// <param name="position">Destination.</param>
681 /// <param name="animate">Scroll with or without animation</param>
682 /// <since_tizen> 9 </since_tizen>
683 public new void ScrollTo(float position, bool animate) => base.ScrollTo(position, animate);
686 /// Scroll to specific item's aligned position with or without animation.
688 /// <param name="index">Target item index of dataset.</param>
689 /// <param name="animate">Boolean flag of animation.</param>
690 /// <param name="align">Align state of item. See details in <see cref="ItemScrollTo"/>.</param>
691 /// <since_tizen> 9 </since_tizen>
692 public virtual void ScrollTo(int index, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
694 if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
696 float scrollPos, curPos, curSize, curItemSize;
697 (float x, float y) = ItemsLayouter.GetItemPosition(index);
698 (float width, float height) = ItemsLayouter.GetItemSize(index);
699 if (ScrollingDirection == Direction.Horizontal)
702 curPos = ScrollPosition.X;
703 curSize = Size.Width;
709 curPos = ScrollPosition.Y;
710 curSize = Size.Height;
711 curItemSize = height;
714 //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalItemSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
717 case ItemScrollTo.Start:
720 case ItemScrollTo.Center:
721 scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
723 case ItemScrollTo.End:
724 scrollPos = scrollPos - curSize + curItemSize;
726 case ItemScrollTo.Nearest:
727 if (scrollPos < curPos - curItemSize)
729 // item is placed before the current screen. scrollTo.Top
731 else if (scrollPos >= curPos + curSize + curItemSize)
733 // item is placed after the current screen. scrollTo.End
734 scrollPos = scrollPos - curSize + curItemSize;
738 // item is in the scroller. ScrollTo() is ignored.
744 //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
745 base.ScrollTo(scrollPos, animate);
749 /// Apply style to CollectionView
751 /// <param name="viewStyle">The style to apply.</param>
752 [EditorBrowsable(EditorBrowsableState.Never)]
753 public override void ApplyStyle(ViewStyle viewStyle)
755 base.ApplyStyle(viewStyle);
756 if (viewStyle != null)
758 //Extension = RecyclerViewItemStyle.CreateExtension();
760 if (itemsLayouter != null)
762 string styleName = "Tizen.NUI.Compoenents." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
763 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
764 if (layouterStyle != null)
765 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
769 // Realize and Decorate the item.
770 internal override RecyclerViewItem RealizeItem(int index)
772 RecyclerViewItem item;
773 if (index == 0 && Header != null)
779 if (index == InternalItemSource.Count - 1 && Footer != null)
787 var context = InternalItemSource.GetItem(index);
788 if (InternalItemSource.IsGroupHeader(index))
790 DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
792 RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
793 if (groupHeader == null)
795 groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
797 groupHeader.Template = templ;
798 groupHeader.isGroupHeader = true;
799 groupHeader.isGroupFooter = false;
800 ContentContainer.Add(groupHeader);
802 groupHeader.ParentItemsView = this;
803 groupHeader.Index = index;
804 groupHeader.ParentGroup = context;
805 groupHeader.BindingContext = context;
809 else if (InternalItemSource.IsGroupFooter(index))
811 DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
813 RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
814 if (groupFooter == null)
816 groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this);
818 groupFooter.Template = templ;
819 groupFooter.isGroupHeader = false;
820 groupFooter.isGroupFooter = true;
821 ContentContainer.Add(groupFooter);
823 groupFooter.ParentItemsView = this;
824 groupFooter.Index = index;
825 groupFooter.ParentGroup = context;
826 groupFooter.BindingContext = context;
833 item = base.RealizeItem(index);
834 item.ParentGroup = InternalItemSource.GetGroupParent(index);
839 item = base.RealizeItem(index);
842 switch (SelectionMode)
844 case ItemSelectionMode.Single:
845 case ItemSelectionMode.SingleAlways:
846 if (item.BindingContext != null && item.BindingContext == SelectedItem)
848 item.IsSelected = true;
852 case ItemSelectionMode.Multiple:
853 if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
855 item.IsSelected = true;
858 case ItemSelectionMode.None:
859 item.IsSelectable = false;
865 // Unrealize and caching the item.
866 internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
868 if (item == null) return;
879 if (item.isGroupHeader || item.isGroupFooter)
882 item.ParentItemsView = null;
883 item.BindingContext = null;
884 item.IsPressed = false;
885 item.IsSelected = false;
886 item.IsEnabled = true;
888 //item.Relayout -= OnItemRelayout;
889 if (!recycle || !PushRecycleGroupCache(item))
890 Utility.Dispose(item);
894 base.UnrealizeItem(item, recycle);
897 internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
899 if (suppressSelectionChangeNotification)
904 foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
906 if (item.BindingContext == null) continue;
907 if (newSelection.Contains(item.BindingContext))
909 if (!item.IsSelected) item.IsSelected = true;
913 if (item.IsSelected) item.IsSelected = false;
916 SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
918 OnPropertyChanged(SelectedItemsProperty.PropertyName);
922 /// Internal selection callback.
924 /// <since_tizen> 9 </since_tizen>
925 protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
931 /// Adjust scrolling position by own scrolling rules.
932 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
934 /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
935 /// <returns>Adjusted scroll destination</returns>
936 [EditorBrowsable(EditorBrowsableState.Never)]
937 protected override float AdjustTargetPositionOfScrollAnimation(float position)
939 // Destination is depending on implementation of layout manager.
940 // Get destination from layout manager.
941 return ItemsLayouter.CalculateCandidateScrollPosition(position);
945 /// OnScroll event callback. Requesting layout to the layouter with given scrollPosition.
947 /// <param name="source">Scroll source object</param>
948 /// <param name="args">Scroll event argument</param>
949 /// <since_tizen> 9 </since_tizen>
950 protected override void OnScrolling(object source, ScrollEventArgs args)
952 if (disposed) return;
954 if (needInitalizeLayouter)
956 ItemsLayouter.Initialize(this);
957 needInitalizeLayouter = false;
960 base.OnScrolling(source, args);
964 /// Dispose ItemsView and all children on it.
966 /// <param name="type">Dispose type.</param>
967 /// <since_tizen> 9 </since_tizen>
968 protected override void Dispose(DisposeTypes type)
975 if (type == DisposeTypes.Explicit)
977 // From now on, no need to use this properties,
978 // so remove reference, to push it into garbage collector.
980 // Arugable to disposing user-created members.
984 Utility.Dispose(Header);
989 Utility.Dispose(Footer);
994 groupHeaderTemplate = null;
995 groupFooterTemplate = null;
997 if (selectedItem != null)
1001 if (selectedItems != null)
1003 selectedItems.Clear();
1004 selectedItems = null;
1006 if (InternalItemSource != null)
1008 InternalItemSource.Dispose();
1009 InternalItemSource = null;
1011 if (recycleGroupHeaderCache != null)
1013 foreach(RecyclerViewItem item in recycleGroupHeaderCache)
1015 UnrealizeItem(item, false);
1017 recycleGroupHeaderCache.Clear();
1019 if (recycleGroupFooterCache != null)
1021 foreach(RecyclerViewItem item in recycleGroupFooterCache)
1023 UnrealizeItem(item, false);
1025 recycleGroupFooterCache.Clear();
1032 private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
1034 var command = colView.SelectionChangedCommand;
1036 if (command != null)
1038 var commandParameter = colView.SelectionChangedCommandParameter;
1040 if (command.CanExecute(commandParameter))
1042 command.Execute(commandParameter);
1045 colView.SelectionChanged?.Invoke(colView, args);
1046 colView.OnSelectionChanged(args);
1049 private static object CoerceSelectedItems(BindableObject bindable, object value)
1053 return new SelectionList((CollectionView)bindable);
1056 if (value is SelectionList)
1061 return new SelectionList((CollectionView)bindable, value as IList<object>);
1064 private static void SelectionModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
1066 var colView = (CollectionView)bindable;
1068 var oldMode = (ItemSelectionMode)oldValue;
1069 var newMode = (ItemSelectionMode)newValue;
1071 IList<object> previousSelection = new List<object>();
1072 IList<object> newSelection = new List<object>();
1076 case ItemSelectionMode.None:
1078 case ItemSelectionMode.Single:
1079 if (colView.SelectedItem != null)
1081 previousSelection.Add(colView.SelectedItem);
1084 case ItemSelectionMode.Multiple:
1085 previousSelection = colView.SelectedItems;
1091 case ItemSelectionMode.None:
1093 case ItemSelectionMode.Single:
1094 if (colView.SelectedItem != null)
1096 newSelection.Add(colView.SelectedItem);
1099 case ItemSelectionMode.Multiple:
1100 newSelection = colView.SelectedItems;
1104 if (previousSelection.Count == newSelection.Count)
1106 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
1108 // Both selections are empty or have the same single item; no reason to signal a change
1113 var args = new SelectionChangedEventArgs(previousSelection, newSelection);
1114 SelectionPropertyChanged(colView, args);
1119 if (ItemsSource == null) return;
1120 if (ItemsLayouter == null) return;
1121 if (ItemTemplate == null) return;
1123 if (disposed) return;
1124 if (needInitalizeLayouter)
1126 if (InternalItemSource == null) return;
1128 InternalItemSource.HasHeader = (header != null);
1129 InternalItemSource.HasFooter = (footer != null);
1132 if (!wasRelayouted) return;
1134 if (needInitalizeLayouter)
1136 ItemsLayouter.Initialize(this);
1137 needInitalizeLayouter = false;
1139 ItemsLayouter.RequestLayout(0.0f, true);
1141 if (ScrollingDirection == Direction.Horizontal)
1143 ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
1147 ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
1151 private bool PushRecycleGroupCache(RecyclerViewItem item)
1153 if (item == null) throw new ArgumentNullException(nameof(item));
1154 if (RecycleCache.Count >= 20) return false;
1155 if (item.Template == null) return false;
1156 if (item.isGroupHeader)
1158 recycleGroupHeaderCache.Add(item);
1160 else if (item.isGroupFooter)
1162 recycleGroupFooterCache.Add(item);
1170 private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1172 RecyclerViewItem viewItem = null;
1174 var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1175 for (int i = 0; i < Cache.Count; i++)
1177 viewItem = Cache[i];
1178 if (Template == viewItem.Template) break;
1181 if (viewItem != null)
1183 Cache.Remove(viewItem);
1188 private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
1190 switch (args.Action)
1192 case NotifyCollectionChangedAction.Add:
1194 case NotifyCollectionChangedAction.Remove:
1195 // Clear removed items.
1196 if (args.OldItems != null)
1198 if (args.OldItems.Contains(selectedItem))
1200 selectedItem = null;
1203 if (selectedItems != null)
1205 foreach (object removed in args.OldItems)
1207 if (selectedItems.Contains(removed))
1209 selectedItems.Remove(removed);
1215 case NotifyCollectionChangedAction.Replace:
1217 case NotifyCollectionChangedAction.Move:
1219 case NotifyCollectionChangedAction.Reset:
1222 throw new ArgumentOutOfRangeException(nameof(args));