1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Collections.Specialized;
21 using System.Windows.Input;
22 using System.ComponentModel;
23 using Tizen.NUI.BaseComponents;
24 using Tizen.NUI.Binding;
26 namespace Tizen.NUI.Components
29 /// Selectable RecyclerView that presenting a collection of items with variable layouters.
31 /// <since_tizen> 9 </since_tizen>
32 public partial class CollectionView : RecyclerView
35 /// Binding Property of selected item in single selection.
37 /// <since_tizen> 9 </since_tizen>
38 public static readonly BindableProperty SelectedItemProperty =
39 BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(CollectionView), null,
40 propertyChanged: (bindable, oldValue, newValue) =>
42 var colView = (CollectionView)bindable;
43 oldValue = colView.selectedItem;
44 colView.selectedItem = newValue;
45 var args = new SelectionChangedEventArgs(oldValue, newValue);
47 foreach (RecyclerViewItem item in colView.ContentContainer.Children.Where((item) => item is RecyclerViewItem))
49 if (item.BindingContext == null) continue;
50 if (item.BindingContext == oldValue) item.IsSelected = false;
51 else if (item.BindingContext == newValue) item.IsSelected = true;
54 SelectionPropertyChanged(colView, args);
56 defaultValueCreator: (bindable) =>
58 var colView = (CollectionView)bindable;
59 return colView.selectedItem;
63 /// Binding Property of selected items list in multiple selection.
65 /// <since_tizen> 9 </since_tizen>
66 public static readonly BindableProperty SelectedItemsProperty =
67 BindableProperty.Create(nameof(SelectedItems), typeof(IList<object>), typeof(CollectionView), null,
68 propertyChanged: (bindable, oldValue, newValue) =>
70 var colView = (CollectionView)bindable;
71 var oldSelection = colView.selectedItems ?? selectEmpty;
72 //FIXME : CoerceSelectedItems calls only isCreatedByXaml
73 var newSelection = (SelectionList)CoerceSelectedItems(colView, newValue);
74 colView.selectedItems = newSelection;
75 colView.SelectedItemsPropertyChanged(oldSelection, newSelection);
77 defaultValueCreator: (bindable) =>
79 var colView = (CollectionView)bindable;
80 colView.selectedItems = colView.selectedItems ?? new SelectionList(colView);
81 return colView.selectedItems;
85 /// Binding Property of selected items list in multiple selection.
87 /// <since_tizen> 9 </since_tizen>
88 public static readonly BindableProperty SelectionModeProperty =
89 BindableProperty.Create(nameof(SelectionMode), typeof(ItemSelectionMode), typeof(CollectionView), ItemSelectionMode.None,
90 propertyChanged: (bindable, oldValue, newValue) =>
92 var colView = (CollectionView)bindable;
93 oldValue = colView.selectionMode;
94 colView.selectionMode = (ItemSelectionMode)newValue;
95 SelectionModePropertyChanged(colView, oldValue, newValue);
97 defaultValueCreator: (bindable) =>
99 var colView = (CollectionView)bindable;
100 return colView.selectionMode;
104 /// Binding Property of items data source.
106 [EditorBrowsable(EditorBrowsableState.Never)]
107 public static readonly BindableProperty ItemsSourceProperty =
108 BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(CollectionView), null,
109 propertyChanged: (bindable, oldValue, newValue) =>
111 var colView = (CollectionView)bindable;
112 oldValue = colView.itemsSource;
114 if (oldValue != null)
116 // Clearing old data!
117 if (oldValue is INotifyCollectionChanged prevNotifyCollectionChanged)
119 prevNotifyCollectionChanged.CollectionChanged -= colView.CollectionChanged;
121 if (colView.selectedItem != null) colView.selectedItem = null;
122 colView.selectedItems?.Clear();
125 colView.itemsSource = (IEnumerable)newValue;
127 if (newValue == null)
129 colView.InternalItemSource?.Dispose();
130 colView.InternalItemSource = null;
131 colView.itemsLayouter?.Clear();
132 colView.ClearCache();
135 if (newValue is INotifyCollectionChanged newNotifyCollectionChanged)
137 newNotifyCollectionChanged.CollectionChanged += colView.CollectionChanged;
140 colView.InternalItemSource?.Dispose();
141 colView.InternalItemSource = ItemsSourceFactory.Create(colView);
143 if (colView.itemsLayouter == null) return;
145 colView.needInitalizeLayouter = true;
148 defaultValueCreator: (bindable) =>
150 var colView = (CollectionView)bindable;
151 return colView.itemsSource;
155 private static readonly IList<object> selectEmpty = new List<object>(0);
156 private DataTemplate itemTemplate = null;
157 private IEnumerable itemsSource = null;
158 private ItemsLayouter itemsLayouter = null;
159 private DataTemplate groupHeaderTemplate;
160 private DataTemplate groupFooterTemplate;
161 private bool isGrouped;
162 private bool wasRelayouted = false;
163 private bool needInitalizeLayouter = false;
164 private object selectedItem;
165 private SelectionList selectedItems;
166 private bool suppressSelectionChangeNotification;
167 private ItemSelectionMode selectionMode = ItemSelectionMode.None;
168 private RecyclerViewItem header;
169 private RecyclerViewItem footer;
170 private View focusedView;
171 private int prevFocusedDataIndex = 0;
172 private List<RecyclerViewItem> recycleGroupHeaderCache { get; } = new List<RecyclerViewItem>();
173 private List<RecyclerViewItem> recycleGroupFooterCache { get; } = new List<RecyclerViewItem>();
174 private bool delayedScrollTo;
175 private (float position, bool anim) delayedScrollToParam;
177 private bool delayedIndexScrollTo;
178 private (int index, bool anim, ItemScrollTo scrollTo) delayedIndexScrollToParam;
181 /// Base constructor.
183 /// <since_tizen> 9 </since_tizen>
184 public CollectionView() : base()
187 SetKeyboardNavigationSupport(true);
191 /// Base constructor with ItemsSource
193 /// <param name="itemsSource">item's data source</param>
194 /// <since_tizen> 9 </since_tizen>
195 public CollectionView(IEnumerable itemsSource) : this()
197 ItemsSource = itemsSource;
201 /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
203 /// <param name="itemsSource">item's data source</param>
204 /// <param name="layouter">item's layout manager</param>
205 /// <param name="template">item's view template with data bindings</param>
206 [EditorBrowsable(EditorBrowsableState.Never)]
207 public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
209 ItemsSource = itemsSource;
210 ItemTemplate = template;
211 ItemsLayouter = layouter;
215 /// Event of Selection changed.
216 /// previous selection list and current selection will be provided.
218 /// <since_tizen> 9 </since_tizen>
219 public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
222 /// Align item in the viewport when ScrollTo() calls.
224 /// <since_tizen> 9 </since_tizen>
225 public enum ItemScrollTo
228 /// Scroll to show item in nearest viewport on scroll direction.
229 /// item is above the scroll viewport, item will be came into front,
230 /// item is under the scroll viewport, item will be came into end,
231 /// item is in the scroll viewport, no scroll.
233 /// <since_tizen> 9 </since_tizen>
236 /// Scroll to show item in start of the viewport.
238 /// <since_tizen> 9 </since_tizen>
241 /// Scroll to show item in center of the viewport.
243 /// <since_tizen> 9 </since_tizen>
246 /// Scroll to show item in end of the viewport.
248 /// <since_tizen> 9 </since_tizen>
253 /// Item's source data in IEnumerable.
255 /// <since_tizen> 9 </since_tizen>
256 public override IEnumerable ItemsSource
258 get => (IEnumerable)GetValue(ItemsSourceProperty);
259 set => SetValue(ItemsSourceProperty, value);
263 /// DataTemplate for items.
264 /// Create visual contents and binding properties.
265 /// return object type is restricted RecyclerViewItem.
266 /// <seealso cref="Tizen.NUI.Binding.DataTemplate" />
268 /// <since_tizen> 9 </since_tizen>
269 public override DataTemplate ItemTemplate
273 return GetValue(ItemTemplateProperty) as DataTemplate;
277 SetValue(ItemTemplateProperty, value);
278 NotifyPropertyChanged();
281 private DataTemplate InternalItemTemplate
289 itemTemplate = value;
295 needInitalizeLayouter = true;
302 /// Layouting items on the scroll ContentContainer.
303 /// <seealso cref="ItemsLayouter" />
304 /// <seealso cref="LinearLayouter" />
305 /// <seealso cref="GridLayouter" />
307 /// <since_tizen> 9 </since_tizen>
308 public virtual ItemsLayouter ItemsLayouter
312 return GetValue(ItemsLayouterProperty) as ItemsLayouter;
316 SetValue(ItemsLayouterProperty, value);
317 NotifyPropertyChanged();
320 private ItemsLayouter InternalItemsLayouter
324 return itemsLayouter;
328 itemsLayouter?.Clear();
331 itemsLayouter = value;
332 base.InternalItemsLayouter = itemsLayouter;
335 needInitalizeLayouter = false;
339 needInitalizeLayouter = true;
341 var styleName = "Tizen.NUI.Components." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
342 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
343 if (layouterStyle != null)
345 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
352 /// Scrolling direction to display items layout.
354 /// <since_tizen> 9 </since_tizen>
355 public new Direction ScrollingDirection
359 return (Direction)GetValue(ScrollingDirectionProperty);
363 SetValue(ScrollingDirectionProperty, value);
364 NotifyPropertyChanged();
367 private Direction InternalScrollingDirection
371 return base.ScrollingDirection;
375 if (base.ScrollingDirection != value)
377 base.ScrollingDirection = value;
378 needInitalizeLayouter = true;
385 /// Selected item in single selection.
387 /// <since_tizen> 9 </since_tizen>
388 public object SelectedItem
390 get => GetValue(SelectedItemProperty);
391 set => SetValue(SelectedItemProperty, value);
395 /// Selected items list in multiple selection.
397 /// <since_tizen> 9 </since_tizen>
398 public IList<object> SelectedItems
400 get => (IList<object>)GetValue(SelectedItemsProperty);
401 // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
405 /// Selection mode to handle items selection. See ItemSelectionMode for details.
407 /// <since_tizen> 9 </since_tizen>
408 public ItemSelectionMode SelectionMode
410 get => (ItemSelectionMode)GetValue(SelectionModeProperty);
411 set => SetValue(SelectionModeProperty, value);
415 /// Command of selection changed.
417 [EditorBrowsable(EditorBrowsableState.Never)]
418 public ICommand SelectionChangedCommand
422 return GetValue(SelectionChangedCommandProperty) as ICommand;
426 SetValue(SelectionChangedCommandProperty, value);
427 NotifyPropertyChanged();
430 private ICommand InternalSelectionChangedCommand { set; get; }
433 /// Command parameter of selection changed.
435 [EditorBrowsable(EditorBrowsableState.Never)]
436 public object SelectionChangedCommandParameter
440 return GetValue(SelectionChangedCommandParameterProperty);
444 SetValue(SelectionChangedCommandParameterProperty, value);
445 NotifyPropertyChanged();
448 private object InternalSelectionChangedCommandParameter { set; get; }
451 /// Header item placed in top-most position.
453 /// <remarks>Please note that, internal index will be increased by header.</remarks>
454 /// <since_tizen> 9 </since_tizen>
455 public RecyclerViewItem Header
459 return GetValue(HeaderProperty) as RecyclerViewItem;
463 SetValue(HeaderProperty, value);
464 NotifyPropertyChanged();
467 private RecyclerViewItem InternalHeader
474 //ContentContainer.Remove(header);
475 Utility.Dispose(header);
480 value.ParentItemsView = this;
481 value.IsHeader = true;
482 ContentContainer.Add(value);
485 if (InternalItemSource != null)
487 InternalItemSource.HasHeader = (value != null);
489 needInitalizeLayouter = true;
495 /// Footer item placed in bottom-most position.
497 /// <remarks>Please note that, internal index will be increased by footer.</remarks>
498 /// <since_tizen> 9 </since_tizen>
499 public RecyclerViewItem Footer
503 return GetValue(FooterProperty) as RecyclerViewItem;
507 SetValue(FooterProperty, value);
508 NotifyPropertyChanged();
511 private RecyclerViewItem InternalFooter
518 //ContentContainer.Remove(footer);
519 Utility.Dispose(footer);
523 value.Index = InternalItemSource?.Count ?? 0;
524 value.ParentItemsView = this;
525 value.IsFooter = true;
526 ContentContainer.Add(value);
529 if (InternalItemSource != null)
531 InternalItemSource.HasFooter = (value != null);
533 needInitalizeLayouter = true;
539 /// Enable groupable view.
541 [EditorBrowsable(EditorBrowsableState.Never)]
542 public bool IsGrouped
546 return (bool)GetValue(IsGroupedProperty);
550 SetValue(IsGroupedProperty, value);
551 NotifyPropertyChanged();
554 private bool InternalIsGrouped
560 needInitalizeLayouter = true;
561 //Need to re-intialize Internal Item Source.
562 if (InternalItemSource != null)
564 InternalItemSource.Dispose();
565 InternalItemSource = null;
567 if (ItemsSource != null)
568 InternalItemSource = ItemsSourceFactory.Create(this);
574 /// DataTemplate of group header.
576 /// <remarks>Please note that, internal index will be increased by group header.
577 /// GroupHeaderTemplate is essential for groupable view.</remarks>
578 [EditorBrowsable(EditorBrowsableState.Never)]
579 public DataTemplate GroupHeaderTemplate
583 return GetValue(GroupHeaderTemplateProperty) as DataTemplate;
587 SetValue(GroupHeaderTemplateProperty, value);
588 NotifyPropertyChanged();
591 private DataTemplate InternalGroupHeaderTemplate
595 return groupHeaderTemplate;
599 groupHeaderTemplate = value;
600 needInitalizeLayouter = true;
601 //Need to re-intialize Internal Item Source.
602 if (InternalItemSource != null)
604 InternalItemSource.Dispose();
605 InternalItemSource = null;
607 if (ItemsSource != null)
608 InternalItemSource = ItemsSourceFactory.Create(this);
614 /// DataTemplate of group footer. Group feature is not supported yet.
616 /// <remarks>Please note that, internal index will be increased by group footer.</remarks>
617 [EditorBrowsable(EditorBrowsableState.Never)]
618 public DataTemplate GroupFooterTemplate
622 return GetValue(GroupFooterTemplateProperty) as DataTemplate;
626 SetValue(GroupFooterTemplateProperty, value);
627 NotifyPropertyChanged();
630 private DataTemplate InternalGroupFooterTemplate
634 return groupFooterTemplate;
638 groupFooterTemplate = value;
639 needInitalizeLayouter = true;
640 //Need to re-intialize Internal Item Source.
641 if (InternalItemSource != null)
643 InternalItemSource.Dispose();
644 InternalItemSource = null;
646 if (ItemsSource != null)
647 InternalItemSource = ItemsSourceFactory.Create(this);
653 /// Internal encapsulated items data source.
655 internal new IGroupableItemSource InternalItemSource
659 return (base.InternalItemSource as IGroupableItemSource);
663 base.InternalItemSource = value;
668 /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
670 [EditorBrowsable(EditorBrowsableState.Never)]
671 internal ItemSizingStrategy SizingStrategy { get; set; }
674 /// <since_tizen> 9 </since_tizen>
675 public override void OnRelayout(Vector2 size, RelayoutContainer container)
677 base.OnRelayout(size, container);
679 wasRelayouted = true;
680 if (needInitalizeLayouter) Init();
684 [EditorBrowsable(EditorBrowsableState.Never)]
685 public override void NotifyDataSetChanged()
687 if (selectedItem != null)
691 if (selectedItems != null)
693 selectedItems.Clear();
696 base.NotifyDataSetChanged();
700 [EditorBrowsable(EditorBrowsableState.Never)]
701 public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
703 View nextFocusedView = null;
705 if (focusedView == null)
707 // If focusedView is null, find child which has previous data index
708 if (ContentContainer.Children.Count > 0 && InternalItemSource.Count > 0)
710 for (int i = 0; i < ContentContainer.Children.Count; i++)
712 RecyclerViewItem item = Children[i] as RecyclerViewItem;
713 if (item?.Index == prevFocusedDataIndex)
715 nextFocusedView = item;
723 // If this is not first focus, request next focus to Layouter
724 nextFocusedView = ItemsLayouter?.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
727 if (nextFocusedView != null)
729 // Check next focused view is inside of visible area.
730 // If it is not, move scroll position to make it visible.
731 Position scrollPosition = ContentContainer.CurrentPosition;
732 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
734 float left = nextFocusedView.Position.X;
735 float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
736 float top = nextFocusedView.Position.Y;
737 float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
739 float visibleRectangleLeft = -scrollPosition.X;
740 float visibleRectangleRight = -scrollPosition.X + Size.Width;
741 float visibleRectangleTop = -scrollPosition.Y;
742 float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
744 if (ScrollingDirection == Direction.Horizontal)
746 if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
748 targetPosition = left;
750 else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
752 targetPosition = right - Size.Width;
757 if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
759 targetPosition = top;
761 else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
763 targetPosition = bottom - Size.Height;
767 focusedView = nextFocusedView;
768 prevFocusedDataIndex = (nextFocusedView as RecyclerViewItem)?.Index ?? -1;
770 ScrollTo(targetPosition, true);
774 // If nextView is null, it means that we should move focus to outside of Control.
775 // Return FocusableView depending on direction.
778 case View.FocusDirection.Left:
780 nextFocusedView = LeftFocusableView;
783 case View.FocusDirection.Right:
785 nextFocusedView = RightFocusableView;
788 case View.FocusDirection.Up:
790 nextFocusedView = UpFocusableView;
793 case View.FocusDirection.Down:
795 nextFocusedView = DownFocusableView;
800 if (nextFocusedView != null)
806 //If FocusableView doesn't exist, not move focus.
807 nextFocusedView = focusedView;
811 return nextFocusedView;
815 /// Update selected items list in multiple selection.
817 /// <param name="newSelection">updated selection list by user</param>
818 /// <since_tizen> 9 </since_tizen>
819 public void UpdateSelectedItems(IList<object> newSelection)
821 var oldSelection = new List<object>(SelectedItems);
823 suppressSelectionChangeNotification = true;
825 SelectedItems.Clear();
827 if (newSelection?.Count > 0)
829 for (int n = 0; n < newSelection.Count; n++)
831 SelectedItems.Add(newSelection[n]);
835 suppressSelectionChangeNotification = false;
837 SelectedItemsPropertyChanged(oldSelection, newSelection);
841 /// Scroll to specific position with or without animation.
843 /// <param name="position">Destination.</param>
844 /// <param name="animate">Scroll with or without animation</param>
845 /// <since_tizen> 9 </since_tizen>
846 public new void ScrollTo(float position, bool animate)
848 if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
849 if ((InternalItemSource == null) || needInitalizeLayouter)
851 delayedScrollTo = true;
852 delayedScrollToParam = (position, animate);
856 base.ScrollTo(position, animate);
860 /// Scrolls to the item at the specified index.
862 /// <param name="index">Index of item.</param>
863 [EditorBrowsable(EditorBrowsableState.Never)]
864 public new void ScrollToIndex(int index)
866 ScrollTo(index, true, ItemScrollTo.Start);
870 /// Scroll to specific item's aligned position with or without animation.
872 /// <param name="index">Target item index of dataset.</param>
873 /// <param name="animate">Boolean flag of animation.</param>
874 /// <param name="align">Align state of item. See details in <see cref="ItemScrollTo"/>.</param>
875 /// <since_tizen> 9 </since_tizen>
876 public virtual void ScrollTo(int index, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
878 if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
879 if ((InternalItemSource == null) || needInitalizeLayouter)
881 delayedIndexScrollTo = true;
882 delayedIndexScrollToParam = (index, animate, align);
885 if (index < 0 || index >= InternalItemSource.Count)
887 throw new Exception("index is out of boundary. index should be a value between (0, " + InternalItemSource.Count.ToString() + ").");
890 float scrollPos, curPos, curSize, curItemSize;
891 (float x, float y) = ItemsLayouter.GetItemPosition(index);
892 (float width, float height) = ItemsLayouter.GetItemSize(index);
893 if (ScrollingDirection == Direction.Horizontal)
896 curPos = ScrollPosition.X;
897 curSize = Size.Width;
903 curPos = ScrollPosition.Y;
904 curSize = Size.Height;
905 curItemSize = height;
908 //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalItemSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
911 case ItemScrollTo.Start:
914 case ItemScrollTo.Center:
915 scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
917 case ItemScrollTo.End:
918 scrollPos = scrollPos - curSize + curItemSize;
920 case ItemScrollTo.Nearest:
921 if (scrollPos < curPos - curItemSize)
923 // item is placed before the current screen. scrollTo.Top
925 else if (scrollPos >= curPos + curSize + curItemSize)
927 // item is placed after the current screen. scrollTo.End
928 scrollPos = scrollPos - curSize + curItemSize;
932 // item is in the scroller. ScrollTo() is ignored.
938 //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
939 base.ScrollTo(scrollPos, animate);
943 /// Apply style to CollectionView
945 /// <param name="viewStyle">The style to apply.</param>
946 [EditorBrowsable(EditorBrowsableState.Never)]
947 public override void ApplyStyle(ViewStyle viewStyle)
949 base.ApplyStyle(viewStyle);
950 if (viewStyle != null)
952 //Extension = RecyclerViewItemStyle.CreateExtension();
954 if (itemsLayouter != null)
956 string styleName = "Tizen.NUI.Compoenents." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
957 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
958 if (layouterStyle != null)
959 itemsLayouter.Padding = new Extents(layouterStyle.Padding);
965 /// Scroll to specified item
968 /// Make sure that the item that is about to receive the accessibility highlight is visible.
970 [EditorBrowsable(EditorBrowsableState.Never)]
971 protected override bool AccessibilityScrollToChild(View child)
973 if (ScrollingDirection == Direction.Horizontal)
975 if (child.ScreenPosition.X + child.Size.Width <= this.ScreenPosition.X)
977 ScrollTo((float)(child.ScreenPosition.X - ContentContainer.ScreenPosition.X), false);
979 else if (child.ScreenPosition.X >= this.ScreenPosition.X + this.Size.Width)
981 ScrollTo((float)(child.ScreenPosition.X + child.Size.Width - ContentContainer.ScreenPosition.X - this.Size.Width), false);
986 if (child.ScreenPosition.Y + child.Size.Height <= this.ScreenPosition.Y)
988 ScrollTo((float)(child.ScreenPosition.Y - ContentContainer.ScreenPosition.Y), false);
990 else if (child.ScreenPosition.Y >= this.ScreenPosition.Y + this.Size.Height)
992 ScrollTo((float)(child.ScreenPosition.Y + child.Size.Height - ContentContainer.ScreenPosition.Y - this.Size.Height), false);
998 // Realize and Decorate the item.
999 internal override RecyclerViewItem RealizeItem(int index)
1001 RecyclerViewItem item;
1002 if (index == 0 && Header != null)
1008 if (index == InternalItemSource.Count - 1 && Footer != null)
1016 var context = InternalItemSource.GetItem(index);
1017 if (InternalItemSource.IsGroupHeader(index))
1019 DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
1021 RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
1022 if (groupHeader == null)
1024 groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
1026 groupHeader.Template = templ;
1027 groupHeader.isGroupHeader = true;
1028 groupHeader.isGroupFooter = false;
1029 ContentContainer.Add(groupHeader);
1032 if (groupHeader != null)
1034 groupHeader.ParentItemsView = this;
1035 groupHeader.Index = index;
1036 groupHeader.ParentGroup = context;
1037 groupHeader.BindingContext = context;
1042 else if (InternalItemSource.IsGroupFooter(index))
1044 DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
1046 RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
1047 if (groupFooter == null)
1049 groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this);
1051 groupFooter.Template = templ;
1052 groupFooter.isGroupHeader = false;
1053 groupFooter.isGroupFooter = true;
1054 ContentContainer.Add(groupFooter);
1057 if (groupFooter != null)
1059 groupFooter.ParentItemsView = this;
1060 groupFooter.Index = index;
1061 groupFooter.ParentGroup = context;
1062 groupFooter.BindingContext = context;
1069 item = base.RealizeItem(index);
1071 throw new Exception("Item realize failed by Null content return.");
1072 item.ParentGroup = InternalItemSource.GetGroupParent(index);
1077 item = base.RealizeItem(index);
1081 throw new Exception("Item realize failed by Null content return.");
1083 switch (SelectionMode)
1085 case ItemSelectionMode.Single:
1086 case ItemSelectionMode.SingleAlways:
1087 if (item.BindingContext != null && item.BindingContext == SelectedItem)
1089 item.IsSelected = true;
1093 case ItemSelectionMode.Multiple:
1094 if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
1096 item.IsSelected = true;
1099 case ItemSelectionMode.None:
1100 item.IsSelectable = false;
1106 // Unrealize and caching the item.
1107 internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
1109 if (item == null) return;
1120 if (item.isGroupHeader || item.isGroupFooter)
1123 item.ParentItemsView = null;
1124 item.BindingContext = null;
1125 item.IsPressed = false;
1126 item.IsSelected = false;
1127 item.IsEnabled = true;
1129 //item.Relayout -= OnItemRelayout;
1130 if (!recycle || !PushRecycleGroupCache(item))
1131 Utility.Dispose(item);
1135 base.UnrealizeItem(item, recycle);
1138 internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
1140 if (suppressSelectionChangeNotification)
1145 foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
1147 if (item.BindingContext == null) continue;
1148 if (newSelection.Contains(item.BindingContext))
1150 if (!item.IsSelected) item.IsSelected = true;
1154 if (item.IsSelected) item.IsSelected = false;
1157 SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
1159 OnPropertyChanged(SelectedItemsProperty.PropertyName);
1163 /// Internal selection callback.
1165 /// <since_tizen> 9 </since_tizen>
1166 protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
1168 //Selection Callback
1172 /// Adjust scrolling position by own scrolling rules.
1173 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1175 /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
1176 /// <returns>Adjusted scroll destination</returns>
1177 [EditorBrowsable(EditorBrowsableState.Never)]
1178 protected override float AdjustTargetPositionOfScrollAnimation(float position)
1180 // Destination is depending on implementation of layout manager.
1181 // Get destination from layout manager.
1182 return ItemsLayouter?.CalculateCandidateScrollPosition(position) ?? position;
1186 [EditorBrowsable(EditorBrowsableState.Never)]
1187 protected override void ClearCache()
1189 foreach (RecyclerViewItem item in recycleGroupHeaderCache)
1191 Utility.Dispose(item);
1193 recycleGroupHeaderCache.Clear();
1194 foreach (RecyclerViewItem item in recycleGroupFooterCache)
1196 Utility.Dispose(item);
1198 recycleGroupFooterCache.Clear();
1204 /// OnScroll event callback. Requesting layout to the layouter with given scrollPosition.
1206 /// <param name="source">Scroll source object</param>
1207 /// <param name="args">Scroll event argument</param>
1208 /// <since_tizen> 9 </since_tizen>
1209 protected override void OnScrolling(object source, ScrollEventArgs args)
1211 if (disposed) return;
1213 if (needInitalizeLayouter && (ItemsLayouter != null))
1215 ItemsLayouter.Initialize(this);
1216 needInitalizeLayouter = false;
1219 base.OnScrolling(source, args);
1223 /// Dispose ItemsView and all children on it.
1225 /// <param name="type">Dispose type.</param>
1226 /// <since_tizen> 9 </since_tizen>
1227 protected override void Dispose(DisposeTypes type)
1234 if (type == DisposeTypes.Explicit)
1236 // From now on, no need to use this properties,
1237 // so remove reference, to push it into garbage collector.
1239 // Arugable to disposing user-created members.
1243 Utility.Dispose(Header);
1248 Utility.Dispose(Footer);
1253 groupHeaderTemplate = null;
1254 groupFooterTemplate = null;
1256 if (selectedItem != null)
1258 selectedItem = null;
1260 if (selectedItems != null)
1262 selectedItems.Clear();
1263 selectedItems = null;
1265 if (InternalItemSource != null)
1267 InternalItemSource.Dispose();
1268 InternalItemSource = null;
1275 private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
1277 var command = colView.SelectionChangedCommand;
1279 if (command != null)
1281 var commandParameter = colView.SelectionChangedCommandParameter;
1283 if (command.CanExecute(commandParameter))
1285 command.Execute(commandParameter);
1288 colView.SelectionChanged?.Invoke(colView, args);
1289 colView.OnSelectionChanged(args);
1292 private static object CoerceSelectedItems(BindableObject bindable, object value)
1296 return new SelectionList((CollectionView)bindable);
1299 if (value is SelectionList)
1304 return new SelectionList((CollectionView)bindable, value as IList<object>);
1307 private static void SelectionModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
1309 var colView = (CollectionView)bindable;
1311 var oldMode = (ItemSelectionMode)oldValue;
1312 var newMode = (ItemSelectionMode)newValue;
1314 IList<object> previousSelection = new List<object>();
1315 IList<object> newSelection = new List<object>();
1319 case ItemSelectionMode.None:
1321 case ItemSelectionMode.Single:
1322 if (colView.SelectedItem != null)
1324 previousSelection.Add(colView.SelectedItem);
1327 case ItemSelectionMode.Multiple:
1328 previousSelection = colView.SelectedItems;
1334 case ItemSelectionMode.None:
1336 case ItemSelectionMode.Single:
1337 if (colView.SelectedItem != null)
1339 newSelection.Add(colView.SelectedItem);
1342 case ItemSelectionMode.Multiple:
1343 newSelection = colView.SelectedItems;
1347 if (previousSelection.Count == newSelection.Count)
1349 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
1351 // Both selections are empty or have the same single item; no reason to signal a change
1356 var args = new SelectionChangedEventArgs(previousSelection, newSelection);
1357 SelectionPropertyChanged(colView, args);
1362 if (ItemsSource == null) return;
1363 if (ItemsLayouter == null) return;
1364 if (ItemTemplate == null) return;
1366 if (disposed) return;
1367 if (needInitalizeLayouter)
1369 if (InternalItemSource == null) return;
1371 InternalItemSource.HasHeader = (header != null);
1372 InternalItemSource.HasFooter = (footer != null);
1375 if (!wasRelayouted) return;
1377 if (needInitalizeLayouter)
1379 itemsLayouter.Clear();
1382 ItemsLayouter.Initialize(this);
1383 needInitalizeLayouter = false;
1385 ItemsLayouter.RequestLayout(0.0f, true);
1387 if (delayedScrollTo)
1389 delayedScrollTo = false;
1390 ScrollTo(delayedScrollToParam.position, delayedScrollToParam.anim);
1393 if (delayedIndexScrollTo)
1395 delayedIndexScrollTo = false;
1396 ScrollTo(delayedIndexScrollToParam.index, delayedIndexScrollToParam.anim, delayedIndexScrollToParam.scrollTo);
1399 if (ScrollingDirection == Direction.Horizontal)
1401 ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
1405 ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
1409 private bool PushRecycleGroupCache(RecyclerViewItem item)
1411 if (item == null) throw new ArgumentNullException(nameof(item));
1412 if (RecycleCache.Count >= 20) return false;
1413 if (item.Template == null) return false;
1414 if (item.isGroupHeader)
1416 recycleGroupHeaderCache.Add(item);
1418 else if (item.isGroupFooter)
1420 recycleGroupFooterCache.Add(item);
1428 private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1430 RecyclerViewItem viewItem = null;
1432 var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1433 for (int i = 0; i < Cache.Count; i++)
1435 viewItem = Cache[i];
1436 if (Template == viewItem.Template) break;
1439 if (viewItem != null)
1441 Cache.Remove(viewItem);
1446 private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
1448 switch (args.Action)
1450 case NotifyCollectionChangedAction.Add:
1452 case NotifyCollectionChangedAction.Remove:
1453 // Clear removed items.
1454 if (args.OldItems != null)
1456 if (args.OldItems.Contains(selectedItem))
1458 selectedItem = null;
1461 if (selectedItems != null)
1463 foreach (object removed in args.OldItems)
1465 if (selectedItems.Contains(removed))
1467 selectedItems.Remove(removed);
1473 case NotifyCollectionChangedAction.Replace:
1475 case NotifyCollectionChangedAction.Move:
1477 case NotifyCollectionChangedAction.Reset:
1480 throw new ArgumentOutOfRangeException(nameof(args));