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
31 /// ItemsSourceProperty
33 [EditorBrowsable(EditorBrowsableState.Never)]
34 public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(RecyclerView), null, propertyChanged: (bindable, oldValue, newValue) =>
36 var instance = bindable as RecyclerView;
39 throw new Exception("Bindable object is not RecyclerView.");
43 instance.InternalItemsSource = newValue as IEnumerable;
46 defaultValueCreator: (bindable) =>
48 var instance = bindable as RecyclerView;
51 throw new Exception("Bindable object is not RecyclerView.");
53 return instance.InternalItemsSource;
57 /// ItemTemplateProperty
59 [EditorBrowsable(EditorBrowsableState.Never)]
60 public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(RecyclerView), null, propertyChanged: (bindable, oldValue, newValue) =>
62 var instance = bindable as RecyclerView;
65 throw new Exception("Bindable object is not RecyclerView.");
69 instance.InternalItemTemplate = newValue as DataTemplate;
72 defaultValueCreator: (bindable) =>
74 var instance = bindable as RecyclerView;
77 throw new Exception("Bindable object is not RecyclerView.");
79 return instance.InternalItemTemplate;
85 /// <since_tizen> 9 </since_tizen>
86 public RecyclerView() : base()
88 Scrolling += OnScrolling;
92 /// Item's source data.
94 /// <since_tizen> 9 </since_tizen>
95 public virtual IEnumerable ItemsSource
99 return GetValue(ItemsSourceProperty) as IEnumerable;
103 SetValue(ItemsSourceProperty, value);
104 NotifyPropertyChanged();
107 internal virtual IEnumerable InternalItemsSource { get; set; }
110 /// DataTemplate for items.
112 /// <since_tizen> 9 </since_tizen>
113 public virtual DataTemplate ItemTemplate
117 return GetValue(ItemTemplateProperty) as DataTemplate;
121 SetValue(ItemTemplateProperty, value);
122 NotifyPropertyChanged();
125 internal virtual DataTemplate InternalItemTemplate { get; set; }
128 /// Internal encapsulated items data source.
130 internal IItemSource InternalItemSource { get; set; }
133 /// RecycleCache of ViewItem.
135 [EditorBrowsable(EditorBrowsableState.Never)]
136 protected List<RecyclerViewItem> RecycleCache { get; } = new List<RecyclerViewItem>();
139 /// Internal Items Layouter.
141 [EditorBrowsable(EditorBrowsableState.Never)]
142 protected virtual ItemsLayouter InternalItemsLayouter { get; set; }
145 /// Max size of RecycleCache. Default is 50.
147 [EditorBrowsable(EditorBrowsableState.Never)]
148 protected int CacheMax { get; set; } = 50;
151 /// <since_tizen> 9 </since_tizen>
152 public override void OnRelayout(Vector2 size, RelayoutContainer container)
154 //Console.WriteLine("[NUI] On ReLayout [{0} {0}]", size.X, size.Y);
155 base.OnRelayout(size, container);
156 if (InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null)
158 InternalItemsLayouter.Initialize(this);
159 InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y, true);
164 /// Notify Dataset is Changed.
166 [EditorBrowsable(EditorBrowsableState.Never)]
167 public virtual void NotifyDataSetChanged()
169 //Need to update view.
170 if (InternalItemsLayouter != null)
172 InternalItemsLayouter.NotifyDataSetChanged();
173 if (ScrollingDirection == Direction.Horizontal)
175 ContentContainer.SizeWidth =
176 InternalItemsLayouter.CalculateLayoutOrientationSize();
180 ContentContainer.SizeHeight =
181 InternalItemsLayouter.CalculateLayoutOrientationSize();
187 /// Notify observable item is changed.
189 /// <param name="source">Dataset source.</param>
190 /// <param name="startIndex">Changed item index.</param>
191 [EditorBrowsable(EditorBrowsableState.Never)]
192 public virtual void NotifyItemChanged(IItemSource source, int startIndex)
194 if (InternalItemsLayouter != null)
196 InternalItemsLayouter.NotifyItemChanged(source, startIndex);
201 /// Notify range of observable items from start to end are changed.
203 /// <param name="source">Dataset source.</param>
204 /// <param name="startRange">Start index of changed items range.</param>
205 /// <param name="endRange">End index of changed items range.</param>
206 [EditorBrowsable(EditorBrowsableState.Never)]
207 public virtual void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
209 if (InternalItemsLayouter != null)
211 InternalItemsLayouter.NotifyItemRangeChanged(source, startRange, endRange);
216 /// Notify observable item is inserted in dataset.
218 /// <param name="source">Dataset source.</param>
219 /// <param name="startIndex">Inserted item index.</param>
220 [EditorBrowsable(EditorBrowsableState.Never)]
221 public virtual void NotifyItemInserted(IItemSource source, int startIndex)
223 if (InternalItemsLayouter != null)
225 InternalItemsLayouter.NotifyItemInserted(source, startIndex);
230 /// Notify count range of observable count items are inserted in startIndex.
232 /// <param name="source">Dataset source.</param>
233 /// <param name="startIndex">Start index of inserted items range.</param>
234 /// <param name="count">The number of inserted items.</param>
235 [EditorBrowsable(EditorBrowsableState.Never)]
236 public virtual void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
238 if (InternalItemsLayouter != null)
240 InternalItemsLayouter.NotifyItemRangeInserted(source, startIndex, count);
245 /// Notify observable item is moved from fromPosition to ToPosition.
247 /// <param name="source">Dataset source.</param>
248 /// <param name="fromPosition">Previous item position.</param>
249 /// <param name="toPosition">Moved item position.</param>
250 [EditorBrowsable(EditorBrowsableState.Never)]
251 public virtual void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
253 if (InternalItemsLayouter != null)
255 InternalItemsLayouter.NotifyItemMoved(source, fromPosition, toPosition);
260 /// Notify the observable item is moved from fromPosition to ToPosition.
262 /// <param name="source"></param>
263 /// <param name="fromPosition"></param>
264 /// <param name="toPosition"></param>
265 /// <param name="count"></param>
266 [EditorBrowsable(EditorBrowsableState.Never)]
267 public virtual void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
269 if (InternalItemsLayouter != null)
271 InternalItemsLayouter.NotifyItemRangeMoved(source, fromPosition, toPosition, count);
276 /// Notify the observable item in startIndex is removed.
278 /// <param name="source">Dataset source.</param>
279 /// <param name="startIndex">Index of removed item.</param>
280 [EditorBrowsable(EditorBrowsableState.Never)]
281 public virtual void NotifyItemRemoved(IItemSource source, int startIndex)
283 if (InternalItemsLayouter != null)
285 InternalItemsLayouter.NotifyItemRemoved(source, startIndex);
290 /// Notify the count range of observable items from the startIndex are removed.
292 /// <param name="source">Dataset source.</param>
293 /// <param name="startIndex">Start index of removed items range.</param>
294 /// <param name="count">The number of removed items</param>
295 [EditorBrowsable(EditorBrowsableState.Never)]
296 public virtual void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
298 if (InternalItemsLayouter != null)
300 InternalItemsLayouter.NotifyItemRangeRemoved(source, startIndex, count);
305 /// Realize indexed item.
307 /// <param name="index"> Index position of realizing item </param>
308 internal virtual RecyclerViewItem RealizeItem(int index)
310 object context = InternalItemSource.GetItem(index);
311 // Check DataTemplate is Same!
312 if (ItemTemplate is DataTemplateSelector)
314 // Need to implements for caching of selector!
319 RecyclerViewItem item = PopRecycleCache(ItemTemplate);
322 DecorateItem(item, index, context);
327 object content = DataTemplateExtensions.CreateContent(ItemTemplate, context, (BindableObject)this) ?? throw new Exception("Template return null object.");
328 if (content is RecyclerViewItem)
330 RecyclerViewItem item = (RecyclerViewItem)content;
331 ContentContainer.Add(item);
332 DecorateItem(item, index, context);
337 throw new Exception("Template content must be type of ViewItem");
343 /// Unrealize indexed item.
345 /// <param name="item"> Target item for unrealizing </param>
346 /// <param name="recycle"> Allow recycle. default is true </param>
347 internal virtual void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
355 item.ParentItemsView = null;
356 item.BindingContext = null;
357 item.IsPressed = false;
358 item.IsSelected = false;
359 item.IsEnabled = true;
361 item.Relayout -= OnItemRelayout;
363 if (!recycle || !PushRecycleCache(item))
365 //ContentContainer.Remove(item);
366 Utility.Dispose(item);
371 /// Adjust scrolling position by own scrolling rules.
372 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
374 /// <param name="position">Scroll position which is calculated by ScrollableBase.</param>
375 /// <returns>Adjusted scroll destination</returns>
376 [EditorBrowsable(EditorBrowsableState.Never)]
377 protected override float AdjustTargetPositionOfScrollAnimation(float position)
379 // Destination is depending on implementation of layout manager.
380 // Get destination from layout manager.
381 return InternalItemsLayouter.CalculateCandidateScrollPosition(position);
385 /// Push the item into the recycle cache. this item will be reused in view update.
387 /// <param name="item"> Target item to push into recycle cache. </param>
388 [EditorBrowsable(EditorBrowsableState.Never)]
389 protected virtual bool PushRecycleCache(RecyclerViewItem item)
393 throw new ArgumentNullException(nameof(item));
396 if (item.Template == null || RecycleCache.Count >= CacheMax)
403 RecycleCache.Add(item);
409 /// Pop the item from the recycle cache.
411 /// <param name="Template"> Template of wanted item. </param>
412 [EditorBrowsable(EditorBrowsableState.Never)]
413 protected virtual RecyclerViewItem PopRecycleCache(DataTemplate Template)
415 for (int i = 0; i < RecycleCache.Count; i++)
417 RecyclerViewItem item = RecycleCache[i];
418 if (item.Template == Template)
420 RecycleCache.Remove(item);
429 /// Clear all remaining caches.
431 [EditorBrowsable(EditorBrowsableState.Never)]
432 protected virtual void ClearCache()
434 foreach (RecyclerViewItem item in RecycleCache)
436 Utility.Dispose(item);
438 RecycleCache.Clear();
442 /// On scroll event callback.
444 /// <since_tizen> 9 </since_tizen>
445 protected virtual void OnScrolling(object source, ScrollEventArgs args)
449 throw new ArgumentNullException(nameof(args));
452 if (!disposed && InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null)
454 //Console.WriteLine("[NUI] On Scrolling! {0} => {1}", ScrollPosition.Y, args.Position.Y);
455 InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
460 /// Dispose ItemsView and all children on it.
462 /// <param name="type">Dispose type.</param>
463 [EditorBrowsable(EditorBrowsableState.Never)]
464 protected override void Dispose(DisposeTypes type)
471 if (type == DisposeTypes.Explicit)
474 if (RecycleCache != null)
478 InternalItemsLayouter?.Clear();
479 InternalItemsLayouter = null;
482 if (InternalItemSource != null)
484 InternalItemSource.Dispose();
485 InternalItemSource = null;
493 private void OnItemRelayout(object sender, EventArgs e)
495 //FIXME: we need to skip the first relayout and only call size changed when real size change happen.
496 //InternalItemsLayouter.NotifyItemSizeChanged((sender as ViewItem));
497 //InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
500 private void DecorateItem(RecyclerViewItem item, int index, object context)
503 item.ParentItemsView = this;
504 item.Template = (ItemTemplate as DataTemplateSelector)?.SelectDataTemplate(InternalItemSource.GetItem(index), this) ?? ItemTemplate;
505 item.BindingContext = context;
506 item.Relayout += OnItemRelayout;