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 = bindable as CollectionView;
45 throw new Exception("Bindable object is not CollectionView.");
48 oldValue = colView.selectedItem;
49 colView.selectedItem = newValue;
51 var args = new SelectionChangedEventArgs(oldValue, newValue);
52 foreach (RecyclerViewItem item in colView.ContentContainer.Children.Where((item) => item is RecyclerViewItem))
54 if (item.BindingContext == null)
59 if (item.BindingContext == oldValue)
61 item.IsSelected = false;
63 else if (item.BindingContext == newValue)
65 item.IsSelected = true;
69 SelectionPropertyChanged(colView, args);
71 defaultValueCreator: (bindable) =>
73 var colView = bindable as CollectionView;
76 throw new Exception("Bindable object is not CollectionView.");
79 return colView.selectedItem;
83 /// Binding Property of selected items list in multiple selection.
85 /// <since_tizen> 9 </since_tizen>
86 public static readonly BindableProperty SelectedItemsProperty =
87 BindableProperty.Create(nameof(SelectedItems), typeof(IList<object>), typeof(CollectionView), null,
88 propertyChanged: (bindable, oldValue, newValue) =>
90 var colView = bindable as CollectionView;
93 throw new Exception("Bindable object is not CollectionView.");
96 var oldSelection = colView.selectedItems ?? selectEmpty;
97 //FIXME : CoerceSelectedItems calls only isCreatedByXaml
98 var newSelection = (SelectionList)CoerceSelectedItems(colView, newValue);
99 colView.selectedItems = newSelection;
100 colView.SelectedItemsPropertyChanged(oldSelection, newSelection);
102 defaultValueCreator: (bindable) =>
104 var colView = bindable as CollectionView;
107 throw new Exception("Bindable object is not CollectionView.");
110 colView.selectedItems = colView.selectedItems ?? new SelectionList(colView);
111 return colView.selectedItems;
115 /// Binding Property of selected items list in multiple selection.
117 /// <since_tizen> 9 </since_tizen>
118 public static readonly BindableProperty SelectionModeProperty =
119 BindableProperty.Create(nameof(SelectionMode), typeof(ItemSelectionMode), typeof(CollectionView), ItemSelectionMode.None,
120 propertyChanged: (bindable, oldValue, newValue) =>
122 var colView = bindable as CollectionView;
125 throw new Exception("Bindable object is not CollectionView.");
128 oldValue = colView.selectionMode;
129 colView.selectionMode = (ItemSelectionMode)newValue;
130 SelectionModePropertyChanged(colView, oldValue, newValue);
132 defaultValueCreator: (bindable) =>
134 var colView = bindable as CollectionView;
137 throw new Exception("Bindable object is not CollectionView.");
140 return colView.selectionMode;
144 private static readonly IList<object> selectEmpty = new List<object>(0);
145 private DataTemplate itemTemplate = null;
146 private IEnumerable itemsSource = null;
147 private ItemsLayouter itemsLayouter = null;
148 private DataTemplate groupHeaderTemplate;
149 private DataTemplate groupFooterTemplate;
150 private bool isGrouped;
151 private bool wasRelayouted = false;
152 private bool needInitalizeLayouter = false;
153 private object selectedItem;
154 private SelectionList selectedItems;
155 private bool suppressSelectionChangeNotification;
156 private ItemSelectionMode selectionMode = ItemSelectionMode.None;
157 private RecyclerViewItem header;
158 private RecyclerViewItem footer;
159 private List<RecyclerViewItem> recycleGroupHeaderCache { get; } = new List<RecyclerViewItem>();
160 private List<RecyclerViewItem> recycleGroupFooterCache { get; } = new List<RecyclerViewItem>();
161 private bool delayedScrollTo;
162 private (float position, bool anim) delayedScrollToParam;
164 private bool delayedIndexScrollTo;
165 private (int index, bool anim, ItemScrollTo scrollTo) delayedIndexScrollToParam;
167 private void Initialize()
170 SetKeyboardNavigationSupport(true);
174 /// Base constructor.
176 /// <since_tizen> 9 </since_tizen>
177 public CollectionView() : base()
183 /// Base constructor with ItemsSource
185 /// <param name="itemsSource">item's data source</param>
186 /// <since_tizen> 9 </since_tizen>
187 public CollectionView(IEnumerable itemsSource) : this()
189 ItemsSource = itemsSource;
193 /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
195 /// <param name="itemsSource">item's data source</param>
196 /// <param name="layouter">item's layout manager</param>
197 /// <param name="template">item's view template with data bindings</param>
198 [EditorBrowsable(EditorBrowsableState.Never)]
199 public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
201 ItemsSource = itemsSource;
202 ItemTemplate = template;
203 ItemsLayouter = layouter;
207 /// Creates a new instance of a CollectionView with style.
209 /// <param name="style">A style applied to the newly created CollectionView.</param>
210 [EditorBrowsable(EditorBrowsableState.Never)]
211 public CollectionView(ControlStyle style) : base(style)
217 /// Event of Selection changed.
218 /// previous selection list and current selection will be provided.
220 /// <since_tizen> 9 </since_tizen>
221 public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
224 /// Align item in the viewport when ScrollTo() calls.
226 /// <since_tizen> 9 </since_tizen>
227 public enum ItemScrollTo
230 /// Scroll to show item in nearest viewport on scroll direction.
231 /// item is above the scroll viewport, item will be came into front,
232 /// item is under the scroll viewport, item will be came into end,
233 /// item is in the scroll viewport, no scroll.
235 /// <since_tizen> 9 </since_tizen>
238 /// Scroll to show item in start of the viewport.
240 /// <since_tizen> 9 </since_tizen>
243 /// Scroll to show item in center of the viewport.
245 /// <since_tizen> 9 </since_tizen>
248 /// Scroll to show item in end of the viewport.
250 /// <since_tizen> 9 </since_tizen>
255 /// Item's source data in IEnumerable.
257 /// <since_tizen> 9 </since_tizen>
258 public override IEnumerable ItemsSource
260 get => GetValue(RecyclerView.ItemsSourceProperty) as IEnumerable;
261 set => SetValue(RecyclerView.ItemsSourceProperty, value);
264 internal override IEnumerable InternalItemsSource
272 if (itemsSource != null)
274 // Clearing old data!
275 if (itemsSource is INotifyCollectionChanged prevNotifyCollectionChanged)
277 prevNotifyCollectionChanged.CollectionChanged -= CollectionChanged;
279 if (selectedItem != null)
283 selectedItems?.Clear();
286 itemsSource = value as IEnumerable;
288 if (itemsSource == null)
290 InternalSource?.Dispose();
291 InternalSource = null;
292 itemsLayouter?.Clear();
296 if (itemsSource is INotifyCollectionChanged newNotifyCollectionChanged)
298 newNotifyCollectionChanged.CollectionChanged += CollectionChanged;
301 InternalSource?.Dispose();
302 InternalSource = ItemsSourceFactory.Create(this);
304 if (itemsLayouter == null) return;
306 needInitalizeLayouter = true;
307 ReinitializeLayout();
313 /// DataTemplate for items.
314 /// Create visual contents and binding properties.
315 /// return object type is restricted RecyclerViewItem.
316 /// <seealso cref="Tizen.NUI.Binding.DataTemplate" />
318 /// <since_tizen> 9 </since_tizen>
319 public override DataTemplate ItemTemplate
323 return GetValue(ItemTemplateProperty) as DataTemplate;
327 SetValue(ItemTemplateProperty, value);
328 NotifyPropertyChanged();
331 internal override DataTemplate InternalItemTemplate
339 itemTemplate = value;
345 needInitalizeLayouter = true;
346 ReinitializeLayout();
352 /// Layouting items on the scroll ContentContainer.
353 /// <seealso cref="ItemsLayouter" />
354 /// <seealso cref="LinearLayouter" />
355 /// <seealso cref="GridLayouter" />
357 /// <since_tizen> 9 </since_tizen>
358 public virtual ItemsLayouter ItemsLayouter
362 return GetValue(ItemsLayouterProperty) as ItemsLayouter;
366 SetValue(ItemsLayouterProperty, value);
367 NotifyPropertyChanged();
373 [EditorBrowsable(EditorBrowsableState.Never)]
374 protected override ItemsLayouter InternalItemsLayouter
378 return itemsLayouter;
382 itemsLayouter?.Clear();
385 itemsLayouter = value;
386 base.InternalItemsLayouter = itemsLayouter;
389 needInitalizeLayouter = false;
393 needInitalizeLayouter = true;
395 var styleName = "Tizen.NUI.Components." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
396 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
397 if (layouterStyle != null)
399 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
401 ReinitializeLayout();
406 /// Scrolling direction to display items layout.
408 /// <since_tizen> 9 </since_tizen>
409 public new Direction ScrollingDirection
413 return (Direction)GetValue(ScrollingDirectionProperty);
417 SetValue(ScrollingDirectionProperty, value);
418 NotifyPropertyChanged();
421 private Direction InternalScrollingDirection
425 return base.ScrollingDirection;
429 if (base.ScrollingDirection != value)
431 base.ScrollingDirection = value;
432 needInitalizeLayouter = true;
433 ReinitializeLayout();
439 /// Selected item in single selection.
441 /// <since_tizen> 9 </since_tizen>
442 public object SelectedItem
444 get => GetValue(SelectedItemProperty);
445 set => SetValue(SelectedItemProperty, value);
449 /// Selected items list in multiple selection.
451 /// <since_tizen> 9 </since_tizen>
452 public IList<object> SelectedItems
454 get => GetValue(SelectedItemsProperty) as IList<object>;
455 // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
459 /// Selection mode to handle items selection. See ItemSelectionMode for details.
461 /// <since_tizen> 9 </since_tizen>
462 public ItemSelectionMode SelectionMode
464 get => (ItemSelectionMode)GetValue(SelectionModeProperty);
465 set => SetValue(SelectionModeProperty, value);
469 /// Command of selection changed.
471 [EditorBrowsable(EditorBrowsableState.Never)]
472 public ICommand SelectionChangedCommand
476 return GetValue(SelectionChangedCommandProperty) as ICommand;
480 SetValue(SelectionChangedCommandProperty, value);
481 NotifyPropertyChanged();
484 private ICommand InternalSelectionChangedCommand { set; get; }
487 /// Command parameter of selection changed.
489 [EditorBrowsable(EditorBrowsableState.Never)]
490 public object SelectionChangedCommandParameter
494 return GetValue(SelectionChangedCommandParameterProperty);
498 SetValue(SelectionChangedCommandParameterProperty, value);
499 NotifyPropertyChanged();
502 private object InternalSelectionChangedCommandParameter { set; get; }
505 /// Header item placed in top-most position.
507 /// <remarks>Please note that, internal index will be increased by header.</remarks>
508 /// <since_tizen> 9 </since_tizen>
509 public RecyclerViewItem Header
513 return GetValue(HeaderProperty) as RecyclerViewItem;
517 SetValue(HeaderProperty, value);
518 NotifyPropertyChanged();
521 private RecyclerViewItem InternalHeader
528 //ContentContainer.Remove(header);
529 Utility.Dispose(header);
534 value.ParentItemsView = this;
535 value.IsHeader = true;
536 ContentContainer.Add(value);
539 if (InternalSource != null)
541 InternalSource.HasHeader = (value != null);
543 needInitalizeLayouter = true;
544 ReinitializeLayout();
549 /// Footer item placed in bottom-most position.
551 /// <remarks>Please note that, internal index will be increased by footer.</remarks>
552 /// <since_tizen> 9 </since_tizen>
553 public RecyclerViewItem Footer
557 return GetValue(FooterProperty) as RecyclerViewItem;
561 SetValue(FooterProperty, value);
562 NotifyPropertyChanged();
565 private RecyclerViewItem InternalFooter
572 //ContentContainer.Remove(footer);
573 Utility.Dispose(footer);
577 value.Index = InternalSource?.Count ?? 0;
578 value.ParentItemsView = this;
579 value.IsFooter = true;
580 ContentContainer.Add(value);
583 if (InternalSource != null)
585 InternalSource.HasFooter = (value != null);
587 needInitalizeLayouter = true;
588 ReinitializeLayout();
593 /// Enable groupable view.
595 [EditorBrowsable(EditorBrowsableState.Never)]
596 public bool IsGrouped
600 return (bool)GetValue(IsGroupedProperty);
604 SetValue(IsGroupedProperty, value);
605 NotifyPropertyChanged();
608 private bool InternalIsGrouped
614 needInitalizeLayouter = true;
615 //Need to re-intialize Internal Item Source.
616 if (InternalSource != null)
618 InternalSource.Dispose();
619 InternalSource = null;
621 if (ItemsSource != null)
623 InternalSource = ItemsSourceFactory.Create(this);
626 ReinitializeLayout();
631 /// DataTemplate of group header.
633 /// <remarks>Please note that, internal index will be increased by group header.
634 /// GroupHeaderTemplate is essential for groupable view.</remarks>
635 [EditorBrowsable(EditorBrowsableState.Never)]
636 public DataTemplate GroupHeaderTemplate
640 return GetValue(GroupHeaderTemplateProperty) as DataTemplate;
644 SetValue(GroupHeaderTemplateProperty, value);
645 NotifyPropertyChanged();
648 private DataTemplate InternalGroupHeaderTemplate
652 return groupHeaderTemplate;
656 groupHeaderTemplate = value;
657 needInitalizeLayouter = true;
659 //Need to re-intialize Internal Item Source.
660 if (InternalSource != null)
662 InternalSource.Dispose();
663 InternalSource = null;
666 if (ItemsSource != null)
668 InternalSource = ItemsSourceFactory.Create(this);
671 ReinitializeLayout();
676 /// DataTemplate of group footer. Group feature is not supported yet.
678 /// <remarks>Please note that, internal index will be increased by group footer.</remarks>
679 [EditorBrowsable(EditorBrowsableState.Never)]
680 public DataTemplate GroupFooterTemplate
684 return GetValue(GroupFooterTemplateProperty) as DataTemplate;
688 SetValue(GroupFooterTemplateProperty, value);
689 NotifyPropertyChanged();
692 private DataTemplate InternalGroupFooterTemplate
696 return groupFooterTemplate;
700 groupFooterTemplate = value;
701 needInitalizeLayouter = true;
703 //Need to re-intialize Internal Item Source.
704 if (InternalSource != null)
706 InternalSource.Dispose();
707 InternalSource = null;
710 if (ItemsSource != null)
712 InternalSource = ItemsSourceFactory.Create(this);
715 ReinitializeLayout();
720 /// Internal encapsulated items data source.
722 internal new IGroupableItemSource InternalSource
726 return (base.InternalSource as IGroupableItemSource);
730 base.InternalSource = value;
735 /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
737 [EditorBrowsable(EditorBrowsableState.Never)]
738 internal ItemSizingStrategy SizingStrategy { get; set; }
741 /// <since_tizen> 9 </since_tizen>
742 public override void OnRelayout(Vector2 size, RelayoutContainer container)
744 base.OnRelayout(size, container);
746 wasRelayouted = true;
747 if (needInitalizeLayouter)
749 ReinitializeLayout();
754 [EditorBrowsable(EditorBrowsableState.Never)]
755 public override void NotifyDataSetChanged()
757 if (selectedItem != null)
761 if (selectedItems != null)
763 selectedItems.Clear();
766 base.NotifyDataSetChanged();
770 /// Update selected items list in multiple selection.
772 /// <param name="newSelection">updated selection list by user</param>
773 /// <since_tizen> 9 </since_tizen>
774 public void UpdateSelectedItems(IList<object> newSelection)
776 if (SelectedItems != null)
778 var oldSelection = new List<object>(SelectedItems);
780 suppressSelectionChangeNotification = true;
782 SelectedItems.Clear();
784 if (newSelection?.Count > 0)
786 for (int n = 0; n < newSelection.Count; n++)
788 SelectedItems.Add(newSelection[n]);
792 suppressSelectionChangeNotification = false;
794 SelectedItemsPropertyChanged(oldSelection, newSelection);
799 /// Scroll to specific position with or without animation.
801 /// <param name="position">Destination.</param>
802 /// <param name="animate">Scroll with or without animation</param>
803 /// <since_tizen> 9 </since_tizen>
804 public new void ScrollTo(float position, bool animate)
806 if (ItemsLayouter == null)
808 throw new Exception("Item Layouter must exist.");
811 if ((InternalSource == null) || needInitalizeLayouter)
813 delayedScrollTo = true;
814 delayedScrollToParam = (position, animate);
818 base.ScrollTo(position, animate);
822 /// Scrolls to the item at the specified index.
824 /// <param name="index">Index of item.</param>
825 [EditorBrowsable(EditorBrowsableState.Never)]
826 public new void ScrollToIndex(int index)
828 ScrollTo(index, true, ItemScrollTo.Start);
832 /// Scroll to specific item's aligned position with or without animation.
834 /// <param name="index">Target item index of dataset.</param>
835 /// <param name="animate">Boolean flag of animation.</param>
836 /// <param name="align">Align state of item. See details in <see cref="ItemScrollTo"/>.</param>
837 /// <since_tizen> 9 </since_tizen>
838 public virtual void ScrollTo(int index, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
840 if (ItemsLayouter == null)
842 throw new Exception("Item Layouter must exist.");
845 if ((InternalSource == null) || needInitalizeLayouter)
847 delayedIndexScrollTo = true;
848 delayedIndexScrollToParam = (index, animate, align);
852 if (index < 0 || index >= InternalSource.Count)
854 throw new Exception("index is out of boundary. index should be a value between (0, " + InternalSource.Count.ToString() + ").");
857 float scrollPos, curPos, curSize, curItemSize;
858 (float x, float y) = ItemsLayouter.GetItemPosition(index);
859 (float width, float height) = ItemsLayouter.GetItemSize(index);
861 if (ScrollingDirection == Direction.Horizontal)
864 curPos = ScrollPosition.X;
865 curSize = Size.Width;
871 curPos = ScrollPosition.Y;
872 curSize = Size.Height;
873 curItemSize = height;
876 //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
879 case ItemScrollTo.Start:
882 case ItemScrollTo.Center:
883 scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
885 case ItemScrollTo.End:
886 scrollPos = scrollPos - curSize + curItemSize;
888 case ItemScrollTo.Nearest:
889 if (scrollPos < curPos - curItemSize)
891 // item is placed before the current screen. scrollTo.Top
893 else if (scrollPos >= curPos + curSize + curItemSize)
895 // item is placed after the current screen. scrollTo.End
896 scrollPos = scrollPos - curSize + curItemSize;
900 // item is in the scroller. ScrollTo() is ignored.
906 //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
907 base.ScrollTo(scrollPos, animate);
911 /// Apply style to CollectionView
913 /// <param name="viewStyle">The style to apply.</param>
914 [EditorBrowsable(EditorBrowsableState.Never)]
915 public override void ApplyStyle(ViewStyle viewStyle)
917 base.ApplyStyle(viewStyle);
918 if (viewStyle != null)
920 //Extension = RecyclerViewItemStyle.CreateExtension();
922 if (itemsLayouter != null)
924 string styleName = "Tizen.NUI.Compoenents." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
925 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
926 if (layouterStyle != null)
928 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
934 /// Initialize AT-SPI object.
936 [EditorBrowsable(EditorBrowsableState.Never)]
937 public override void OnInitialize()
940 AccessibilityRole = Role.List;
944 /// Scroll to specified item
947 /// Make sure that the item that is about to receive the accessibility highlight is visible.
949 [EditorBrowsable(EditorBrowsableState.Never)]
950 protected override bool AccessibilityScrollToChild(View child)
952 if (ScrollingDirection == Direction.Horizontal)
954 if (child.ScreenPosition.X + child.Size.Width <= this.ScreenPosition.X)
956 ScrollTo((float)(child.ScreenPosition.X - ContentContainer.ScreenPosition.X), false);
958 else if (child.ScreenPosition.X >= this.ScreenPosition.X + this.Size.Width)
960 ScrollTo((float)(child.ScreenPosition.X + child.Size.Width - ContentContainer.ScreenPosition.X - this.Size.Width), false);
965 if (child.ScreenPosition.Y + child.Size.Height <= this.ScreenPosition.Y)
967 ScrollTo((float)(child.ScreenPosition.Y - ContentContainer.ScreenPosition.Y), false);
969 else if (child.ScreenPosition.Y >= this.ScreenPosition.Y + this.Size.Height)
971 ScrollTo((float)(child.ScreenPosition.Y + child.Size.Height - ContentContainer.ScreenPosition.Y - this.Size.Height), false);
978 [EditorBrowsable(EditorBrowsableState.Never)]
979 protected internal override RecyclerViewItem RealizeItem(int index)
981 RecyclerViewItem item;
982 if (index == 0 && Header != null)
988 if (index == InternalSource.Count - 1 && Footer != null)
996 var context = InternalSource.GetItem(index);
997 if (InternalSource.IsGroupHeader(index))
999 item = RealizeGroupHeader(index, context);
1001 else if (InternalSource.IsGroupFooter(index))
1005 item = RealizeGroupFooter(index, context);
1009 item = base.RealizeItem(index);
1012 throw new Exception("Item realize failed by Null content return.");
1014 item.ParentGroup = InternalSource.GetGroupParent(index);
1019 item = base.RealizeItem(index);
1023 throw new Exception("Item realize failed by Null content return.");
1025 switch (SelectionMode)
1027 case ItemSelectionMode.Single:
1028 case ItemSelectionMode.SingleAlways:
1029 if (item.BindingContext != null && item.BindingContext == SelectedItem)
1031 item.IsSelected = true;
1035 case ItemSelectionMode.Multiple:
1036 if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
1038 item.IsSelected = true;
1041 case ItemSelectionMode.None:
1042 item.IsSelectable = false;
1049 [EditorBrowsable(EditorBrowsableState.Never)]
1050 protected internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
1068 if (item.IsGroupHeader || item.IsGroupFooter)
1071 item.ParentItemsView = null;
1072 item.BindingContext = null;
1073 item.IsPressed = false;
1074 item.IsSelected = false;
1075 item.IsEnabled = true;
1077 //item.Relayout -= OnItemRelayout;
1078 if (!recycle || !PushRecycleGroupCache(item))
1080 Utility.Dispose(item);
1085 base.UnrealizeItem(item, recycle);
1088 internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
1090 if (suppressSelectionChangeNotification)
1095 foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
1097 if (item.BindingContext == null) continue;
1098 if (newSelection.Contains(item.BindingContext))
1100 if (!item.IsSelected)
1102 item.IsSelected = true;
1107 if (item.IsSelected)
1109 item.IsSelected = false;
1113 SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
1115 OnPropertyChanged(SelectedItemsProperty.PropertyName);
1119 /// Internal selection callback.
1121 /// <since_tizen> 9 </since_tizen>
1122 protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
1124 //Selection Callback
1128 /// Adjust scrolling position by own scrolling rules.
1129 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1131 /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
1132 /// <returns>Adjusted scroll destination</returns>
1133 [EditorBrowsable(EditorBrowsableState.Never)]
1134 protected override float AdjustTargetPositionOfScrollAnimation(float position)
1136 // Destination is depending on implementation of layout manager.
1137 // Get destination from layout manager.
1138 return ItemsLayouter?.CalculateCandidateScrollPosition(position) ?? position;
1142 [EditorBrowsable(EditorBrowsableState.Never)]
1143 protected override void ClearCache()
1145 foreach (RecyclerViewItem item in recycleGroupHeaderCache)
1147 Utility.Dispose(item);
1149 recycleGroupHeaderCache.Clear();
1150 foreach (RecyclerViewItem item in recycleGroupFooterCache)
1152 Utility.Dispose(item);
1154 recycleGroupFooterCache.Clear();
1160 /// OnScroll event callback. Requesting layout to the layouter with given scrollPosition.
1162 /// <param name="source">Scroll source object</param>
1163 /// <param name="args">Scroll event argument</param>
1164 /// <since_tizen> 9 </since_tizen>
1165 protected override void OnScrolling(object source, ScrollEventArgs args)
1167 if (disposed) return;
1169 if (needInitalizeLayouter && (ItemsLayouter != null))
1171 ItemsLayouter.Initialize(this);
1172 needInitalizeLayouter = false;
1175 base.OnScrolling(source, args);
1179 /// Dispose ItemsView and all children on it.
1181 /// <param name="type">Dispose type.</param>
1182 /// <since_tizen> 9 </since_tizen>
1183 protected override void Dispose(DisposeTypes type)
1190 if (type == DisposeTypes.Explicit)
1192 // From now on, no need to use this properties,
1193 // so remove reference, to push it into garbage collector.
1195 // Arugable to disposing user-created members.
1199 Utility.Dispose(Header);
1204 Utility.Dispose(Footer);
1209 groupHeaderTemplate = null;
1210 groupFooterTemplate = null;
1212 if (selectedItem != null)
1214 selectedItem = null;
1216 if (selectedItems != null)
1218 selectedItems.Clear();
1219 selectedItems = null;
1221 if (InternalSource != null)
1223 InternalSource.Dispose();
1224 InternalSource = null;
1231 private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
1233 var command = colView.SelectionChangedCommand;
1235 if (command != null)
1237 var commandParameter = colView.SelectionChangedCommandParameter;
1238 if (commandParameter == null)
1240 commandParameter = args;
1243 if (command.CanExecute(commandParameter))
1245 command.Execute(commandParameter);
1248 colView.SelectionChanged?.Invoke(colView, args);
1249 colView.OnSelectionChanged(args);
1252 private static object CoerceSelectedItems(CollectionView colView, object value)
1256 return new SelectionList(colView);
1259 if (value is SelectionList)
1264 return new SelectionList(colView, value as IList<object>);
1267 private static void SelectionModePropertyChanged(CollectionView colView, object oldValue, object newValue)
1269 var oldMode = (ItemSelectionMode)oldValue;
1270 var newMode = (ItemSelectionMode)newValue;
1272 IList<object> previousSelection = new List<object>();
1273 IList<object> newSelection = new List<object>();
1277 case ItemSelectionMode.None:
1279 case ItemSelectionMode.Single:
1280 if (colView.SelectedItem != null)
1282 previousSelection.Add(colView.SelectedItem);
1285 case ItemSelectionMode.Multiple:
1286 previousSelection = colView.SelectedItems;
1292 case ItemSelectionMode.None:
1294 case ItemSelectionMode.Single:
1295 if (colView.SelectedItem != null)
1297 newSelection.Add(colView.SelectedItem);
1300 case ItemSelectionMode.Multiple:
1301 newSelection = colView.SelectedItems;
1305 if (previousSelection.Count == newSelection.Count)
1307 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
1309 // Both selections are empty or have the same single item; no reason to signal a change
1314 var args = new SelectionChangedEventArgs(previousSelection, newSelection);
1315 SelectionPropertyChanged(colView, args);
1318 private void ReinitializeLayout()
1320 if (ItemsSource == null || ItemsLayouter == null || ItemTemplate == null)
1335 if (needInitalizeLayouter)
1337 if (InternalSource == null)
1342 InternalSource.HasHeader = (header != null);
1343 InternalSource.HasFooter = (footer != null);
1345 itemsLayouter.Clear();
1348 ItemsLayouter.Initialize(this);
1349 needInitalizeLayouter = false;
1352 ItemsLayouter.RequestLayout(0.0f, true);
1354 if (delayedScrollTo)
1356 delayedScrollTo = false;
1357 ScrollTo(delayedScrollToParam.position, delayedScrollToParam.anim);
1360 if (delayedIndexScrollTo)
1362 delayedIndexScrollTo = false;
1363 ScrollTo(delayedIndexScrollToParam.index, delayedIndexScrollToParam.anim, delayedIndexScrollToParam.scrollTo);
1366 if (ScrollingDirection == Direction.Horizontal)
1368 ContentContainer.SizeWidth = (float)ItemsLayouter?.CalculateLayoutOrientationSize();
1372 ContentContainer.SizeHeight = (float)ItemsLayouter?.CalculateLayoutOrientationSize();
1376 private bool PushRecycleGroupCache(RecyclerViewItem item)
1380 throw new ArgumentNullException(nameof(item));
1383 if (item.Template == null || RecycleCache.Count >= 20)
1388 if (item.IsGroupHeader)
1390 recycleGroupHeaderCache.Add(item);
1392 else if (item.IsGroupFooter)
1394 recycleGroupFooterCache.Add(item);
1407 private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1409 RecyclerViewItem viewItem = null;
1411 var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1412 for (int i = 0; i < Cache.Count; i++)
1414 viewItem = Cache[i];
1415 if (Template == viewItem.Template)
1421 if (viewItem != null)
1423 Cache.Remove(viewItem);
1430 private RecyclerViewItem RealizeGroupHeader(int index, object context)
1432 DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
1434 RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
1436 if (groupHeader == null)
1438 groupHeader = DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this) as RecyclerViewItem;
1439 if (groupHeader == null)
1444 groupHeader.Template = templ;
1445 groupHeader.IsGroupHeader = true;
1446 groupHeader.IsGroupFooter = false;
1447 ContentContainer.Add(groupHeader);
1450 if (groupHeader != null)
1452 groupHeader.ParentItemsView = this;
1453 groupHeader.Index = index;
1454 groupHeader.ParentGroup = context;
1455 groupHeader.BindingContext = context;
1463 private RecyclerViewItem RealizeGroupFooter(int index, object context)
1465 DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
1467 RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
1469 if (groupFooter == null)
1471 groupFooter = DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this) as RecyclerViewItem;
1472 if (groupFooter == null)
1477 groupFooter.Template = templ;
1478 groupFooter.IsGroupHeader = false;
1479 groupFooter.IsGroupFooter = true;
1480 ContentContainer.Add(groupFooter);
1483 if (groupFooter != null)
1485 groupFooter.ParentItemsView = this;
1486 groupFooter.Index = index;
1487 groupFooter.ParentGroup = context;
1488 groupFooter.BindingContext = context;
1495 private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
1497 switch (args.Action)
1499 case NotifyCollectionChangedAction.Add:
1501 case NotifyCollectionChangedAction.Remove:
1502 // Clear removed items.
1503 if (args.OldItems != null)
1505 if (args.OldItems.Contains(selectedItem))
1507 selectedItem = null;
1510 if (selectedItems != null)
1512 foreach (object removed in args.OldItems)
1514 if (selectedItems.Contains(removed))
1516 selectedItems.Remove(removed);
1522 case NotifyCollectionChangedAction.Replace:
1524 case NotifyCollectionChangedAction.Move:
1526 case NotifyCollectionChangedAction.Reset:
1529 throw new ArgumentOutOfRangeException(nameof(args));