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.ComponentModel;
21 using Tizen.NUI.BaseComponents;
22 using Tizen.NUI.Binding;
24 namespace Tizen.NUI.Components
27 /// [Draft] This class provides a View that can layouting items in list and grid with high performance.
29 [EditorBrowsable(EditorBrowsableState.Never)]
30 public abstract class RecyclerView : ScrollableBase, ICollectionChangedNotifier
35 [EditorBrowsable(EditorBrowsableState.Never)]
36 public RecyclerView() : base()
38 Scrolling += OnScrolling;
42 /// Item's source data.
44 [EditorBrowsable(EditorBrowsableState.Never)]
45 public virtual IEnumerable ItemsSource { get; set; }
48 /// DataTemplate for items.
50 [EditorBrowsable(EditorBrowsableState.Never)]
51 public virtual DataTemplate ItemTemplate { get; set; }
54 /// Internal encapsulated items data source.
56 internal IItemSource InternalItemSource { get; set;}
59 /// RecycleCache of ViewItem.
61 [EditorBrowsable(EditorBrowsableState.Never)]
62 protected List<RecyclerViewItem> RecycleCache { get; } = new List<RecyclerViewItem>();
65 /// Internal Items Layouter.
67 [EditorBrowsable(EditorBrowsableState.Never)]
68 protected ItemsLayouter InternalItemsLayouter {get; set; }
71 /// Max size of RecycleCache. Default is 50.
73 [EditorBrowsable(EditorBrowsableState.Never)]
74 protected int CacheMax { get; set; } = 50;
77 [EditorBrowsable(EditorBrowsableState.Never)]
78 public override void OnRelayout(Vector2 size, RelayoutContainer container)
80 //Console.WriteLine("[NUI] On ReLayout [{0} {0}]", size.X, size.Y);
81 base.OnRelayout(size, container);
82 if (InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null)
84 InternalItemsLayouter.Initialize(this);
85 InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y, true);
90 /// Notify Dataset is Changed.
92 [EditorBrowsable(EditorBrowsableState.Never)]
93 public void NotifyDataSetChanged()
95 //Need to update view.
96 if (InternalItemsLayouter != null)
98 InternalItemsLayouter.NotifyDataSetChanged();
99 if (ScrollingDirection == Direction.Horizontal)
101 ContentContainer.SizeWidth =
102 InternalItemsLayouter.CalculateLayoutOrientationSize();
106 ContentContainer.SizeHeight =
107 InternalItemsLayouter.CalculateLayoutOrientationSize();
113 /// Notify observable item is changed.
115 /// <param name="source">Dataset source.</param>
116 /// <param name="startIndex">Changed item index.</param>
117 [EditorBrowsable(EditorBrowsableState.Never)]
118 public void NotifyItemChanged(IItemSource source, int startIndex)
120 if (InternalItemsLayouter != null)
122 InternalItemsLayouter.NotifyItemChanged(source, startIndex);
127 /// Notify observable item is inserted in dataset.
129 /// <param name="source">Dataset source.</param>
130 /// <param name="startIndex">Inserted item index.</param>
131 [EditorBrowsable(EditorBrowsableState.Never)]
132 public void NotifyItemInserted(IItemSource source, int startIndex)
134 if (InternalItemsLayouter != null)
136 InternalItemsLayouter.NotifyItemInserted(source, startIndex);
141 /// Notify observable item is moved from fromPosition to ToPosition.
143 /// <param name="source">Dataset source.</param>
144 /// <param name="fromPosition">Previous item position.</param>
145 /// <param name="toPosition">Moved item position.</param>
146 [EditorBrowsable(EditorBrowsableState.Never)]
147 public void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
149 if (InternalItemsLayouter != null)
151 InternalItemsLayouter.NotifyItemMoved(source, fromPosition, toPosition);
156 /// Notify range of observable items from start to end are changed.
158 /// <param name="source">Dataset source.</param>
159 /// <param name="start">Start index of changed items range.</param>
160 /// <param name="end">End index of changed items range.</param>
161 [EditorBrowsable(EditorBrowsableState.Never)]
162 public void NotifyItemRangeChanged(IItemSource source, int start, int end)
164 if (InternalItemsLayouter != null)
166 InternalItemsLayouter.NotifyItemRangeChanged(source, start, end);
171 /// Notify count range of observable count items are inserted in startIndex.
173 /// <param name="source">Dataset source.</param>
174 /// <param name="startIndex">Start index of inserted items range.</param>
175 /// <param name="count">The number of inserted items.</param>
176 [EditorBrowsable(EditorBrowsableState.Never)]
177 public void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
179 if (InternalItemsLayouter != null)
181 InternalItemsLayouter.NotifyItemRangeInserted(source, startIndex, count);
186 /// Notify the count range of observable items from the startIndex are removed.
188 /// <param name="source">Dataset source.</param>
189 /// <param name="startIndex">Start index of removed items range.</param>
190 /// <param name="count">The number of removed items</param>
191 [EditorBrowsable(EditorBrowsableState.Never)]
192 public void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
194 if (InternalItemsLayouter != null)
196 InternalItemsLayouter.NotifyItemRangeRemoved(source, startIndex, count);
201 /// Notify the observable item in startIndex is removed.
203 /// <param name="source">Dataset source.</param>
204 /// <param name="startIndex">Index of removed item.</param>
205 [EditorBrowsable(EditorBrowsableState.Never)]
206 public void NotifyItemRemoved(IItemSource source, int startIndex)
208 if (InternalItemsLayouter != null)
210 InternalItemsLayouter.NotifyItemRemoved(source, startIndex);
215 /// Realize indexed item.
217 /// <param name="index"> Index position of realizing item </param>
218 internal virtual RecyclerViewItem RealizeItem(int index)
220 object context = InternalItemSource.GetItem(index);
221 // Check DataTemplate is Same!
222 if (ItemTemplate is DataTemplateSelector)
224 // Need to implements for caching of selector!
229 RecyclerViewItem item = PopRecycleCache(ItemTemplate);
232 DecorateItem(item, index, context);
237 object content = DataTemplateExtensions.CreateContent(ItemTemplate, context, (BindableObject)this) ?? throw new Exception("Template return null object.");
238 if (content is RecyclerViewItem)
240 RecyclerViewItem item = (RecyclerViewItem)content;
241 ContentContainer.Add(item);
242 DecorateItem(item, index, context);
247 throw new Exception("Template content must be type of ViewItem");
253 /// Unrealize indexed item.
255 /// <param name="item"> Target item for unrealizing </param>
256 /// <param name="recycle"> Allow recycle. default is true </param>
257 internal virtual void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
260 item.ParentItemsView = null;
261 // Remove BindingContext null set for performance improving.
262 //item.BindingContext = null;
263 item.IsPressed = false;
264 item.IsSelected = false;
265 item.IsEnabled = true;
266 // Remove Update Style on default for performance improving.
267 //item.UpdateState();
268 item.Relayout -= OnItemRelayout;
270 if (!recycle || !PushRecycleCache(item))
272 //ContentContainer.Remove(item);
273 Utility.Dispose(item);
278 /// Adjust scrolling position by own scrolling rules.
279 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
281 /// <param name="position">Scroll position which is calculated by ScrollableBase.</param>
282 /// <returns>Adjusted scroll destination</returns>
283 [EditorBrowsable(EditorBrowsableState.Never)]
284 protected override float AdjustTargetPositionOfScrollAnimation(float position)
286 // Destination is depending on implementation of layout manager.
287 // Get destination from layout manager.
288 return InternalItemsLayouter.CalculateCandidateScrollPosition(position);
292 /// Push the item into the recycle cache. this item will be reused in view update.
294 /// <param name="item"> Target item to push into recycle cache. </param>
295 [EditorBrowsable(EditorBrowsableState.Never)]
296 protected virtual bool PushRecycleCache(RecyclerViewItem item)
298 if (item == null) throw new ArgumentNullException(nameof(item));
299 if (RecycleCache.Count >= CacheMax) return false;
300 if (item.Template == null) return false;
303 RecycleCache.Add(item);
308 /// Pop the item from the recycle cache.
310 /// <param name="Template"> Template of wanted item. </param>
311 [EditorBrowsable(EditorBrowsableState.Never)]
312 protected virtual RecyclerViewItem PopRecycleCache(DataTemplate Template)
314 for (int i = 0; i < RecycleCache.Count; i++)
316 RecyclerViewItem item = RecycleCache[i];
317 if (item.Template == Template)
319 RecycleCache.Remove(item);
328 /// On scroll event callback.
330 [EditorBrowsable(EditorBrowsableState.Never)]
331 protected virtual void OnScrolling(object source, ScrollEventArgs args)
333 if (args == null) throw new ArgumentNullException(nameof(args));
334 if (!disposed && InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null)
336 //Console.WriteLine("[NUI] On Scrolling! {0} => {1}", ScrollPosition.Y, args.Position.Y);
337 InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
342 /// Dispose ItemsView and all children on it.
344 /// <param name="type">Dispose type.</param>
345 [EditorBrowsable(EditorBrowsableState.Never)]
346 protected override void Dispose(DisposeTypes type)
353 if (type == DisposeTypes.Explicit)
357 if (RecycleCache != null)
359 foreach (RecyclerViewItem item in RecycleCache)
361 //ContentContainer.Remove(item);
362 Utility.Dispose(item);
364 RecycleCache.Clear();
366 InternalItemsLayouter.Clear();
367 InternalItemsLayouter = null;
370 if (InternalItemSource != null) InternalItemSource.Dispose();
377 private void OnItemRelayout(object sender, EventArgs e)
379 //FIXME: we need to skip the first relayout and only call size changed when real size change happen.
380 //InternalItemsLayouter.NotifyItemSizeChanged((sender as ViewItem));
381 //InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
384 private void DecorateItem(RecyclerViewItem item, int index, object context)
387 item.ParentItemsView = this;
388 item.Template = (ItemTemplate as DataTemplateSelector)?.SelectDataTemplate(InternalItemSource.GetItem(index), this) ?? ItemTemplate;
389 item.BindingContext = context;
390 item.Relayout += OnItemRelayout;