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 /// A View that serves as a base class for views that contain a templated list of items.
27 /// <since_tizen> 9 </since_tizen>
28 public abstract class RecyclerView : ScrollableBase, ICollectionChangedNotifier
33 /// <since_tizen> 9 </since_tizen>
34 public RecyclerView() : base()
36 Scrolling += OnScrolling;
40 /// Item's source data.
42 /// <since_tizen> 9 </since_tizen>
43 public virtual IEnumerable ItemsSource { get; set; }
46 /// DataTemplate for items.
48 /// <since_tizen> 9 </since_tizen>
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 /// <since_tizen> 9 </since_tizen>
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)
273 if (item == null) return;
275 item.ParentItemsView = null;
276 item.BindingContext = null;
277 item.IsPressed = false;
278 item.IsSelected = false;
279 item.IsEnabled = true;
281 item.Relayout -= OnItemRelayout;
283 if (!recycle || !PushRecycleCache(item))
285 //ContentContainer.Remove(item);
286 Utility.Dispose(item);
291 /// Adjust scrolling position by own scrolling rules.
292 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
294 /// <param name="position">Scroll position which is calculated by ScrollableBase.</param>
295 /// <returns>Adjusted scroll destination</returns>
296 [EditorBrowsable(EditorBrowsableState.Never)]
297 protected override float AdjustTargetPositionOfScrollAnimation(float position)
299 // Destination is depending on implementation of layout manager.
300 // Get destination from layout manager.
301 return InternalItemsLayouter.CalculateCandidateScrollPosition(position);
305 /// Push the item into the recycle cache. this item will be reused in view update.
307 /// <param name="item"> Target item to push into recycle cache. </param>
308 [EditorBrowsable(EditorBrowsableState.Never)]
309 protected virtual bool PushRecycleCache(RecyclerViewItem item)
311 if (item == null) throw new ArgumentNullException(nameof(item));
312 if (RecycleCache.Count >= CacheMax) return false;
313 if (item.Template == null) return false;
316 RecycleCache.Add(item);
321 /// Pop the item from the recycle cache.
323 /// <param name="Template"> Template of wanted item. </param>
324 [EditorBrowsable(EditorBrowsableState.Never)]
325 protected virtual RecyclerViewItem PopRecycleCache(DataTemplate Template)
327 for (int i = 0; i < RecycleCache.Count; i++)
329 RecyclerViewItem item = RecycleCache[i];
330 if (item.Template == Template)
332 RecycleCache.Remove(item);
341 /// On scroll event callback.
343 /// <since_tizen> 9 </since_tizen>
344 protected virtual void OnScrolling(object source, ScrollEventArgs args)
346 if (args == null) throw new ArgumentNullException(nameof(args));
347 if (!disposed && InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null)
349 //Console.WriteLine("[NUI] On Scrolling! {0} => {1}", ScrollPosition.Y, args.Position.Y);
350 InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
355 /// Dispose ItemsView and all children on it.
357 /// <param name="type">Dispose type.</param>
358 [EditorBrowsable(EditorBrowsableState.Never)]
359 protected override void Dispose(DisposeTypes type)
366 if (type == DisposeTypes.Explicit)
369 if (RecycleCache != null)
371 foreach (RecyclerViewItem item in RecycleCache)
373 UnrealizeItem(item, false);
375 RecycleCache.Clear();
377 InternalItemsLayouter?.Clear();
378 InternalItemsLayouter = null;
381 if (InternalItemSource != null)
383 InternalItemSource.Dispose();
384 InternalItemSource = null;
392 private void OnItemRelayout(object sender, EventArgs e)
394 //FIXME: we need to skip the first relayout and only call size changed when real size change happen.
395 //InternalItemsLayouter.NotifyItemSizeChanged((sender as ViewItem));
396 //InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
399 private void DecorateItem(RecyclerViewItem item, int index, object context)
402 item.ParentItemsView = this;
403 item.Template = (ItemTemplate as DataTemplateSelector)?.SelectDataTemplate(InternalItemSource.GetItem(index), this) ?? ItemTemplate;
404 item.BindingContext = context;
405 item.Relayout += OnItemRelayout;