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;
50 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;
168 /// Base constructor.
170 /// <since_tizen> 9 </since_tizen>
171 public CollectionView() : base()
174 SetKeyboardNavigationSupport(true);
178 /// Base constructor with ItemsSource
180 /// <param name="itemsSource">item's data source</param>
181 /// <since_tizen> 9 </since_tizen>
182 public CollectionView(IEnumerable itemsSource) : this()
184 ItemsSource = itemsSource;
188 /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
190 /// <param name="itemsSource">item's data source</param>
191 /// <param name="layouter">item's layout manager</param>
192 /// <param name="template">item's view template with data bindings</param>
193 [EditorBrowsable(EditorBrowsableState.Never)]
194 public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
196 ItemsSource = itemsSource;
197 ItemTemplate = template;
198 ItemsLayouter = layouter;
202 /// Event of Selection changed.
203 /// previous selection list and current selection will be provided.
205 /// <since_tizen> 9 </since_tizen>
206 public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
209 /// Align item in the viewport when ScrollTo() calls.
211 /// <since_tizen> 9 </since_tizen>
212 public enum ItemScrollTo
215 /// Scroll to show item in nearest viewport on scroll direction.
216 /// item is above the scroll viewport, item will be came into front,
217 /// item is under the scroll viewport, item will be came into end,
218 /// item is in the scroll viewport, no scroll.
220 /// <since_tizen> 9 </since_tizen>
223 /// Scroll to show item in start of the viewport.
225 /// <since_tizen> 9 </since_tizen>
228 /// Scroll to show item in center of the viewport.
230 /// <since_tizen> 9 </since_tizen>
233 /// Scroll to show item in end of the viewport.
235 /// <since_tizen> 9 </since_tizen>
240 /// Item's source data in IEnumerable.
242 /// <since_tizen> 9 </since_tizen>
243 public override IEnumerable ItemsSource
245 get => GetValue(RecyclerView.ItemsSourceProperty) as IEnumerable;
246 set => SetValue(RecyclerView.ItemsSourceProperty, value);
249 internal override IEnumerable InternalItemsSource
257 if (itemsSource != null)
259 // Clearing old data!
260 if (itemsSource is INotifyCollectionChanged prevNotifyCollectionChanged)
262 prevNotifyCollectionChanged.CollectionChanged -= CollectionChanged;
264 if (selectedItem != null)
268 selectedItems?.Clear();
271 itemsSource = value as IEnumerable;
273 if (itemsSource == null)
275 InternalItemSource?.Dispose();
276 InternalItemSource = null;
277 itemsLayouter?.Clear();
281 if (itemsSource is INotifyCollectionChanged newNotifyCollectionChanged)
283 newNotifyCollectionChanged.CollectionChanged += CollectionChanged;
286 InternalItemSource?.Dispose();
287 InternalItemSource = ItemsSourceFactory.Create(this);
289 if (itemsLayouter == null) return;
291 needInitalizeLayouter = true;
292 ReinitializeLayout();
298 /// DataTemplate for items.
299 /// Create visual contents and binding properties.
300 /// return object type is restricted RecyclerViewItem.
301 /// <seealso cref="Tizen.NUI.Binding.DataTemplate" />
303 /// <since_tizen> 9 </since_tizen>
304 public override DataTemplate ItemTemplate
308 return GetValue(ItemTemplateProperty) as DataTemplate;
312 SetValue(ItemTemplateProperty, value);
313 NotifyPropertyChanged();
316 internal override DataTemplate InternalItemTemplate
324 itemTemplate = value;
330 needInitalizeLayouter = true;
331 ReinitializeLayout();
337 /// Layouting items on the scroll ContentContainer.
338 /// <seealso cref="ItemsLayouter" />
339 /// <seealso cref="LinearLayouter" />
340 /// <seealso cref="GridLayouter" />
342 /// <since_tizen> 9 </since_tizen>
343 public virtual ItemsLayouter ItemsLayouter
347 return GetValue(ItemsLayouterProperty) as ItemsLayouter;
351 SetValue(ItemsLayouterProperty, value);
352 NotifyPropertyChanged();
358 [EditorBrowsable(EditorBrowsableState.Never)]
359 protected override ItemsLayouter InternalItemsLayouter
363 return itemsLayouter;
367 itemsLayouter?.Clear();
370 itemsLayouter = value;
371 base.InternalItemsLayouter = itemsLayouter;
374 needInitalizeLayouter = false;
378 needInitalizeLayouter = true;
380 var styleName = "Tizen.NUI.Components." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
381 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
382 if (layouterStyle != null)
384 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
386 ReinitializeLayout();
391 /// Scrolling direction to display items layout.
393 /// <since_tizen> 9 </since_tizen>
394 public new Direction ScrollingDirection
398 return (Direction)GetValue(ScrollingDirectionProperty);
402 SetValue(ScrollingDirectionProperty, value);
403 NotifyPropertyChanged();
406 private Direction InternalScrollingDirection
410 return base.ScrollingDirection;
414 if (base.ScrollingDirection != value)
416 base.ScrollingDirection = value;
417 needInitalizeLayouter = true;
418 ReinitializeLayout();
424 /// Selected item in single selection.
426 /// <since_tizen> 9 </since_tizen>
427 public object SelectedItem
429 get => GetValue(SelectedItemProperty);
430 set => SetValue(SelectedItemProperty, value);
434 /// Selected items list in multiple selection.
436 /// <since_tizen> 9 </since_tizen>
437 public IList<object> SelectedItems
439 get => GetValue(SelectedItemsProperty) as IList<object>;
440 // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
444 /// Selection mode to handle items selection. See ItemSelectionMode for details.
446 /// <since_tizen> 9 </since_tizen>
447 public ItemSelectionMode SelectionMode
449 get => (ItemSelectionMode)GetValue(SelectionModeProperty);
450 set => SetValue(SelectionModeProperty, value);
454 /// Command of selection changed.
456 [EditorBrowsable(EditorBrowsableState.Never)]
457 public ICommand SelectionChangedCommand
461 return GetValue(SelectionChangedCommandProperty) as ICommand;
465 SetValue(SelectionChangedCommandProperty, value);
466 NotifyPropertyChanged();
469 private ICommand InternalSelectionChangedCommand { set; get; }
472 /// Command parameter of selection changed.
474 [EditorBrowsable(EditorBrowsableState.Never)]
475 public object SelectionChangedCommandParameter
479 return GetValue(SelectionChangedCommandParameterProperty);
483 SetValue(SelectionChangedCommandParameterProperty, value);
484 NotifyPropertyChanged();
487 private object InternalSelectionChangedCommandParameter { set; get; }
490 /// Header item placed in top-most position.
492 /// <remarks>Please note that, internal index will be increased by header.</remarks>
493 /// <since_tizen> 9 </since_tizen>
494 public RecyclerViewItem Header
498 return GetValue(HeaderProperty) as RecyclerViewItem;
502 SetValue(HeaderProperty, value);
503 NotifyPropertyChanged();
506 private RecyclerViewItem InternalHeader
513 //ContentContainer.Remove(header);
514 Utility.Dispose(header);
519 value.ParentItemsView = this;
520 value.IsHeader = true;
521 ContentContainer.Add(value);
524 if (InternalItemSource != null)
526 InternalItemSource.HasHeader = (value != null);
528 needInitalizeLayouter = true;
529 ReinitializeLayout();
534 /// Footer item placed in bottom-most position.
536 /// <remarks>Please note that, internal index will be increased by footer.</remarks>
537 /// <since_tizen> 9 </since_tizen>
538 public RecyclerViewItem Footer
542 return GetValue(FooterProperty) as RecyclerViewItem;
546 SetValue(FooterProperty, value);
547 NotifyPropertyChanged();
550 private RecyclerViewItem InternalFooter
557 //ContentContainer.Remove(footer);
558 Utility.Dispose(footer);
562 value.Index = InternalItemSource?.Count ?? 0;
563 value.ParentItemsView = this;
564 value.IsFooter = true;
565 ContentContainer.Add(value);
568 if (InternalItemSource != null)
570 InternalItemSource.HasFooter = (value != null);
572 needInitalizeLayouter = true;
573 ReinitializeLayout();
578 /// Enable groupable view.
580 [EditorBrowsable(EditorBrowsableState.Never)]
581 public bool IsGrouped
585 return (bool)GetValue(IsGroupedProperty);
589 SetValue(IsGroupedProperty, value);
590 NotifyPropertyChanged();
593 private bool InternalIsGrouped
599 needInitalizeLayouter = true;
600 //Need to re-intialize Internal Item Source.
601 if (InternalItemSource != null)
603 InternalItemSource.Dispose();
604 InternalItemSource = null;
606 if (ItemsSource != null)
608 InternalItemSource = ItemsSourceFactory.Create(this);
611 ReinitializeLayout();
616 /// DataTemplate of group header.
618 /// <remarks>Please note that, internal index will be increased by group header.
619 /// GroupHeaderTemplate is essential for groupable view.</remarks>
620 [EditorBrowsable(EditorBrowsableState.Never)]
621 public DataTemplate GroupHeaderTemplate
625 return GetValue(GroupHeaderTemplateProperty) as DataTemplate;
629 SetValue(GroupHeaderTemplateProperty, value);
630 NotifyPropertyChanged();
633 private DataTemplate InternalGroupHeaderTemplate
637 return groupHeaderTemplate;
641 groupHeaderTemplate = value;
642 needInitalizeLayouter = true;
644 //Need to re-intialize Internal Item Source.
645 if (InternalItemSource != null)
647 InternalItemSource.Dispose();
648 InternalItemSource = null;
651 if (ItemsSource != null)
653 InternalItemSource = ItemsSourceFactory.Create(this);
656 ReinitializeLayout();
661 /// DataTemplate of group footer. Group feature is not supported yet.
663 /// <remarks>Please note that, internal index will be increased by group footer.</remarks>
664 [EditorBrowsable(EditorBrowsableState.Never)]
665 public DataTemplate GroupFooterTemplate
669 return GetValue(GroupFooterTemplateProperty) as DataTemplate;
673 SetValue(GroupFooterTemplateProperty, value);
674 NotifyPropertyChanged();
677 private DataTemplate InternalGroupFooterTemplate
681 return groupFooterTemplate;
685 groupFooterTemplate = value;
686 needInitalizeLayouter = true;
688 //Need to re-intialize Internal Item Source.
689 if (InternalItemSource != null)
691 InternalItemSource.Dispose();
692 InternalItemSource = null;
695 if (ItemsSource != null)
697 InternalItemSource = ItemsSourceFactory.Create(this);
700 ReinitializeLayout();
705 /// Internal encapsulated items data source.
707 internal new IGroupableItemSource InternalItemSource
711 return (base.InternalItemSource as IGroupableItemSource);
715 base.InternalItemSource = value;
720 /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
722 [EditorBrowsable(EditorBrowsableState.Never)]
723 internal ItemSizingStrategy SizingStrategy { get; set; }
726 /// <since_tizen> 9 </since_tizen>
727 public override void OnRelayout(Vector2 size, RelayoutContainer container)
729 base.OnRelayout(size, container);
731 wasRelayouted = true;
732 if (needInitalizeLayouter)
734 ReinitializeLayout();
739 [EditorBrowsable(EditorBrowsableState.Never)]
740 public override void NotifyDataSetChanged()
742 if (selectedItem != null)
746 if (selectedItems != null)
748 selectedItems.Clear();
751 base.NotifyDataSetChanged();
755 /// Update selected items list in multiple selection.
757 /// <param name="newSelection">updated selection list by user</param>
758 /// <since_tizen> 9 </since_tizen>
759 public void UpdateSelectedItems(IList<object> newSelection)
761 if (SelectedItems != null)
763 var oldSelection = new List<object>(SelectedItems);
765 suppressSelectionChangeNotification = true;
767 SelectedItems.Clear();
769 if (newSelection?.Count > 0)
771 for (int n = 0; n < newSelection.Count; n++)
773 SelectedItems.Add(newSelection[n]);
777 suppressSelectionChangeNotification = false;
779 SelectedItemsPropertyChanged(oldSelection, newSelection);
784 /// Scroll to specific position with or without animation.
786 /// <param name="position">Destination.</param>
787 /// <param name="animate">Scroll with or without animation</param>
788 /// <since_tizen> 9 </since_tizen>
789 public new void ScrollTo(float position, bool animate)
791 if (ItemsLayouter == null)
793 throw new Exception("Item Layouter must exist.");
796 if ((InternalItemSource == null) || needInitalizeLayouter)
798 delayedScrollTo = true;
799 delayedScrollToParam = (position, animate);
803 base.ScrollTo(position, animate);
807 /// Scrolls to the item at the specified index.
809 /// <param name="index">Index of item.</param>
810 [EditorBrowsable(EditorBrowsableState.Never)]
811 public new void ScrollToIndex(int index)
813 ScrollTo(index, true, ItemScrollTo.Start);
817 /// Scroll to specific item's aligned position with or without animation.
819 /// <param name="index">Target item index of dataset.</param>
820 /// <param name="animate">Boolean flag of animation.</param>
821 /// <param name="align">Align state of item. See details in <see cref="ItemScrollTo"/>.</param>
822 /// <since_tizen> 9 </since_tizen>
823 public virtual void ScrollTo(int index, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
825 if (ItemsLayouter == null)
827 throw new Exception("Item Layouter must exist.");
830 if ((InternalItemSource == null) || needInitalizeLayouter)
832 delayedIndexScrollTo = true;
833 delayedIndexScrollToParam = (index, animate, align);
837 if (index < 0 || index >= InternalItemSource.Count)
839 throw new Exception("index is out of boundary. index should be a value between (0, " + InternalItemSource.Count.ToString() + ").");
842 float scrollPos, curPos, curSize, curItemSize;
843 (float x, float y) = ItemsLayouter.GetItemPosition(index);
844 (float width, float height) = ItemsLayouter.GetItemSize(index);
846 if (ScrollingDirection == Direction.Horizontal)
849 curPos = ScrollPosition.X;
850 curSize = Size.Width;
856 curPos = ScrollPosition.Y;
857 curSize = Size.Height;
858 curItemSize = height;
861 //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalItemSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
864 case ItemScrollTo.Start:
867 case ItemScrollTo.Center:
868 scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
870 case ItemScrollTo.End:
871 scrollPos = scrollPos - curSize + curItemSize;
873 case ItemScrollTo.Nearest:
874 if (scrollPos < curPos - curItemSize)
876 // item is placed before the current screen. scrollTo.Top
878 else if (scrollPos >= curPos + curSize + curItemSize)
880 // item is placed after the current screen. scrollTo.End
881 scrollPos = scrollPos - curSize + curItemSize;
885 // item is in the scroller. ScrollTo() is ignored.
891 //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
892 base.ScrollTo(scrollPos, animate);
896 /// Apply style to CollectionView
898 /// <param name="viewStyle">The style to apply.</param>
899 [EditorBrowsable(EditorBrowsableState.Never)]
900 public override void ApplyStyle(ViewStyle viewStyle)
902 base.ApplyStyle(viewStyle);
903 if (viewStyle != null)
905 //Extension = RecyclerViewItemStyle.CreateExtension();
907 if (itemsLayouter != null)
909 string styleName = "Tizen.NUI.Compoenents." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
910 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
911 if (layouterStyle != null)
913 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
919 /// Initialize AT-SPI object.
921 [EditorBrowsable(EditorBrowsableState.Never)]
922 public override void OnInitialize()
925 AccessibilityRole = Role.List;
929 /// Scroll to specified item
932 /// Make sure that the item that is about to receive the accessibility highlight is visible.
934 [EditorBrowsable(EditorBrowsableState.Never)]
935 protected override bool AccessibilityScrollToChild(View child)
937 if (ScrollingDirection == Direction.Horizontal)
939 if (child.ScreenPosition.X + child.Size.Width <= this.ScreenPosition.X)
941 ScrollTo((float)(child.ScreenPosition.X - ContentContainer.ScreenPosition.X), false);
943 else if (child.ScreenPosition.X >= this.ScreenPosition.X + this.Size.Width)
945 ScrollTo((float)(child.ScreenPosition.X + child.Size.Width - ContentContainer.ScreenPosition.X - this.Size.Width), false);
950 if (child.ScreenPosition.Y + child.Size.Height <= this.ScreenPosition.Y)
952 ScrollTo((float)(child.ScreenPosition.Y - ContentContainer.ScreenPosition.Y), false);
954 else if (child.ScreenPosition.Y >= this.ScreenPosition.Y + this.Size.Height)
956 ScrollTo((float)(child.ScreenPosition.Y + child.Size.Height - ContentContainer.ScreenPosition.Y - this.Size.Height), false);
962 // Realize and Decorate the item.
964 internal override RecyclerViewItem RealizeItem(int index)
966 RecyclerViewItem item;
967 if (index == 0 && Header != null)
973 if (index == InternalItemSource.Count - 1 && Footer != null)
981 var context = InternalItemSource.GetItem(index);
982 if (InternalItemSource.IsGroupHeader(index))
984 item = RealizeGroupHeader(index, context);
986 else if (InternalItemSource.IsGroupFooter(index))
990 item = RealizeGroupFooter(index, context);
994 item = base.RealizeItem(index);
997 throw new Exception("Item realize failed by Null content return.");
999 item.ParentGroup = InternalItemSource.GetGroupParent(index);
1004 item = base.RealizeItem(index);
1008 throw new Exception("Item realize failed by Null content return.");
1010 switch (SelectionMode)
1012 case ItemSelectionMode.Single:
1013 case ItemSelectionMode.SingleAlways:
1014 if (item.BindingContext != null && item.BindingContext == SelectedItem)
1016 item.IsSelected = true;
1020 case ItemSelectionMode.Multiple:
1021 if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
1023 item.IsSelected = true;
1026 case ItemSelectionMode.None:
1027 item.IsSelectable = false;
1033 // Unrealize and caching the item.
1034 internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
1052 if (item.isGroupHeader || item.isGroupFooter)
1055 item.ParentItemsView = null;
1056 item.BindingContext = null;
1057 item.IsPressed = false;
1058 item.IsSelected = false;
1059 item.IsEnabled = true;
1061 //item.Relayout -= OnItemRelayout;
1062 if (!recycle || !PushRecycleGroupCache(item))
1064 Utility.Dispose(item);
1069 base.UnrealizeItem(item, recycle);
1072 internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
1074 if (suppressSelectionChangeNotification)
1079 foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
1081 if (item.BindingContext == null) continue;
1082 if (newSelection.Contains(item.BindingContext))
1084 if (!item.IsSelected)
1086 item.IsSelected = true;
1091 if (item.IsSelected)
1093 item.IsSelected = false;
1097 SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
1099 OnPropertyChanged(SelectedItemsProperty.PropertyName);
1103 /// Internal selection callback.
1105 /// <since_tizen> 9 </since_tizen>
1106 protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
1108 //Selection Callback
1112 /// Adjust scrolling position by own scrolling rules.
1113 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1115 /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
1116 /// <returns>Adjusted scroll destination</returns>
1117 [EditorBrowsable(EditorBrowsableState.Never)]
1118 protected override float AdjustTargetPositionOfScrollAnimation(float position)
1120 // Destination is depending on implementation of layout manager.
1121 // Get destination from layout manager.
1122 return ItemsLayouter?.CalculateCandidateScrollPosition(position) ?? position;
1126 [EditorBrowsable(EditorBrowsableState.Never)]
1127 protected override void ClearCache()
1129 foreach (RecyclerViewItem item in recycleGroupHeaderCache)
1131 Utility.Dispose(item);
1133 recycleGroupHeaderCache.Clear();
1134 foreach (RecyclerViewItem item in recycleGroupFooterCache)
1136 Utility.Dispose(item);
1138 recycleGroupFooterCache.Clear();
1144 /// OnScroll event callback. Requesting layout to the layouter with given scrollPosition.
1146 /// <param name="source">Scroll source object</param>
1147 /// <param name="args">Scroll event argument</param>
1148 /// <since_tizen> 9 </since_tizen>
1149 protected override void OnScrolling(object source, ScrollEventArgs args)
1151 if (disposed) return;
1153 if (needInitalizeLayouter && (ItemsLayouter != null))
1155 ItemsLayouter.Initialize(this);
1156 needInitalizeLayouter = false;
1159 base.OnScrolling(source, args);
1163 /// Dispose ItemsView and all children on it.
1165 /// <param name="type">Dispose type.</param>
1166 /// <since_tizen> 9 </since_tizen>
1167 protected override void Dispose(DisposeTypes type)
1174 if (type == DisposeTypes.Explicit)
1176 // From now on, no need to use this properties,
1177 // so remove reference, to push it into garbage collector.
1179 // Arugable to disposing user-created members.
1183 Utility.Dispose(Header);
1188 Utility.Dispose(Footer);
1193 groupHeaderTemplate = null;
1194 groupFooterTemplate = null;
1196 if (selectedItem != null)
1198 selectedItem = null;
1200 if (selectedItems != null)
1202 selectedItems.Clear();
1203 selectedItems = null;
1205 if (InternalItemSource != null)
1207 InternalItemSource.Dispose();
1208 InternalItemSource = null;
1215 private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
1217 var command = colView.SelectionChangedCommand;
1219 if (command != null)
1221 var commandParameter = colView.SelectionChangedCommandParameter;
1223 if (command.CanExecute(commandParameter))
1225 command.Execute(commandParameter);
1228 colView.SelectionChanged?.Invoke(colView, args);
1229 colView.OnSelectionChanged(args);
1232 private static object CoerceSelectedItems(BindableObject bindable, object value)
1234 var colView = bindable as CollectionView;
1237 return new SelectionList(colView);
1240 if (value is SelectionList)
1245 return new SelectionList(colView, value as IList<object>);
1248 private static void SelectionModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
1250 var colView = bindable as CollectionView;
1252 var oldMode = (ItemSelectionMode)oldValue;
1253 var newMode = (ItemSelectionMode)newValue;
1255 IList<object> previousSelection = new List<object>();
1256 IList<object> newSelection = new List<object>();
1260 case ItemSelectionMode.None:
1262 case ItemSelectionMode.Single:
1263 if (colView.SelectedItem != null)
1265 previousSelection.Add(colView.SelectedItem);
1268 case ItemSelectionMode.Multiple:
1269 previousSelection = colView.SelectedItems;
1275 case ItemSelectionMode.None:
1277 case ItemSelectionMode.Single:
1278 if (colView.SelectedItem != null)
1280 newSelection.Add(colView.SelectedItem);
1283 case ItemSelectionMode.Multiple:
1284 newSelection = colView.SelectedItems;
1288 if (previousSelection.Count == newSelection.Count)
1290 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
1292 // Both selections are empty or have the same single item; no reason to signal a change
1297 var args = new SelectionChangedEventArgs(previousSelection, newSelection);
1298 SelectionPropertyChanged(colView, args);
1301 private void ReinitializeLayout()
1303 if (ItemsSource == null || ItemsLayouter == null || ItemTemplate == null)
1318 if (needInitalizeLayouter)
1320 if (InternalItemSource == null)
1325 InternalItemSource.HasHeader = (header != null);
1326 InternalItemSource.HasFooter = (footer != null);
1328 itemsLayouter.Clear();
1331 ItemsLayouter.Initialize(this);
1332 needInitalizeLayouter = false;
1335 ItemsLayouter.RequestLayout(0.0f, true);
1337 if (delayedScrollTo)
1339 delayedScrollTo = false;
1340 ScrollTo(delayedScrollToParam.position, delayedScrollToParam.anim);
1343 if (delayedIndexScrollTo)
1345 delayedIndexScrollTo = false;
1346 ScrollTo(delayedIndexScrollToParam.index, delayedIndexScrollToParam.anim, delayedIndexScrollToParam.scrollTo);
1349 if (ScrollingDirection == Direction.Horizontal)
1351 ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
1355 ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
1359 private bool PushRecycleGroupCache(RecyclerViewItem item)
1363 throw new ArgumentNullException(nameof(item));
1366 if (item.Template == null || RecycleCache.Count >= 20)
1371 if (item.isGroupHeader)
1373 recycleGroupHeaderCache.Add(item);
1375 else if (item.isGroupFooter)
1377 recycleGroupFooterCache.Add(item);
1390 private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1392 RecyclerViewItem viewItem = null;
1394 var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1395 for (int i = 0; i < Cache.Count; i++)
1397 viewItem = Cache[i];
1398 if (Template == viewItem.Template)
1404 if (viewItem != null)
1406 Cache.Remove(viewItem);
1413 private RecyclerViewItem RealizeGroupHeader(int index, object context)
1415 DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
1417 RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
1419 if (groupHeader == null)
1421 groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
1423 groupHeader.Template = templ;
1424 groupHeader.isGroupHeader = true;
1425 groupHeader.isGroupFooter = false;
1426 ContentContainer.Add(groupHeader);
1429 if (groupHeader != null)
1431 groupHeader.ParentItemsView = this;
1432 groupHeader.Index = index;
1433 groupHeader.ParentGroup = context;
1434 groupHeader.BindingContext = context;
1442 private RecyclerViewItem RealizeGroupFooter(int index, object context)
1444 DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
1446 RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
1448 if (groupFooter == null)
1450 groupFooter = DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this) as RecyclerViewItem;
1452 groupFooter.Template = templ;
1453 groupFooter.isGroupHeader = false;
1454 groupFooter.isGroupFooter = true;
1455 ContentContainer.Add(groupFooter);
1458 if (groupFooter != null)
1460 groupFooter.ParentItemsView = this;
1461 groupFooter.Index = index;
1462 groupFooter.ParentGroup = context;
1463 groupFooter.BindingContext = context;
1470 private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
1472 switch (args.Action)
1474 case NotifyCollectionChangedAction.Add:
1476 case NotifyCollectionChangedAction.Remove:
1477 // Clear removed items.
1478 if (args.OldItems != null)
1480 if (args.OldItems.Contains(selectedItem))
1482 selectedItem = null;
1485 if (selectedItems != null)
1487 foreach (object removed in args.OldItems)
1489 if (selectedItems.Contains(removed))
1491 selectedItems.Remove(removed);
1497 case NotifyCollectionChangedAction.Replace:
1499 case NotifyCollectionChangedAction.Move:
1501 case NotifyCollectionChangedAction.Reset:
1504 throw new ArgumentOutOfRangeException(nameof(args));