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 partial 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 List<RecyclerViewItem> recycleGroupHeaderCache { get; } = new List<RecyclerViewItem>();
120 private List<RecyclerViewItem> recycleGroupFooterCache { get; } = new List<RecyclerViewItem>();
121 private bool delayedScrollTo;
122 private (float position, bool anim) delayedScrollToParam;
124 private bool delayedIndexScrollTo;
125 private (int index, bool anim, ItemScrollTo scrollTo) delayedIndexScrollToParam;
128 /// Base constructor.
130 /// <since_tizen> 9 </since_tizen>
131 public CollectionView() : base()
134 SetKeyboardNavigationSupport(true);
138 /// Base constructor with ItemsSource
140 /// <param name="itemsSource">item's data source</param>
141 /// <since_tizen> 9 </since_tizen>
142 public CollectionView(IEnumerable itemsSource) : this()
144 ItemsSource = itemsSource;
148 /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
150 /// <param name="itemsSource">item's data source</param>
151 /// <param name="layouter">item's layout manager</param>
152 /// <param name="template">item's view template with data bindings</param>
153 [EditorBrowsable(EditorBrowsableState.Never)]
154 public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
156 ItemsSource = itemsSource;
157 ItemTemplate = template;
158 ItemsLayouter = layouter;
162 /// Event of Selection changed.
163 /// previous selection list and current selection will be provided.
165 /// <since_tizen> 9 </since_tizen>
166 public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
169 /// Align item in the viewport when ScrollTo() calls.
171 /// <since_tizen> 9 </since_tizen>
172 public enum ItemScrollTo
175 /// Scroll to show item in nearest viewport on scroll direction.
176 /// item is above the scroll viewport, item will be came into front,
177 /// item is under the scroll viewport, item will be came into end,
178 /// item is in the scroll viewport, no scroll.
180 /// <since_tizen> 9 </since_tizen>
183 /// Scroll to show item in start of the viewport.
185 /// <since_tizen> 9 </since_tizen>
188 /// Scroll to show item in center of the viewport.
190 /// <since_tizen> 9 </since_tizen>
193 /// Scroll to show item in end of the viewport.
195 /// <since_tizen> 9 </since_tizen>
200 /// Item's source data in IEnumerable.
202 /// <since_tizen> 9 </since_tizen>
203 public override IEnumerable ItemsSource
205 get => (IEnumerable)GetValue(RecyclerView.ItemsSourceProperty);
206 set => SetValue(RecyclerView.ItemsSourceProperty, value);
209 internal override IEnumerable InternalItemsSource
217 if (itemsSource != null)
219 // Clearing old data!
220 if (itemsSource is INotifyCollectionChanged prevNotifyCollectionChanged)
222 prevNotifyCollectionChanged.CollectionChanged -= CollectionChanged;
224 if (selectedItem != null) selectedItem = null;
225 selectedItems?.Clear();
228 itemsSource = (IEnumerable)value;
230 if (itemsSource == null)
232 InternalItemSource?.Dispose();
233 InternalItemSource = null;
234 itemsLayouter?.Clear();
238 if (itemsSource is INotifyCollectionChanged newNotifyCollectionChanged)
240 newNotifyCollectionChanged.CollectionChanged += CollectionChanged;
243 InternalItemSource?.Dispose();
244 InternalItemSource = ItemsSourceFactory.Create(this);
246 if (itemsLayouter == null) return;
248 needInitalizeLayouter = true;
255 /// DataTemplate for items.
256 /// Create visual contents and binding properties.
257 /// return object type is restricted RecyclerViewItem.
258 /// <seealso cref="Tizen.NUI.Binding.DataTemplate" />
260 /// <since_tizen> 9 </since_tizen>
261 public override DataTemplate ItemTemplate
265 return GetValue(ItemTemplateProperty) as DataTemplate;
269 SetValue(ItemTemplateProperty, value);
270 NotifyPropertyChanged();
273 internal override DataTemplate InternalItemTemplate
281 itemTemplate = value;
287 needInitalizeLayouter = true;
294 /// Layouting items on the scroll ContentContainer.
295 /// <seealso cref="ItemsLayouter" />
296 /// <seealso cref="LinearLayouter" />
297 /// <seealso cref="GridLayouter" />
299 /// <since_tizen> 9 </since_tizen>
300 public virtual ItemsLayouter ItemsLayouter
304 return GetValue(ItemsLayouterProperty) as ItemsLayouter;
308 SetValue(ItemsLayouterProperty, value);
309 NotifyPropertyChanged();
315 [EditorBrowsable(EditorBrowsableState.Never)]
316 protected override ItemsLayouter InternalItemsLayouter
320 return itemsLayouter;
324 itemsLayouter?.Clear();
327 itemsLayouter = value;
328 base.InternalItemsLayouter = itemsLayouter;
331 needInitalizeLayouter = false;
335 needInitalizeLayouter = true;
337 var styleName = "Tizen.NUI.Components." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
338 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
339 if (layouterStyle != null)
341 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
348 /// Scrolling direction to display items layout.
350 /// <since_tizen> 9 </since_tizen>
351 public new Direction ScrollingDirection
355 return (Direction)GetValue(ScrollingDirectionProperty);
359 SetValue(ScrollingDirectionProperty, value);
360 NotifyPropertyChanged();
363 private Direction InternalScrollingDirection
367 return base.ScrollingDirection;
371 if (base.ScrollingDirection != value)
373 base.ScrollingDirection = value;
374 needInitalizeLayouter = true;
381 /// Selected item in single selection.
383 /// <since_tizen> 9 </since_tizen>
384 public object SelectedItem
386 get => GetValue(SelectedItemProperty);
387 set => SetValue(SelectedItemProperty, value);
391 /// Selected items list in multiple selection.
393 /// <since_tizen> 9 </since_tizen>
394 public IList<object> SelectedItems
396 get => (IList<object>)GetValue(SelectedItemsProperty);
397 // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
401 /// Selection mode to handle items selection. See ItemSelectionMode for details.
403 /// <since_tizen> 9 </since_tizen>
404 public ItemSelectionMode SelectionMode
406 get => (ItemSelectionMode)GetValue(SelectionModeProperty);
407 set => SetValue(SelectionModeProperty, value);
411 /// Command of selection changed.
413 [EditorBrowsable(EditorBrowsableState.Never)]
414 public ICommand SelectionChangedCommand
418 return GetValue(SelectionChangedCommandProperty) as ICommand;
422 SetValue(SelectionChangedCommandProperty, value);
423 NotifyPropertyChanged();
426 private ICommand InternalSelectionChangedCommand { set; get; }
429 /// Command parameter of selection changed.
431 [EditorBrowsable(EditorBrowsableState.Never)]
432 public object SelectionChangedCommandParameter
436 return GetValue(SelectionChangedCommandParameterProperty);
440 SetValue(SelectionChangedCommandParameterProperty, value);
441 NotifyPropertyChanged();
444 private object InternalSelectionChangedCommandParameter { set; get; }
447 /// Header item placed in top-most position.
449 /// <remarks>Please note that, internal index will be increased by header.</remarks>
450 /// <since_tizen> 9 </since_tizen>
451 public RecyclerViewItem Header
455 return GetValue(HeaderProperty) as RecyclerViewItem;
459 SetValue(HeaderProperty, value);
460 NotifyPropertyChanged();
463 private RecyclerViewItem InternalHeader
470 //ContentContainer.Remove(header);
471 Utility.Dispose(header);
476 value.ParentItemsView = this;
477 value.IsHeader = true;
478 ContentContainer.Add(value);
481 if (InternalItemSource != null)
483 InternalItemSource.HasHeader = (value != null);
485 needInitalizeLayouter = true;
491 /// Footer item placed in bottom-most position.
493 /// <remarks>Please note that, internal index will be increased by footer.</remarks>
494 /// <since_tizen> 9 </since_tizen>
495 public RecyclerViewItem Footer
499 return GetValue(FooterProperty) as RecyclerViewItem;
503 SetValue(FooterProperty, value);
504 NotifyPropertyChanged();
507 private RecyclerViewItem InternalFooter
514 //ContentContainer.Remove(footer);
515 Utility.Dispose(footer);
519 value.Index = InternalItemSource?.Count ?? 0;
520 value.ParentItemsView = this;
521 value.IsFooter = true;
522 ContentContainer.Add(value);
525 if (InternalItemSource != null)
527 InternalItemSource.HasFooter = (value != null);
529 needInitalizeLayouter = true;
535 /// Enable groupable view.
537 [EditorBrowsable(EditorBrowsableState.Never)]
538 public bool IsGrouped
542 return (bool)GetValue(IsGroupedProperty);
546 SetValue(IsGroupedProperty, value);
547 NotifyPropertyChanged();
550 private bool InternalIsGrouped
556 needInitalizeLayouter = true;
557 //Need to re-intialize Internal Item Source.
558 if (InternalItemSource != null)
560 InternalItemSource.Dispose();
561 InternalItemSource = null;
563 if (ItemsSource != null)
564 InternalItemSource = ItemsSourceFactory.Create(this);
570 /// DataTemplate of group header.
572 /// <remarks>Please note that, internal index will be increased by group header.
573 /// GroupHeaderTemplate is essential for groupable view.</remarks>
574 [EditorBrowsable(EditorBrowsableState.Never)]
575 public DataTemplate GroupHeaderTemplate
579 return GetValue(GroupHeaderTemplateProperty) as DataTemplate;
583 SetValue(GroupHeaderTemplateProperty, value);
584 NotifyPropertyChanged();
587 private DataTemplate InternalGroupHeaderTemplate
591 return groupHeaderTemplate;
595 groupHeaderTemplate = value;
596 needInitalizeLayouter = true;
597 //Need to re-intialize Internal Item Source.
598 if (InternalItemSource != null)
600 InternalItemSource.Dispose();
601 InternalItemSource = null;
603 if (ItemsSource != null)
604 InternalItemSource = ItemsSourceFactory.Create(this);
610 /// DataTemplate of group footer. Group feature is not supported yet.
612 /// <remarks>Please note that, internal index will be increased by group footer.</remarks>
613 [EditorBrowsable(EditorBrowsableState.Never)]
614 public DataTemplate GroupFooterTemplate
618 return GetValue(GroupFooterTemplateProperty) as DataTemplate;
622 SetValue(GroupFooterTemplateProperty, value);
623 NotifyPropertyChanged();
626 private DataTemplate InternalGroupFooterTemplate
630 return groupFooterTemplate;
634 groupFooterTemplate = value;
635 needInitalizeLayouter = true;
636 //Need to re-intialize Internal Item Source.
637 if (InternalItemSource != null)
639 InternalItemSource.Dispose();
640 InternalItemSource = null;
642 if (ItemsSource != null)
643 InternalItemSource = ItemsSourceFactory.Create(this);
649 /// Internal encapsulated items data source.
651 internal new IGroupableItemSource InternalItemSource
655 return (base.InternalItemSource as IGroupableItemSource);
659 base.InternalItemSource = value;
664 /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
666 [EditorBrowsable(EditorBrowsableState.Never)]
667 internal ItemSizingStrategy SizingStrategy { get; set; }
670 /// <since_tizen> 9 </since_tizen>
671 public override void OnRelayout(Vector2 size, RelayoutContainer container)
673 base.OnRelayout(size, container);
675 wasRelayouted = true;
676 if (needInitalizeLayouter) Init();
680 [EditorBrowsable(EditorBrowsableState.Never)]
681 public override void NotifyDataSetChanged()
683 if (selectedItem != null)
687 if (selectedItems != null)
689 selectedItems.Clear();
692 base.NotifyDataSetChanged();
696 /// Update selected items list in multiple selection.
698 /// <param name="newSelection">updated selection list by user</param>
699 /// <since_tizen> 9 </since_tizen>
700 public void UpdateSelectedItems(IList<object> newSelection)
702 if (SelectedItems != null)
704 var oldSelection = new List<object>(SelectedItems);
706 suppressSelectionChangeNotification = true;
708 SelectedItems.Clear();
710 if (newSelection?.Count > 0)
712 for (int n = 0; n < newSelection.Count; n++)
714 SelectedItems.Add(newSelection[n]);
718 suppressSelectionChangeNotification = false;
720 SelectedItemsPropertyChanged(oldSelection, newSelection);
725 /// Scroll to specific position with or without animation.
727 /// <param name="position">Destination.</param>
728 /// <param name="animate">Scroll with or without animation</param>
729 /// <since_tizen> 9 </since_tizen>
730 public new void ScrollTo(float position, bool animate)
732 if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
733 if ((InternalItemSource == null) || needInitalizeLayouter)
735 delayedScrollTo = true;
736 delayedScrollToParam = (position, animate);
740 base.ScrollTo(position, animate);
744 /// Scrolls to the item at the specified index.
746 /// <param name="index">Index of item.</param>
747 [EditorBrowsable(EditorBrowsableState.Never)]
748 public new void ScrollToIndex(int index)
750 ScrollTo(index, true, ItemScrollTo.Start);
754 /// Scroll to specific item's aligned position with or without animation.
756 /// <param name="index">Target item index of dataset.</param>
757 /// <param name="animate">Boolean flag of animation.</param>
758 /// <param name="align">Align state of item. See details in <see cref="ItemScrollTo"/>.</param>
759 /// <since_tizen> 9 </since_tizen>
760 public virtual void ScrollTo(int index, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
762 if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
763 if ((InternalItemSource == null) || needInitalizeLayouter)
765 delayedIndexScrollTo = true;
766 delayedIndexScrollToParam = (index, animate, align);
769 if (index < 0 || index >= InternalItemSource.Count)
771 throw new Exception("index is out of boundary. index should be a value between (0, " + InternalItemSource.Count.ToString() + ").");
774 float scrollPos, curPos, curSize, curItemSize;
775 (float x, float y) = ItemsLayouter.GetItemPosition(index);
776 (float width, float height) = ItemsLayouter.GetItemSize(index);
777 if (ScrollingDirection == Direction.Horizontal)
780 curPos = ScrollPosition.X;
781 curSize = Size.Width;
787 curPos = ScrollPosition.Y;
788 curSize = Size.Height;
789 curItemSize = height;
792 //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalItemSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
795 case ItemScrollTo.Start:
798 case ItemScrollTo.Center:
799 scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
801 case ItemScrollTo.End:
802 scrollPos = scrollPos - curSize + curItemSize;
804 case ItemScrollTo.Nearest:
805 if (scrollPos < curPos - curItemSize)
807 // item is placed before the current screen. scrollTo.Top
809 else if (scrollPos >= curPos + curSize + curItemSize)
811 // item is placed after the current screen. scrollTo.End
812 scrollPos = scrollPos - curSize + curItemSize;
816 // item is in the scroller. ScrollTo() is ignored.
822 //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
823 base.ScrollTo(scrollPos, animate);
827 /// Apply style to CollectionView
829 /// <param name="viewStyle">The style to apply.</param>
830 [EditorBrowsable(EditorBrowsableState.Never)]
831 public override void ApplyStyle(ViewStyle viewStyle)
833 base.ApplyStyle(viewStyle);
834 if (viewStyle != null)
836 //Extension = RecyclerViewItemStyle.CreateExtension();
838 if (itemsLayouter != null)
840 string styleName = "Tizen.NUI.Compoenents." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
841 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
842 if (layouterStyle != null)
843 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
848 /// Initialize AT-SPI object.
850 [EditorBrowsable(EditorBrowsableState.Never)]
851 public override void OnInitialize()
854 AccessibilityRole = Role.List;
858 /// Scroll to specified item
861 /// Make sure that the item that is about to receive the accessibility highlight is visible.
863 [EditorBrowsable(EditorBrowsableState.Never)]
864 protected override bool AccessibilityScrollToChild(View child)
866 if (ScrollingDirection == Direction.Horizontal)
868 if (child.ScreenPosition.X + child.Size.Width <= this.ScreenPosition.X)
870 ScrollTo((float)(child.ScreenPosition.X - ContentContainer.ScreenPosition.X), false);
872 else if (child.ScreenPosition.X >= this.ScreenPosition.X + this.Size.Width)
874 ScrollTo((float)(child.ScreenPosition.X + child.Size.Width - ContentContainer.ScreenPosition.X - this.Size.Width), false);
879 if (child.ScreenPosition.Y + child.Size.Height <= this.ScreenPosition.Y)
881 ScrollTo((float)(child.ScreenPosition.Y - ContentContainer.ScreenPosition.Y), false);
883 else if (child.ScreenPosition.Y >= this.ScreenPosition.Y + this.Size.Height)
885 ScrollTo((float)(child.ScreenPosition.Y + child.Size.Height - ContentContainer.ScreenPosition.Y - this.Size.Height), false);
891 // Realize and Decorate the item.
892 internal override RecyclerViewItem RealizeItem(int index)
894 RecyclerViewItem item;
895 if (index == 0 && Header != null)
901 if (index == InternalItemSource.Count - 1 && Footer != null)
909 var context = InternalItemSource.GetItem(index);
910 if (InternalItemSource.IsGroupHeader(index))
912 DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
914 RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
915 if (groupHeader == null)
917 groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
919 groupHeader.Template = templ;
920 groupHeader.isGroupHeader = true;
921 groupHeader.isGroupFooter = false;
922 ContentContainer.Add(groupHeader);
925 if (groupHeader != null)
927 groupHeader.ParentItemsView = this;
928 groupHeader.Index = index;
929 groupHeader.ParentGroup = context;
930 groupHeader.BindingContext = context;
935 else if (InternalItemSource.IsGroupFooter(index))
937 DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
939 RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
940 if (groupFooter == null)
942 groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this);
944 groupFooter.Template = templ;
945 groupFooter.isGroupHeader = false;
946 groupFooter.isGroupFooter = true;
947 ContentContainer.Add(groupFooter);
950 if (groupFooter != null)
952 groupFooter.ParentItemsView = this;
953 groupFooter.Index = index;
954 groupFooter.ParentGroup = context;
955 groupFooter.BindingContext = context;
962 item = base.RealizeItem(index);
964 throw new Exception("Item realize failed by Null content return.");
965 item.ParentGroup = InternalItemSource.GetGroupParent(index);
970 item = base.RealizeItem(index);
974 throw new Exception("Item realize failed by Null content return.");
976 switch (SelectionMode)
978 case ItemSelectionMode.Single:
979 case ItemSelectionMode.SingleAlways:
980 if (item.BindingContext != null && item.BindingContext == SelectedItem)
982 item.IsSelected = true;
986 case ItemSelectionMode.Multiple:
987 if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
989 item.IsSelected = true;
992 case ItemSelectionMode.None:
993 item.IsSelectable = false;
999 // Unrealize and caching the item.
1000 internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
1002 if (item == null) return;
1013 if (item.isGroupHeader || item.isGroupFooter)
1016 item.ParentItemsView = null;
1017 item.BindingContext = null;
1018 item.IsPressed = false;
1019 item.IsSelected = false;
1020 item.IsEnabled = true;
1022 //item.Relayout -= OnItemRelayout;
1023 if (!recycle || !PushRecycleGroupCache(item))
1024 Utility.Dispose(item);
1028 base.UnrealizeItem(item, recycle);
1031 internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
1033 if (suppressSelectionChangeNotification)
1038 foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
1040 if (item.BindingContext == null) continue;
1041 if (newSelection.Contains(item.BindingContext))
1043 if (!item.IsSelected) item.IsSelected = true;
1047 if (item.IsSelected) item.IsSelected = false;
1050 SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
1052 OnPropertyChanged(SelectedItemsProperty.PropertyName);
1056 /// Internal selection callback.
1058 /// <since_tizen> 9 </since_tizen>
1059 protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
1061 //Selection Callback
1065 /// Adjust scrolling position by own scrolling rules.
1066 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1068 /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
1069 /// <returns>Adjusted scroll destination</returns>
1070 [EditorBrowsable(EditorBrowsableState.Never)]
1071 protected override float AdjustTargetPositionOfScrollAnimation(float position)
1073 // Destination is depending on implementation of layout manager.
1074 // Get destination from layout manager.
1075 return ItemsLayouter?.CalculateCandidateScrollPosition(position) ?? position;
1079 [EditorBrowsable(EditorBrowsableState.Never)]
1080 protected override void ClearCache()
1082 foreach (RecyclerViewItem item in recycleGroupHeaderCache)
1084 Utility.Dispose(item);
1086 recycleGroupHeaderCache.Clear();
1087 foreach (RecyclerViewItem item in recycleGroupFooterCache)
1089 Utility.Dispose(item);
1091 recycleGroupFooterCache.Clear();
1097 /// OnScroll event callback. Requesting layout to the layouter with given scrollPosition.
1099 /// <param name="source">Scroll source object</param>
1100 /// <param name="args">Scroll event argument</param>
1101 /// <since_tizen> 9 </since_tizen>
1102 protected override void OnScrolling(object source, ScrollEventArgs args)
1104 if (disposed) return;
1106 if (needInitalizeLayouter && (ItemsLayouter != null))
1108 ItemsLayouter.Initialize(this);
1109 needInitalizeLayouter = false;
1112 base.OnScrolling(source, args);
1116 /// Dispose ItemsView and all children on it.
1118 /// <param name="type">Dispose type.</param>
1119 /// <since_tizen> 9 </since_tizen>
1120 protected override void Dispose(DisposeTypes type)
1127 if (type == DisposeTypes.Explicit)
1129 // From now on, no need to use this properties,
1130 // so remove reference, to push it into garbage collector.
1132 // Arugable to disposing user-created members.
1136 Utility.Dispose(Header);
1141 Utility.Dispose(Footer);
1146 groupHeaderTemplate = null;
1147 groupFooterTemplate = null;
1149 if (selectedItem != null)
1151 selectedItem = null;
1153 if (selectedItems != null)
1155 selectedItems.Clear();
1156 selectedItems = null;
1158 if (InternalItemSource != null)
1160 InternalItemSource.Dispose();
1161 InternalItemSource = null;
1168 private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
1170 var command = colView.SelectionChangedCommand;
1172 if (command != null)
1174 var commandParameter = colView.SelectionChangedCommandParameter;
1176 if (command.CanExecute(commandParameter))
1178 command.Execute(commandParameter);
1181 colView.SelectionChanged?.Invoke(colView, args);
1182 colView.OnSelectionChanged(args);
1185 private static object CoerceSelectedItems(BindableObject bindable, object value)
1189 return new SelectionList((CollectionView)bindable);
1192 if (value is SelectionList)
1197 return new SelectionList((CollectionView)bindable, value as IList<object>);
1200 private static void SelectionModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
1202 var colView = (CollectionView)bindable;
1204 var oldMode = (ItemSelectionMode)oldValue;
1205 var newMode = (ItemSelectionMode)newValue;
1207 IList<object> previousSelection = new List<object>();
1208 IList<object> newSelection = new List<object>();
1212 case ItemSelectionMode.None:
1214 case ItemSelectionMode.Single:
1215 if (colView.SelectedItem != null)
1217 previousSelection.Add(colView.SelectedItem);
1220 case ItemSelectionMode.Multiple:
1221 previousSelection = colView.SelectedItems;
1227 case ItemSelectionMode.None:
1229 case ItemSelectionMode.Single:
1230 if (colView.SelectedItem != null)
1232 newSelection.Add(colView.SelectedItem);
1235 case ItemSelectionMode.Multiple:
1236 newSelection = colView.SelectedItems;
1240 if (previousSelection.Count == newSelection.Count)
1242 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
1244 // Both selections are empty or have the same single item; no reason to signal a change
1249 var args = new SelectionChangedEventArgs(previousSelection, newSelection);
1250 SelectionPropertyChanged(colView, args);
1255 if (ItemsSource == null) return;
1256 if (ItemsLayouter == null) return;
1257 if (ItemTemplate == null) return;
1259 if (disposed) return;
1260 if (needInitalizeLayouter)
1262 if (InternalItemSource == null) return;
1264 InternalItemSource.HasHeader = (header != null);
1265 InternalItemSource.HasFooter = (footer != null);
1268 if (!wasRelayouted) return;
1270 if (needInitalizeLayouter)
1272 itemsLayouter.Clear();
1275 ItemsLayouter.Initialize(this);
1276 needInitalizeLayouter = false;
1278 ItemsLayouter.RequestLayout(0.0f, true);
1280 if (delayedScrollTo)
1282 delayedScrollTo = false;
1283 ScrollTo(delayedScrollToParam.position, delayedScrollToParam.anim);
1286 if (delayedIndexScrollTo)
1288 delayedIndexScrollTo = false;
1289 ScrollTo(delayedIndexScrollToParam.index, delayedIndexScrollToParam.anim, delayedIndexScrollToParam.scrollTo);
1292 if (ScrollingDirection == Direction.Horizontal)
1294 ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
1298 ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
1302 private bool PushRecycleGroupCache(RecyclerViewItem item)
1304 if (item == null) throw new ArgumentNullException(nameof(item));
1305 if (RecycleCache.Count >= 20) return false;
1306 if (item.Template == null) return false;
1307 if (item.isGroupHeader)
1309 recycleGroupHeaderCache.Add(item);
1311 else if (item.isGroupFooter)
1313 recycleGroupFooterCache.Add(item);
1321 private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1323 RecyclerViewItem viewItem = null;
1325 var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1326 for (int i = 0; i < Cache.Count; i++)
1328 viewItem = Cache[i];
1329 if (Template == viewItem.Template) break;
1332 if (viewItem != null)
1334 Cache.Remove(viewItem);
1339 private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
1341 switch (args.Action)
1343 case NotifyCollectionChangedAction.Add:
1345 case NotifyCollectionChangedAction.Remove:
1346 // Clear removed items.
1347 if (args.OldItems != null)
1349 if (args.OldItems.Contains(selectedItem))
1351 selectedItem = null;
1354 if (selectedItems != null)
1356 foreach (object removed in args.OldItems)
1358 if (selectedItems.Contains(removed))
1360 selectedItems.Remove(removed);
1366 case NotifyCollectionChangedAction.Replace:
1368 case NotifyCollectionChangedAction.Move:
1370 case NotifyCollectionChangedAction.Reset:
1373 throw new ArgumentOutOfRangeException(nameof(args));