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.
17 using System.Collections;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using Tizen.NUI.Binding;
22 namespace Tizen.NUI.Components
25 /// [Draft] This class provides a View that can layouting items in list and grid with high performance.
27 [EditorBrowsable(EditorBrowsableState.Never)]
28 public abstract class RecyclerView : ScrollableBase, ICollectionChangedNotifier
33 [EditorBrowsable(EditorBrowsableState.Never)]
34 public RecyclerView() : base()
36 Scrolling += OnScrolling;
40 /// Item's source data.
42 [EditorBrowsable(EditorBrowsableState.Never)]
43 public virtual IEnumerable ItemsSource { get; set; }
46 /// DataTemplate for items.
48 [EditorBrowsable(EditorBrowsableState.Never)]
49 public virtual DataTemplate ItemTemplate { get; set; }
52 /// Internal encapsulated items data source.
54 internal IItemSource InternalItemSource { get; set; }
57 /// RecycleCache of ViewItem.
59 [EditorBrowsable(EditorBrowsableState.Never)]
60 protected List<RecyclerViewItem> RecycleCache { get; } = new List<RecyclerViewItem>();
63 /// Internal Items Layouter.
65 [EditorBrowsable(EditorBrowsableState.Never)]
66 protected ItemsLayouter InternalItemsLayouter { get; set; }
69 /// Max size of RecycleCache. Default is 50.
71 [EditorBrowsable(EditorBrowsableState.Never)]
72 protected int CacheMax { get; set; } = 50;
75 [EditorBrowsable(EditorBrowsableState.Never)]
76 public override void OnRelayout(Vector2 size, RelayoutContainer container)
78 //Console.WriteLine("[NUI] On ReLayout [{0} {0}]", size.X, size.Y);
79 base.OnRelayout(size, container);
80 if (InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null)
82 InternalItemsLayouter.Initialize(this);
83 InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y, true);
88 /// Notify Dataset is Changed.
90 [EditorBrowsable(EditorBrowsableState.Never)]
91 public virtual void NotifyDataSetChanged()
93 //Need to update view.
94 if (InternalItemsLayouter != null)
96 InternalItemsLayouter.NotifyDataSetChanged();
97 if (ScrollingDirection == Direction.Horizontal)
99 ContentContainer.SizeWidth =
100 InternalItemsLayouter.CalculateLayoutOrientationSize();
104 ContentContainer.SizeHeight =
105 InternalItemsLayouter.CalculateLayoutOrientationSize();
111 /// Notify observable item is changed.
113 /// <param name="source">Dataset source.</param>
114 /// <param name="startIndex">Changed item index.</param>
115 [EditorBrowsable(EditorBrowsableState.Never)]
116 public virtual void NotifyItemChanged(IItemSource source, int startIndex)
118 if (InternalItemsLayouter != null)
120 InternalItemsLayouter.NotifyItemChanged(source, startIndex);
125 /// Notify range of observable items from start to end are changed.
127 /// <param name="source">Dataset source.</param>
128 /// <param name="startRange">Start index of changed items range.</param>
129 /// <param name="endRange">End index of changed items range.</param>
130 [EditorBrowsable(EditorBrowsableState.Never)]
131 public virtual void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
133 if (InternalItemsLayouter != null)
135 InternalItemsLayouter.NotifyItemRangeChanged(source, startRange, endRange);
140 /// Notify observable item is inserted in dataset.
142 /// <param name="source">Dataset source.</param>
143 /// <param name="startIndex">Inserted item index.</param>
144 [EditorBrowsable(EditorBrowsableState.Never)]
145 public virtual void NotifyItemInserted(IItemSource source, int startIndex)
147 if (InternalItemsLayouter != null)
149 InternalItemsLayouter.NotifyItemInserted(source, startIndex);
154 /// Notify count range of observable count items are inserted in startIndex.
156 /// <param name="source">Dataset source.</param>
157 /// <param name="startIndex">Start index of inserted items range.</param>
158 /// <param name="count">The number of inserted items.</param>
159 [EditorBrowsable(EditorBrowsableState.Never)]
160 public virtual void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
162 if (InternalItemsLayouter != null)
164 InternalItemsLayouter.NotifyItemRangeInserted(source, startIndex, count);
169 /// Notify observable item is moved from fromPosition to ToPosition.
171 /// <param name="source">Dataset source.</param>
172 /// <param name="fromPosition">Previous item position.</param>
173 /// <param name="toPosition">Moved item position.</param>
174 [EditorBrowsable(EditorBrowsableState.Never)]
175 public virtual void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
177 if (InternalItemsLayouter != null)
179 InternalItemsLayouter.NotifyItemMoved(source, fromPosition, toPosition);
184 /// Notify the observable item is moved from fromPosition to ToPosition.
186 /// <param name="source"></param>
187 /// <param name="fromPosition"></param>
188 /// <param name="toPosition"></param>
189 /// <param name="count"></param>
190 [EditorBrowsable(EditorBrowsableState.Never)]
191 public virtual void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
193 if (InternalItemsLayouter != null)
195 InternalItemsLayouter.NotifyItemRangeMoved(source, fromPosition, toPosition, count);
200 /// Notify the observable item in startIndex is removed.
202 /// <param name="source">Dataset source.</param>
203 /// <param name="startIndex">Index of removed item.</param>
204 [EditorBrowsable(EditorBrowsableState.Never)]
205 public virtual void NotifyItemRemoved(IItemSource source, int startIndex)
207 if (InternalItemsLayouter != null)
209 InternalItemsLayouter.NotifyItemRemoved(source, startIndex);
214 /// Notify the count range of observable items from the startIndex are removed.
216 /// <param name="source">Dataset source.</param>
217 /// <param name="startIndex">Start index of removed items range.</param>
218 /// <param name="count">The number of removed items</param>
219 [EditorBrowsable(EditorBrowsableState.Never)]
220 public virtual void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
222 if (InternalItemsLayouter != null)
224 InternalItemsLayouter.NotifyItemRangeRemoved(source, startIndex, count);
229 /// Realize indexed item.
231 /// <param name="index"> Index position of realizing item </param>
232 internal virtual RecyclerViewItem RealizeItem(int index)
234 object context = InternalItemSource.GetItem(index);
235 // Check DataTemplate is Same!
236 if (ItemTemplate is DataTemplateSelector)
238 // Need to implements for caching of selector!
243 RecyclerViewItem item = PopRecycleCache(ItemTemplate);
246 DecorateItem(item, index, context);
251 object content = DataTemplateExtensions.CreateContent(ItemTemplate, context, (BindableObject)this) ?? throw new Exception("Template return null object.");
252 if (content is RecyclerViewItem)
254 RecyclerViewItem item = (RecyclerViewItem)content;
255 ContentContainer.Add(item);
256 DecorateItem(item, index, context);
261 throw new Exception("Template content must be type of ViewItem");
267 /// Unrealize indexed item.
269 /// <param name="item"> Target item for unrealizing </param>
270 /// <param name="recycle"> Allow recycle. default is true </param>
271 internal virtual void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
274 item.ParentItemsView = null;
275 item.BindingContext = null;
276 item.IsPressed = false;
277 item.IsSelected = false;
278 item.IsEnabled = true;
280 item.Relayout -= OnItemRelayout;
282 if (!recycle || !PushRecycleCache(item))
284 //ContentContainer.Remove(item);
285 Utility.Dispose(item);
290 /// Adjust scrolling position by own scrolling rules.
291 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
293 /// <param name="position">Scroll position which is calculated by ScrollableBase.</param>
294 /// <returns>Adjusted scroll destination</returns>
295 [EditorBrowsable(EditorBrowsableState.Never)]
296 protected override float AdjustTargetPositionOfScrollAnimation(float position)
298 // Destination is depending on implementation of layout manager.
299 // Get destination from layout manager.
300 return InternalItemsLayouter.CalculateCandidateScrollPosition(position);
304 /// Push the item into the recycle cache. this item will be reused in view update.
306 /// <param name="item"> Target item to push into recycle cache. </param>
307 [EditorBrowsable(EditorBrowsableState.Never)]
308 protected virtual bool PushRecycleCache(RecyclerViewItem item)
310 if (item == null) throw new ArgumentNullException(nameof(item));
311 if (RecycleCache.Count >= CacheMax) return false;
312 if (item.Template == null) return false;
315 RecycleCache.Add(item);
320 /// Pop the item from the recycle cache.
322 /// <param name="Template"> Template of wanted item. </param>
323 [EditorBrowsable(EditorBrowsableState.Never)]
324 protected virtual RecyclerViewItem PopRecycleCache(DataTemplate Template)
326 for (int i = 0; i < RecycleCache.Count; i++)
328 RecyclerViewItem item = RecycleCache[i];
329 if (item.Template == Template)
331 RecycleCache.Remove(item);
340 /// On scroll event callback.
342 [EditorBrowsable(EditorBrowsableState.Never)]
343 protected virtual void OnScrolling(object source, ScrollEventArgs args)
345 if (args == null) throw new ArgumentNullException(nameof(args));
346 if (!disposed && InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null)
348 //Console.WriteLine("[NUI] On Scrolling! {0} => {1}", ScrollPosition.Y, args.Position.Y);
349 InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
354 /// Dispose ItemsView and all children on it.
356 /// <param name="type">Dispose type.</param>
357 [EditorBrowsable(EditorBrowsableState.Never)]
358 protected override void Dispose(DisposeTypes type)
365 if (type == DisposeTypes.Explicit)
369 if (RecycleCache != null)
371 foreach (RecyclerViewItem item in RecycleCache)
373 //ContentContainer.Remove(item);
374 Utility.Dispose(item);
376 RecycleCache.Clear();
378 InternalItemsLayouter.Clear();
379 InternalItemsLayouter = null;
382 if (InternalItemSource != null) InternalItemSource.Dispose();
389 private void OnItemRelayout(object sender, EventArgs e)
391 //FIXME: we need to skip the first relayout and only call size changed when real size change happen.
392 //InternalItemsLayouter.NotifyItemSizeChanged((sender as ViewItem));
393 //InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
396 private void DecorateItem(RecyclerViewItem item, int index, object context)
399 item.ParentItemsView = this;
400 item.Template = (ItemTemplate as DataTemplateSelector)?.SelectDataTemplate(InternalItemSource.GetItem(index), this) ?? ItemTemplate;
401 item.BindingContext = context;
402 item.Relayout += OnItemRelayout;