1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Collections.Specialized;
21 using System.Windows.Input;
22 using System.ComponentModel;
23 using Tizen.NUI.BaseComponents;
24 using Tizen.NUI.Binding;
26 namespace Tizen.NUI.Components
29 /// Selectable RecyclerView that presenting a collection of items with variable layouters.
31 /// <since_tizen> 9 </since_tizen>
32 public class CollectionView : RecyclerView
35 /// Binding Property of selected item in single selection.
37 /// <since_tizen> 9 </since_tizen>
38 public static readonly BindableProperty SelectedItemProperty =
39 BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(CollectionView), null,
40 propertyChanged: (bindable, oldValue, newValue) =>
42 var colView = (CollectionView)bindable;
43 oldValue = colView.selectedItem;
44 colView.selectedItem = newValue;
45 var args = new SelectionChangedEventArgs(oldValue, newValue);
47 foreach (RecyclerViewItem item in colView.ContentContainer.Children.Where((item) => item is RecyclerViewItem))
49 if (item.BindingContext == null) continue;
50 if (item.BindingContext == oldValue) item.IsSelected = false;
51 else if (item.BindingContext == newValue) item.IsSelected = true;
54 SelectionPropertyChanged(colView, args);
56 defaultValueCreator: (bindable) =>
58 var colView = (CollectionView)bindable;
59 return colView.selectedItem;
63 /// Binding Property of selected items list in multiple selection.
65 /// <since_tizen> 9 </since_tizen>
66 public static readonly BindableProperty SelectedItemsProperty =
67 BindableProperty.Create(nameof(SelectedItems), typeof(IList<object>), typeof(CollectionView), null,
68 propertyChanged: (bindable, oldValue, newValue) =>
70 var colView = (CollectionView)bindable;
71 var oldSelection = colView.selectedItems ?? selectEmpty;
72 //FIXME : CoerceSelectedItems calls only isCreatedByXaml
73 var newSelection = (SelectionList)CoerceSelectedItems(colView, newValue);
74 colView.selectedItems = newSelection;
75 colView.SelectedItemsPropertyChanged(oldSelection, newSelection);
77 defaultValueCreator: (bindable) =>
79 var colView = (CollectionView)bindable;
80 colView.selectedItems = colView.selectedItems ?? new SelectionList(colView);
81 return colView.selectedItems;
85 /// Binding Property of selected items list in multiple selection.
87 /// <since_tizen> 9 </since_tizen>
88 public static readonly BindableProperty SelectionModeProperty =
89 BindableProperty.Create(nameof(SelectionMode), typeof(ItemSelectionMode), typeof(CollectionView), ItemSelectionMode.None,
90 propertyChanged: (bindable, oldValue, newValue) =>
92 var colView = (CollectionView)bindable;
93 oldValue = colView.selectionMode;
94 colView.selectionMode = (ItemSelectionMode)newValue;
95 SelectionModePropertyChanged(colView, oldValue, newValue);
97 defaultValueCreator: (bindable) =>
99 var colView = (CollectionView)bindable;
100 return colView.selectionMode;
104 private static readonly IList<object> selectEmpty = new List<object>(0);
105 private DataTemplate itemTemplate = null;
106 private IEnumerable itemsSource = null;
107 private ItemsLayouter itemsLayouter = null;
108 private DataTemplate groupHeaderTemplate;
109 private DataTemplate groupFooterTemplate;
110 private bool isGrouped;
111 private bool wasRelayouted = false;
112 private bool needInitalizeLayouter = false;
113 private object selectedItem;
114 private SelectionList selectedItems;
115 private bool suppressSelectionChangeNotification;
116 private ItemSelectionMode selectionMode = ItemSelectionMode.None;
117 private RecyclerViewItem header;
118 private RecyclerViewItem footer;
119 private View focusedView;
120 private int prevFocusedDataIndex = 0;
121 private List<RecyclerViewItem> recycleGroupHeaderCache { get; } = new List<RecyclerViewItem>();
122 private List<RecyclerViewItem> recycleGroupFooterCache { get; } = new List<RecyclerViewItem>();
123 private bool delayedScrollTo;
124 private (float position, bool anim) delayedScrollToParam;
126 private bool delayedIndexScrollTo;
127 private (int index, bool anim, ItemScrollTo scrollTo) delayedIndexScrollToParam;
130 /// Base constructor.
132 /// <since_tizen> 9 </since_tizen>
133 public CollectionView() : base()
136 SetKeyboardNavigationSupport(true);
140 /// Base constructor with ItemsSource
142 /// <param name="itemsSource">item's data source</param>
143 /// <since_tizen> 9 </since_tizen>
144 public CollectionView(IEnumerable itemsSource) : this()
146 ItemsSource = itemsSource;
150 /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
152 /// <param name="itemsSource">item's data source</param>
153 /// <param name="layouter">item's layout manager</param>
154 /// <param name="template">item's view template with data bindings</param>
155 [EditorBrowsable(EditorBrowsableState.Never)]
156 public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
158 ItemsSource = itemsSource;
159 ItemTemplate = template;
160 ItemsLayouter = layouter;
164 /// Event of Selection changed.
165 /// previous selection list and current selection will be provided.
167 /// <since_tizen> 9 </since_tizen>
168 public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
171 /// Align item in the viewport when ScrollTo() calls.
173 /// <since_tizen> 9 </since_tizen>
174 public enum ItemScrollTo
177 /// Scroll to show item in nearest viewport on scroll direction.
178 /// item is above the scroll viewport, item will be came into front,
179 /// item is under the scroll viewport, item will be came into end,
180 /// item is in the scroll viewport, no scroll.
182 /// <since_tizen> 9 </since_tizen>
185 /// Scroll to show item in start of the viewport.
187 /// <since_tizen> 9 </since_tizen>
190 /// Scroll to show item in center of the viewport.
192 /// <since_tizen> 9 </since_tizen>
195 /// Scroll to show item in end of the viewport.
197 /// <since_tizen> 9 </since_tizen>
202 /// Item's source data in IEnumerable.
204 /// <since_tizen> 9 </since_tizen>
205 public override IEnumerable ItemsSource
213 if (itemsSource != null)
215 // Clearing old data!
216 if (itemsSource is INotifyCollectionChanged prevNotifyCollectionChanged)
218 prevNotifyCollectionChanged.CollectionChanged -= CollectionChanged;
220 if (selectedItem != null) selectedItem = null;
221 selectedItems?.Clear();
227 InternalItemSource?.Dispose();
228 InternalItemSource = null;
229 itemsLayouter?.Clear();
233 if (itemsSource is INotifyCollectionChanged newNotifyCollectionChanged)
235 newNotifyCollectionChanged.CollectionChanged += CollectionChanged;
238 InternalItemSource?.Dispose();
239 InternalItemSource = ItemsSourceFactory.Create(this);
241 if (itemsLayouter == null) return;
243 needInitalizeLayouter = true;
249 /// DataTemplate for items.
250 /// Create visual contents and binding properties.
251 /// return object type is restricted RecyclerViewItem.
252 /// <seealso cref="Tizen.NUI.Binding.DataTemplate" />
254 /// <since_tizen> 9 </since_tizen>
255 public override DataTemplate ItemTemplate
263 itemTemplate = value;
269 needInitalizeLayouter = true;
276 /// Layouting items on the scroll ContentContainer.
277 /// <seealso cref="ItemsLayouter" />
278 /// <seealso cref="LinearLayouter" />
279 /// <seealso cref="GridLayouter" />
281 /// <since_tizen> 9 </since_tizen>
282 public virtual ItemsLayouter ItemsLayouter
286 return itemsLayouter;
290 itemsLayouter?.Clear();
293 itemsLayouter = value;
294 base.InternalItemsLayouter = itemsLayouter;
297 needInitalizeLayouter = false;
301 needInitalizeLayouter = true;
303 var styleName = "Tizen.NUI.Components." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
304 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
305 if (layouterStyle != null)
307 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
314 /// Scrolling direction to display items layout.
316 /// <since_tizen> 9 </since_tizen>
317 public new Direction ScrollingDirection
321 return base.ScrollingDirection;
325 if (base.ScrollingDirection != value)
327 base.ScrollingDirection = value;
328 needInitalizeLayouter = true;
335 /// Selected item in single selection.
337 /// <since_tizen> 9 </since_tizen>
338 public object SelectedItem
340 get => GetValue(SelectedItemProperty);
341 set => SetValue(SelectedItemProperty, value);
345 /// Selected items list in multiple selection.
347 /// <since_tizen> 9 </since_tizen>
348 public IList<object> SelectedItems
350 get => (IList<object>)GetValue(SelectedItemsProperty);
351 // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
355 /// Selection mode to handle items selection. See ItemSelectionMode for details.
357 /// <since_tizen> 9 </since_tizen>
358 public ItemSelectionMode SelectionMode
360 get => (ItemSelectionMode)GetValue(SelectionModeProperty);
361 set => SetValue(SelectionModeProperty, value);
365 /// Command of selection changed.
367 [EditorBrowsable(EditorBrowsableState.Never)]
368 public ICommand SelectionChangedCommand { set; get; }
371 /// Command parameter of selection changed.
373 [EditorBrowsable(EditorBrowsableState.Never)]
374 public object SelectionChangedCommandParameter { set; get; }
377 /// Header item placed in top-most position.
379 /// <remarks>Please note that, internal index will be increased by header.</remarks>
380 /// <since_tizen> 9 </since_tizen>
381 public RecyclerViewItem Header
388 //ContentContainer.Remove(header);
389 Utility.Dispose(header);
394 value.ParentItemsView = this;
395 value.IsHeader = true;
396 ContentContainer.Add(value);
399 if (InternalItemSource != null)
401 InternalItemSource.HasHeader = (value != null);
403 needInitalizeLayouter = true;
409 /// Footer item placed in bottom-most position.
411 /// <remarks>Please note that, internal index will be increased by footer.</remarks>
412 /// <since_tizen> 9 </since_tizen>
413 public RecyclerViewItem Footer
420 //ContentContainer.Remove(footer);
421 Utility.Dispose(footer);
425 value.Index = InternalItemSource?.Count ?? 0;
426 value.ParentItemsView = this;
427 value.IsFooter = true;
428 ContentContainer.Add(value);
431 if (InternalItemSource != null)
433 InternalItemSource.HasFooter = (value != null);
435 needInitalizeLayouter = true;
441 /// Enable groupable view.
443 [EditorBrowsable(EditorBrowsableState.Never)]
444 public bool IsGrouped
450 needInitalizeLayouter = true;
451 //Need to re-intialize Internal Item Source.
452 if (InternalItemSource != null)
454 InternalItemSource.Dispose();
455 InternalItemSource = null;
457 if (ItemsSource != null)
458 InternalItemSource = ItemsSourceFactory.Create(this);
464 /// DataTemplate of group header.
466 /// <remarks>Please note that, internal index will be increased by group header.
467 /// GroupHeaderTemplate is essential for groupable view.</remarks>
468 [EditorBrowsable(EditorBrowsableState.Never)]
469 public DataTemplate GroupHeaderTemplate
473 return groupHeaderTemplate;
477 groupHeaderTemplate = value;
478 needInitalizeLayouter = true;
479 //Need to re-intialize Internal Item Source.
480 if (InternalItemSource != null)
482 InternalItemSource.Dispose();
483 InternalItemSource = null;
485 if (ItemsSource != null)
486 InternalItemSource = ItemsSourceFactory.Create(this);
492 /// DataTemplate of group footer. Group feature is not supported yet.
494 /// <remarks>Please note that, internal index will be increased by group footer.</remarks>
495 [EditorBrowsable(EditorBrowsableState.Never)]
496 public DataTemplate GroupFooterTemplate
500 return groupFooterTemplate;
504 groupFooterTemplate = value;
505 needInitalizeLayouter = true;
506 //Need to re-intialize Internal Item Source.
507 if (InternalItemSource != null)
509 InternalItemSource.Dispose();
510 InternalItemSource = null;
512 if (ItemsSource != null)
513 InternalItemSource = ItemsSourceFactory.Create(this);
519 /// Internal encapsulated items data source.
521 internal new IGroupableItemSource InternalItemSource
525 return (base.InternalItemSource as IGroupableItemSource);
529 base.InternalItemSource = value;
534 /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
536 [EditorBrowsable(EditorBrowsableState.Never)]
537 internal ItemSizingStrategy SizingStrategy { get; set; }
540 /// <since_tizen> 9 </since_tizen>
541 public override void OnRelayout(Vector2 size, RelayoutContainer container)
543 base.OnRelayout(size, container);
545 wasRelayouted = true;
546 if (needInitalizeLayouter) Init();
550 [EditorBrowsable(EditorBrowsableState.Never)]
551 public override void NotifyDataSetChanged()
553 if (selectedItem != null)
557 if (selectedItems != null)
559 selectedItems.Clear();
562 base.NotifyDataSetChanged();
566 [EditorBrowsable(EditorBrowsableState.Never)]
567 public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
569 View nextFocusedView = null;
571 if (focusedView == null)
573 // If focusedView is null, find child which has previous data index
574 if (ContentContainer.Children.Count > 0 && InternalItemSource.Count > 0)
576 for (int i = 0; i < ContentContainer.Children.Count; i++)
578 RecyclerViewItem item = Children[i] as RecyclerViewItem;
579 if (item?.Index == prevFocusedDataIndex)
581 nextFocusedView = item;
589 // If this is not first focus, request next focus to Layouter
590 nextFocusedView = ItemsLayouter.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
593 if (nextFocusedView != null)
595 // Check next focused view is inside of visible area.
596 // If it is not, move scroll position to make it visible.
597 Position scrollPosition = ContentContainer.CurrentPosition;
598 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
600 float left = nextFocusedView.Position.X;
601 float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
602 float top = nextFocusedView.Position.Y;
603 float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
605 float visibleRectangleLeft = -scrollPosition.X;
606 float visibleRectangleRight = -scrollPosition.X + Size.Width;
607 float visibleRectangleTop = -scrollPosition.Y;
608 float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
610 if (ScrollingDirection == Direction.Horizontal)
612 if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
614 targetPosition = left;
616 else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
618 targetPosition = right - Size.Width;
623 if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
625 targetPosition = top;
627 else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
629 targetPosition = bottom - Size.Height;
633 focusedView = nextFocusedView;
634 prevFocusedDataIndex = (nextFocusedView as RecyclerViewItem)?.Index ?? -1;
636 ScrollTo(targetPosition, true);
640 // If nextView is null, it means that we should move focus to outside of Control.
641 // Return FocusableView depending on direction.
644 case View.FocusDirection.Left:
646 nextFocusedView = LeftFocusableView;
649 case View.FocusDirection.Right:
651 nextFocusedView = RightFocusableView;
654 case View.FocusDirection.Up:
656 nextFocusedView = UpFocusableView;
659 case View.FocusDirection.Down:
661 nextFocusedView = DownFocusableView;
666 if (nextFocusedView != null)
672 //If FocusableView doesn't exist, not move focus.
673 nextFocusedView = focusedView;
677 return nextFocusedView;
681 /// Update selected items list in multiple selection.
683 /// <param name="newSelection">updated selection list by user</param>
684 /// <since_tizen> 9 </since_tizen>
685 public void UpdateSelectedItems(IList<object> newSelection)
687 var oldSelection = new List<object>(SelectedItems);
689 suppressSelectionChangeNotification = true;
691 SelectedItems.Clear();
693 if (newSelection?.Count > 0)
695 for (int n = 0; n < newSelection.Count; n++)
697 SelectedItems.Add(newSelection[n]);
701 suppressSelectionChangeNotification = false;
703 SelectedItemsPropertyChanged(oldSelection, newSelection);
707 /// Scroll to specific position with or without animation.
709 /// <param name="position">Destination.</param>
710 /// <param name="animate">Scroll with or without animation</param>
711 /// <since_tizen> 9 </since_tizen>
712 public new void ScrollTo(float position, bool animate)
714 if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
715 if ((InternalItemSource == null) || needInitalizeLayouter)
717 delayedScrollTo = true;
718 delayedScrollToParam = (position, animate);
722 base.ScrollTo(position, animate);
726 /// Scrolls to the item at the specified index.
728 /// <param name="index">Index of item.</param>
729 [EditorBrowsable(EditorBrowsableState.Never)]
730 public new void ScrollToIndex(int index)
732 ScrollTo(index, true, ItemScrollTo.Start);
736 /// Scroll to specific item's aligned position with or without animation.
738 /// <param name="index">Target item index of dataset.</param>
739 /// <param name="animate">Boolean flag of animation.</param>
740 /// <param name="align">Align state of item. See details in <see cref="ItemScrollTo"/>.</param>
741 /// <since_tizen> 9 </since_tizen>
742 public virtual void ScrollTo(int index, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
744 if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
745 if ((InternalItemSource == null) || needInitalizeLayouter)
747 delayedIndexScrollTo = true;
748 delayedIndexScrollToParam = (index, animate, align);
751 if (index < 0 || index >= InternalItemSource.Count)
753 throw new Exception("index is out of boundary. index should be a value between (0, " + InternalItemSource.Count.ToString() + ").");
756 float scrollPos, curPos, curSize, curItemSize;
757 (float x, float y) = ItemsLayouter.GetItemPosition(index);
758 (float width, float height) = ItemsLayouter.GetItemSize(index);
759 if (ScrollingDirection == Direction.Horizontal)
762 curPos = ScrollPosition.X;
763 curSize = Size.Width;
769 curPos = ScrollPosition.Y;
770 curSize = Size.Height;
771 curItemSize = height;
774 //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalItemSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
777 case ItemScrollTo.Start:
780 case ItemScrollTo.Center:
781 scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
783 case ItemScrollTo.End:
784 scrollPos = scrollPos - curSize + curItemSize;
786 case ItemScrollTo.Nearest:
787 if (scrollPos < curPos - curItemSize)
789 // item is placed before the current screen. scrollTo.Top
791 else if (scrollPos >= curPos + curSize + curItemSize)
793 // item is placed after the current screen. scrollTo.End
794 scrollPos = scrollPos - curSize + curItemSize;
798 // item is in the scroller. ScrollTo() is ignored.
804 //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
805 base.ScrollTo(scrollPos, animate);
809 /// Apply style to CollectionView
811 /// <param name="viewStyle">The style to apply.</param>
812 [EditorBrowsable(EditorBrowsableState.Never)]
813 public override void ApplyStyle(ViewStyle viewStyle)
815 base.ApplyStyle(viewStyle);
816 if (viewStyle != null)
818 //Extension = RecyclerViewItemStyle.CreateExtension();
820 if (itemsLayouter != null)
822 string styleName = "Tizen.NUI.Compoenents." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
823 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
824 if (layouterStyle != null)
825 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
829 // Realize and Decorate the item.
830 internal override RecyclerViewItem RealizeItem(int index)
832 RecyclerViewItem item;
833 if (index == 0 && Header != null)
839 if (index == InternalItemSource.Count - 1 && Footer != null)
847 var context = InternalItemSource.GetItem(index);
848 if (InternalItemSource.IsGroupHeader(index))
850 DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
852 RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
853 if (groupHeader == null)
855 groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
857 groupHeader.Template = templ;
858 groupHeader.isGroupHeader = true;
859 groupHeader.isGroupFooter = false;
860 ContentContainer.Add(groupHeader);
863 if (groupHeader != null)
865 groupHeader.ParentItemsView = this;
866 groupHeader.Index = index;
867 groupHeader.ParentGroup = context;
868 groupHeader.BindingContext = context;
873 else if (InternalItemSource.IsGroupFooter(index))
875 DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
877 RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
878 if (groupFooter == null)
880 groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this);
882 groupFooter.Template = templ;
883 groupFooter.isGroupHeader = false;
884 groupFooter.isGroupFooter = true;
885 ContentContainer.Add(groupFooter);
888 if (groupFooter != null)
890 groupFooter.ParentItemsView = this;
891 groupFooter.Index = index;
892 groupFooter.ParentGroup = context;
893 groupFooter.BindingContext = context;
900 item = base.RealizeItem(index);
901 item.ParentGroup = InternalItemSource.GetGroupParent(index);
906 item = base.RealizeItem(index);
909 switch (SelectionMode)
911 case ItemSelectionMode.Single:
912 case ItemSelectionMode.SingleAlways:
913 if (item.BindingContext != null && item.BindingContext == SelectedItem)
915 item.IsSelected = true;
919 case ItemSelectionMode.Multiple:
920 if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
922 item.IsSelected = true;
925 case ItemSelectionMode.None:
926 item.IsSelectable = false;
932 // Unrealize and caching the item.
933 internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
935 if (item == null) return;
946 if (item.isGroupHeader || item.isGroupFooter)
949 item.ParentItemsView = null;
950 item.BindingContext = null;
951 item.IsPressed = false;
952 item.IsSelected = false;
953 item.IsEnabled = true;
955 //item.Relayout -= OnItemRelayout;
956 if (!recycle || !PushRecycleGroupCache(item))
957 Utility.Dispose(item);
961 base.UnrealizeItem(item, recycle);
964 internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
966 if (suppressSelectionChangeNotification)
971 foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
973 if (item.BindingContext == null) continue;
974 if (newSelection.Contains(item.BindingContext))
976 if (!item.IsSelected) item.IsSelected = true;
980 if (item.IsSelected) item.IsSelected = false;
983 SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
985 OnPropertyChanged(SelectedItemsProperty.PropertyName);
989 /// Internal selection callback.
991 /// <since_tizen> 9 </since_tizen>
992 protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
998 /// Adjust scrolling position by own scrolling rules.
999 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1001 /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
1002 /// <returns>Adjusted scroll destination</returns>
1003 [EditorBrowsable(EditorBrowsableState.Never)]
1004 protected override float AdjustTargetPositionOfScrollAnimation(float position)
1006 // Destination is depending on implementation of layout manager.
1007 // Get destination from layout manager.
1008 return ItemsLayouter?.CalculateCandidateScrollPosition(position) ?? position;
1012 [EditorBrowsable(EditorBrowsableState.Never)]
1013 protected override void ClearCache()
1015 foreach (RecyclerViewItem item in recycleGroupHeaderCache)
1017 Utility.Dispose(item);
1019 recycleGroupHeaderCache.Clear();
1020 foreach (RecyclerViewItem item in recycleGroupFooterCache)
1022 Utility.Dispose(item);
1024 recycleGroupFooterCache.Clear();
1030 /// OnScroll event callback. Requesting layout to the layouter with given scrollPosition.
1032 /// <param name="source">Scroll source object</param>
1033 /// <param name="args">Scroll event argument</param>
1034 /// <since_tizen> 9 </since_tizen>
1035 protected override void OnScrolling(object source, ScrollEventArgs args)
1037 if (disposed) return;
1039 if (needInitalizeLayouter && (ItemsLayouter != null))
1041 ItemsLayouter.Initialize(this);
1042 needInitalizeLayouter = false;
1045 base.OnScrolling(source, args);
1049 /// Dispose ItemsView and all children on it.
1051 /// <param name="type">Dispose type.</param>
1052 /// <since_tizen> 9 </since_tizen>
1053 protected override void Dispose(DisposeTypes type)
1060 if (type == DisposeTypes.Explicit)
1062 // From now on, no need to use this properties,
1063 // so remove reference, to push it into garbage collector.
1065 // Arugable to disposing user-created members.
1069 Utility.Dispose(Header);
1074 Utility.Dispose(Footer);
1079 groupHeaderTemplate = null;
1080 groupFooterTemplate = null;
1082 if (selectedItem != null)
1084 selectedItem = null;
1086 if (selectedItems != null)
1088 selectedItems.Clear();
1089 selectedItems = null;
1091 if (InternalItemSource != null)
1093 InternalItemSource.Dispose();
1094 InternalItemSource = null;
1101 private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
1103 var command = colView.SelectionChangedCommand;
1105 if (command != null)
1107 var commandParameter = colView.SelectionChangedCommandParameter;
1109 if (command.CanExecute(commandParameter))
1111 command.Execute(commandParameter);
1114 colView.SelectionChanged?.Invoke(colView, args);
1115 colView.OnSelectionChanged(args);
1118 private static object CoerceSelectedItems(BindableObject bindable, object value)
1122 return new SelectionList((CollectionView)bindable);
1125 if (value is SelectionList)
1130 return new SelectionList((CollectionView)bindable, value as IList<object>);
1133 private static void SelectionModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
1135 var colView = (CollectionView)bindable;
1137 var oldMode = (ItemSelectionMode)oldValue;
1138 var newMode = (ItemSelectionMode)newValue;
1140 IList<object> previousSelection = new List<object>();
1141 IList<object> newSelection = new List<object>();
1145 case ItemSelectionMode.None:
1147 case ItemSelectionMode.Single:
1148 if (colView.SelectedItem != null)
1150 previousSelection.Add(colView.SelectedItem);
1153 case ItemSelectionMode.Multiple:
1154 previousSelection = colView.SelectedItems;
1160 case ItemSelectionMode.None:
1162 case ItemSelectionMode.Single:
1163 if (colView.SelectedItem != null)
1165 newSelection.Add(colView.SelectedItem);
1168 case ItemSelectionMode.Multiple:
1169 newSelection = colView.SelectedItems;
1173 if (previousSelection.Count == newSelection.Count)
1175 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
1177 // Both selections are empty or have the same single item; no reason to signal a change
1182 var args = new SelectionChangedEventArgs(previousSelection, newSelection);
1183 SelectionPropertyChanged(colView, args);
1188 if (ItemsSource == null) return;
1189 if (ItemsLayouter == null) return;
1190 if (ItemTemplate == null) return;
1192 if (disposed) return;
1193 if (needInitalizeLayouter)
1195 if (InternalItemSource == null) return;
1197 InternalItemSource.HasHeader = (header != null);
1198 InternalItemSource.HasFooter = (footer != null);
1201 if (!wasRelayouted) return;
1203 if (needInitalizeLayouter)
1205 itemsLayouter.Clear();
1208 ItemsLayouter.Initialize(this);
1209 needInitalizeLayouter = false;
1211 ItemsLayouter.RequestLayout(0.0f, true);
1213 if (delayedScrollTo)
1215 delayedScrollTo = false;
1216 ScrollTo(delayedScrollToParam.position, delayedScrollToParam.anim);
1219 if (delayedIndexScrollTo)
1221 delayedIndexScrollTo = false;
1222 ScrollTo(delayedIndexScrollToParam.index, delayedIndexScrollToParam.anim, delayedIndexScrollToParam.scrollTo);
1225 if (ScrollingDirection == Direction.Horizontal)
1227 ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
1231 ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
1235 private bool PushRecycleGroupCache(RecyclerViewItem item)
1237 if (item == null) throw new ArgumentNullException(nameof(item));
1238 if (RecycleCache.Count >= 20) return false;
1239 if (item.Template == null) return false;
1240 if (item.isGroupHeader)
1242 recycleGroupHeaderCache.Add(item);
1244 else if (item.isGroupFooter)
1246 recycleGroupFooterCache.Add(item);
1254 private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1256 RecyclerViewItem viewItem = null;
1258 var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1259 for (int i = 0; i < Cache.Count; i++)
1261 viewItem = Cache[i];
1262 if (Template == viewItem.Template) break;
1265 if (viewItem != null)
1267 Cache.Remove(viewItem);
1272 private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
1274 switch (args.Action)
1276 case NotifyCollectionChangedAction.Add:
1278 case NotifyCollectionChangedAction.Remove:
1279 // Clear removed items.
1280 if (args.OldItems != null)
1282 if (args.OldItems.Contains(selectedItem))
1284 selectedItem = null;
1287 if (selectedItems != null)
1289 foreach (object removed in args.OldItems)
1291 if (selectedItems.Contains(removed))
1293 selectedItems.Remove(removed);
1299 case NotifyCollectionChangedAction.Replace:
1301 case NotifyCollectionChangedAction.Move:
1303 case NotifyCollectionChangedAction.Reset:
1306 throw new ArgumentOutOfRangeException(nameof(args));