{
var app = new MainApplication();
FormsMaps.Init("HERE", "write-your-API-key-here");
+ global::Xamarin.Forms.Platform.Tizen.Forms.SetFlags("CollectionView_Experimental", "Shell_Experimental");
global::Xamarin.Forms.Platform.Tizen.Forms.Init(app);
FormsMaterial.Init();
app.Run(args);
using System;
+using System.Linq;
using System.Collections.Specialized;
+using System.Collections.Generic;
using ElmSharp;
using EBox = ElmSharp.Box;
using EScroller = ElmSharp.Scroller;
using ESize = ElmSharp.Size;
using EPoint = ElmSharp.Point;
-using System.Collections.Generic;
namespace Xamarin.Forms.Platform.Tizen.Native
{
ICollectionViewLayoutManager _layoutManager;
ItemAdaptor _adaptor;
EBox _innerLayout;
+ EvasObject _emptyView;
Dictionary<ViewHolder, int> _viewHolderIndexTable = new Dictionary<ViewHolder, int>();
ViewHolder _lastSelectedViewHolder;
{
OnAdaptorChanging();
_adaptor = value;
- _adaptor.CollectionView = this;
OnAdaptorChanged();
}
}
- int ICollectionViewController.Count => Adaptor?.Count ?? 0;
+ int ICollectionViewController.Count
+ {
+ get
+ {
+ if (Adaptor == null || Adaptor is IEmptyAdaptor)
+ return 0;
+ return Adaptor.Count;
+ }
+ }
EPoint ICollectionViewController.ParentPosition => new EPoint
{
ScrollTo(Adaptor.GetItemIndex(item), position, animate);
}
- void ICollectionViewController.RequestLayoutItems() => RequestLayoutItems();
+ public void ItemMeasureInvalidated(int index)
+ {
+ LayoutManager?.ItemMeasureInvalidated(index);
+ }
+ void ICollectionViewController.RequestLayoutItems() => RequestLayoutItems();
ESize ICollectionViewController.GetItemSize()
{
+ return (this as ICollectionViewController).GetItemSize(LayoutManager.IsHorizontal ? AllocatedSize.Width * 100 : AllocatedSize.Width, LayoutManager.IsHorizontal ? AllocatedSize.Height : AllocatedSize.Height * 100);
+ }
+
+ ESize ICollectionViewController.GetItemSize(int widthConstraint, int heightConstraint)
+ {
if (Adaptor == null)
{
return new ESize(0, 0);
}
+
if (_itemSize.Width > 0 && _itemSize.Height > 0)
{
return _itemSize;
}
- _itemSize = Adaptor.MeasureItem(AllocatedSize.Width, AllocatedSize.Height);
+ _itemSize = Adaptor.MeasureItem(widthConstraint, heightConstraint);
_itemSize.Width = Math.Max(_itemSize.Width, 10);
_itemSize.Height = Math.Max(_itemSize.Height, 10);
return _itemSize;
}
+ ESize ICollectionViewController.GetItemSize(int index, int widthConstraint, int heightConstraint)
+ {
+ if (Adaptor == null)
+ {
+ return new ESize(0, 0);
+ }
+ return Adaptor.MeasureItem(index, widthConstraint, heightConstraint);
+ }
+
ViewHolder ICollectionViewController.RealizeView(int index)
{
if (Adaptor == null)
return null;
- var holder = _pool.GetRecyclerView();
+ var holder = _pool.GetRecyclerView(Adaptor.GetViewCategory(index));
if (holder != null)
{
holder.Show();
}
else
{
- var content = Adaptor.CreateNativeView(this);
+ var content = Adaptor.CreateNativeView(index, this);
holder = new ViewHolder(this);
holder.RequestSelected += OnRequestItemSelection;
holder.Content = content;
+ holder.ViewCategory = Adaptor.GetViewCategory(index);
_innerLayout.PackEnd(holder);
}
void ICollectionViewController.UnrealizeView(ViewHolder view)
{
_viewHolderIndexTable.Remove(view);
+ Adaptor.UnBinding(view.Content);
view.ResetState();
view.Hide();
_pool.AddRecyclerView(view);
+ if (_lastSelectedViewHolder == view)
+ {
+ _lastSelectedViewHolder = null;
+ }
+ }
+
+ void ICollectionViewController.ContentSizeUpdated()
+ {
+ OnInnerLayout();
}
protected virtual EScroller CreateScroller(EvasObject parent)
}
}
-
void OnLayoutManagerChanging()
{
_layoutManager?.Reset();
if (_layoutManager == null)
return;
+ _itemSize = new ESize(-1, -1);
_layoutManager.CollectionView = this;
_layoutManager.SizeAllocated(AllocatedSize);
RequestLayoutItems();
void OnAdaptorChanging()
{
+ if (Adaptor is IEmptyAdaptor)
+ {
+ RemoveEmptyView();
+ }
+
_layoutManager?.Reset();
if (Adaptor != null)
{
return;
_itemSize = new ESize(-1, -1);
+ Adaptor.CollectionView = this;
(Adaptor as INotifyCollectionChanged).CollectionChanged += OnCollectionChanged;
-
+
+ LayoutManager?.ItemSourceUpdated();
RequestLayoutItems();
- if (LayoutManager != null)
+ if (Adaptor is IEmptyAdaptor)
{
- var itemSize = (this as ICollectionViewController).GetItemSize();
+ CreateEmptyView();
}
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
+ // CollectionChanged could be called when Apaptor was changed on CollectionChanged event
+ if (Adaptor is IEmptyAdaptor)
+ {
+ return;
+ }
+
if (e.Action == NotifyCollectionChangedAction.Add)
{
int idx = e.NewStartingIndex;
+ if (idx == -1)
+ {
+ idx = Adaptor.Count - e.NewItems.Count;
+ }
foreach (var item in e.NewItems)
{
+ foreach (var viewHolder in _viewHolderIndexTable.Keys.ToList())
+ {
+ if (_viewHolderIndexTable[viewHolder] >= idx)
+ {
+ _viewHolderIndexTable[viewHolder]++;
+ }
+ }
LayoutManager.ItemInserted(idx++);
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
int idx = e.OldStartingIndex;
- foreach (var item in e.OldItems)
+
+ // Can't tracking remove if there is no data of old index
+ if (idx == -1)
+ {
+ LayoutManager.ItemSourceUpdated();
+ }
+ else
{
- LayoutManager.ItemRemoved(idx);
+ foreach (var item in e.OldItems)
+ {
+ LayoutManager.ItemRemoved(idx);
+ foreach (var viewHolder in _viewHolderIndexTable.Keys.ToList())
+ {
+ if (_viewHolderIndexTable[viewHolder] > idx)
+ {
+ _viewHolderIndexTable[viewHolder]--;
+ }
+ }
+ }
}
}
else if (e.Action == NotifyCollectionChangedAction.Move)
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
- LayoutManager.ItemUpdated(e.NewStartingIndex);
+ // Can't tracking if there is no information old data
+ if (e.OldItems.Count > 1 || e.NewStartingIndex == -1)
+ {
+ LayoutManager.ItemSourceUpdated();
+ }
+ else
+ {
+ LayoutManager.ItemUpdated(e.NewStartingIndex);
+ }
}
else if (e.Action == NotifyCollectionChangedAction.Reset)
{
LayoutManager.Reset();
+ LayoutManager.ItemSourceUpdated();
}
RequestLayoutItems();
}
void OnInnerLayout()
{
+ // OnInnerLayout was called when child item was added
+ // so, need to check scroll canvas size
var size = _layoutManager.GetScrollCanvasSize();
_innerLayout.MinimumWidth = size.Width;
_innerLayout.MinimumHeight = size.Height;
}
Scroller.SetPageSize(itemSize.Width, itemSize.Height);
}
+
+ void CreateEmptyView()
+ {
+ _emptyView = Adaptor.CreateNativeView(this);
+ _emptyView.Show();
+ Adaptor.SetBinding(_emptyView, 0);
+ _emptyView.Geometry = Geometry;
+ _emptyView.MinimumHeight = Geometry.Height;
+ _emptyView.MinimumWidth = Geometry.Width;
+ Scroller.SetContent(_emptyView, true);
+ _innerLayout.Hide();
+ }
+
+ void RemoveEmptyView()
+ {
+ _innerLayout.Show();
+ Scroller.SetContent(_innerLayout, true);
+ Adaptor.RemoveNativeView(_emptyView);
+ _emptyView = null;
+ }
}
public interface ICollectionViewController
int Count { get; }
ESize GetItemSize();
+
+ ESize GetItemSize(int widthConstraint, int heightConstraint);
+
+ ESize GetItemSize(int index, int widthConstraint, int heightConstraint);
+
+ void ContentSizeUpdated();
}
public enum CollectionViewSelectionMode
None,
Single,
}
-
}
namespace Xamarin.Forms.Platform.Tizen.Native
{
- public class EmptyItemAdaptor : ItemTemplateAdaptor
+ public class EmptyItemAdaptor : ItemTemplateAdaptor, IEmptyAdaptor
{
static DataTemplate s_defaultEmptyTemplate = new DataTemplate(typeof(EmptyView));
public EmptyItemAdaptor(ItemsView itemsView, IEnumerable items, DataTemplate template) : base(itemsView, items, template)
Rect _last;
Dictionary<int, RealizedItem> _realizedItem = new Dictionary<int, RealizedItem>();
- public GridLayoutManager(bool isHorizontal, int span = 1)
+ List<int> _itemSizes;
+ List<bool> _cached;
+ List<int> _accumulatedItemSizes;
+ bool _hasUnevenRows;
+ int _baseItemSize;
+
+ public GridLayoutManager(bool isHorizontal, int span = 1) : this(isHorizontal, span, ItemSizingStrategy.MeasureFirstItem) { }
+
+ public GridLayoutManager(bool isHorizontal, int span, ItemSizingStrategy sizingStrategy)
{
IsHorizontal = isHorizontal;
Span = span;
+ _hasUnevenRows = sizingStrategy == ItemSizingStrategy.MeasureAllItems;
}
- public int Span { get; internal set; }
+ public int Span { get; private set; }
public bool IsHorizontal { get; }
{
Reset();
_allocatedSize = size;
- _scrollCanvasSize = new ESize(0, 0);
+ InitializeMeasureCache();
}
public ESize GetScrollCanvasSize()
{
+ if (CollectionView.Count == 0 || _allocatedSize.Width <= 0 || _allocatedSize.Height <= 0)
+ return _allocatedSize;
+
if (_scrollCanvasSize.Width > 0 && _scrollCanvasSize.Height > 0)
return _scrollCanvasSize;
- var itemCount = CollectionView.Count;
- var itemSize = CollectionView.GetItemSize();
+ int totalItemSize = 0;
+ if (_hasUnevenRows)
+ {
+ totalItemSize = _accumulatedItemSizes[_accumulatedItemSizes.Count - 1];
+ }
+ else
+ {
+ totalItemSize = (int)Math.Ceiling(CollectionView.Count / (double)Span) * BaseItemSize;
+ }
+
if (IsHorizontal)
{
- return _scrollCanvasSize = new ESize((int)Math.Ceiling(itemCount / (double)Span) * itemSize.Width , _allocatedSize.Height);
+ _scrollCanvasSize = new ESize(totalItemSize , _allocatedSize.Height);
}
else
{
- return _scrollCanvasSize = new ESize(_allocatedSize.Width, (int)Math.Ceiling(itemCount / (double)Span) * itemSize.Height);
+ _scrollCanvasSize = new ESize(_allocatedSize.Width, totalItemSize);
+ }
+
+ return _scrollCanvasSize;
+ }
+
+ int BaseItemSize
+ {
+ get
+ {
+ if (_baseItemSize == 0)
+ {
+ if (_allocatedSize.Width <= 0 || _allocatedSize.Height <= 0)
+ return 0;
+
+ var itembound = CollectionView.GetItemSize(ItemWidthConstraint, ItemHeightConstraint);
+ _baseItemSize = IsHorizontal ? itembound.Width : itembound.Height;
+ }
+ return _baseItemSize;
+ }
+ }
+
+ int ItemWidthConstraint => IsHorizontal ? _allocatedSize.Width * 100 : ColumnSize;
+ int ItemHeightConstraint => IsHorizontal ? ColumnSize : _allocatedSize.Height * 100;
+
+ int ColumnSize
+ {
+ get
+ {
+ return IsHorizontal ? _allocatedSize.Height / Span : _allocatedSize.Width / Span;
}
}
return true;
var diff = IsHorizontal ? Math.Abs(_last.X - viewport.X) : Math.Abs(_last.Y - viewport.Y);
- var margin = IsHorizontal ? CollectionView.GetItemSize().Width : CollectionView.GetItemSize().Height;
- if (diff > margin)
+ if (diff > BaseItemSize)
return true;
return false;
}
_isLayouting = true;
_last = bound;
-
- var size = CollectionView.GetItemSize();
- var itemSize = IsHorizontal ? size.Width : size.Height;
-
+
int padding = Span * 2;
- int startIndex = Math.Max(GetStartIndex(bound, itemSize) - padding, 0);
- int endIndex = Math.Min(GetEndIndex(bound, itemSize) + padding, CollectionView.Count - 1);
+ int startIndex = Math.Max(GetStartIndex(bound) - padding, 0);
+ int endIndex = Math.Min(GetEndIndex(bound) + padding, CollectionView.Count - 1);
foreach (var index in _realizedItem.Keys.ToList())
{
public void UpdateSpan(int span)
{
Span = span;
- _scrollCanvasSize = new ESize(0, 0);
+ InitializeMeasureCache();
CollectionView.RequestLayoutItems();
}
}
}
+ UpdateInsertedSize(inserted);
+
_scrollCanvasSize = new ESize(0, 0);
}
_realizedItem.Remove(last);
}
+ UpdateRemovedSize(removed);
_scrollCanvasSize = new ESize(0, 0);
}
public Rect GetItemBound(int index)
{
- var size = CollectionView.GetItemSize();
- if (IsHorizontal)
+ int rowIndex = index / Span;
+ int columnIndex = index % Span;
+ var columnSize = ColumnSize;
+
+ int rowStartPoint = 0;
+ int columnStartPoint = 0;
+ int itemSize = 0;
+
+ if (!_hasUnevenRows)
{
- size.Height = _allocatedSize.Height / Span;
+ itemSize = BaseItemSize;
+ rowStartPoint = rowIndex * BaseItemSize;
+ columnStartPoint = columnIndex * columnSize;
}
- else
+ else if (_cached[index])
{
- size.Width = _allocatedSize.Width / Span;
+ var updatedMaxItemSize = GetMaxItemSize(index);
+ itemSize = _itemSizes[index];
+ rowStartPoint = _accumulatedItemSizes[rowIndex] - updatedMaxItemSize + (updatedMaxItemSize - itemSize) / 2;
+ columnStartPoint = columnSize * columnIndex;
}
+ else
+ {
+ var oldMaxItemSize = GetMaxItemSize(index);
- int rowIndex = index / Span;
- int colIndex = index % Span;
- var colSize = IsHorizontal ? size.Height : size.Width;
+ var measured = CollectionView.GetItemSize(index, ItemWidthConstraint, ItemHeightConstraint);
+ itemSize = IsHorizontal ? measured.Width : measured.Height;
- return
- IsHorizontal ?
- new Rect(rowIndex * size.Width, colIndex * size.Height, size.Width, size.Height) :
- new Rect(colIndex * size.Width, rowIndex * size.Height, size.Width, size.Height);
+ if (itemSize != _itemSizes[index])
+ {
+ _itemSizes[index] = itemSize;
+ }
+
+ var updatedMaxItemSize = GetMaxItemSize(index);
+ if (oldMaxItemSize != updatedMaxItemSize)
+ {
+ UpdateAccumulatedItemSize(rowIndex, updatedMaxItemSize - oldMaxItemSize);
+ int columnStart = (index / Span) * Span;
+ for (int toUpdate = columnStart; toUpdate < index; toUpdate++)
+ {
+ if (_realizedItem.ContainsKey(toUpdate))
+ {
+ var updated = _realizedItem[toUpdate].View.Geometry;
+ if (IsHorizontal)
+ {
+ updated.X += (updatedMaxItemSize - oldMaxItemSize) / 2;
+ }
+ else
+ {
+ updated.Y += (updatedMaxItemSize - oldMaxItemSize) / 2;
+ }
+ _realizedItem[toUpdate].View.Geometry = updated;
+ }
+ }
+ CollectionView.ContentSizeUpdated();
+ }
+ rowStartPoint = _accumulatedItemSizes[rowIndex] - updatedMaxItemSize + (updatedMaxItemSize - itemSize) / 2;
+ columnStartPoint = columnSize * columnIndex;
+
+ _cached[index] = true;
+ }
+
+ return IsHorizontal ?
+ new Rect(rowStartPoint, columnStartPoint, itemSize, columnSize) :
+ new Rect(columnStartPoint, rowStartPoint, columnSize, itemSize);
}
public void Reset()
{
- foreach (var realizedItem in _realizedItem.Values)
+ foreach (var realizedItem in _realizedItem.Values.ToList())
{
CollectionView.UnrealizeView(realizedItem.View);
}
_scrollCanvasSize = new ESize(0, 0);
}
+ public void ItemSourceUpdated()
+ {
+ InitializeMeasureCache();
+ }
+
+ public void ItemMeasureInvalidated(int index)
+ {
+ if (_realizedItem.ContainsKey(index))
+ {
+ CollectionView.RequestLayoutItems();
+ }
+ if (_hasUnevenRows)
+ {
+ if (_cached.Count > index)
+ _cached[index] = false;
+ }
+ }
+
+ void InitializeMeasureCache()
+ {
+ _baseItemSize = 0;
+ _scrollCanvasSize = new ESize(0, 0);
+ _last = new Rect(0, 0, 0, 0);
+
+ if (!_hasUnevenRows)
+ return;
+
+ if (_allocatedSize.Width <= 0 || _allocatedSize.Height <= 0)
+ return;
+
+ int n = CollectionView.Count;
+ _itemSizes = new List<int>();
+ _cached = new List<bool>();
+ _accumulatedItemSizes = new List<int>();
+
+ for (int i = 0; i < n; i++)
+ {
+ _cached.Add(false);
+ _itemSizes.Add(BaseItemSize);
+ if (i % Span == 0)
+ {
+ int accIndex = i / Span;
+ _accumulatedItemSizes.Add((accIndex > 0 ? _accumulatedItemSizes[accIndex - 1] : 0) + _itemSizes[i]);
+ }
+ }
+ }
+
+ void BuildAccumulatedSize()
+ {
+ _accumulatedItemSizes = new List<int>();
+ int n = _itemSizes.Count;
+ for (int i = 0; i < n; i++)
+ {
+ int accIndex = i / Span;
+ int prevSize = accIndex > 0 ? _accumulatedItemSizes[accIndex - 1] : 0;
+ if (i % Span == 0)
+ {
+ _accumulatedItemSizes.Add(prevSize);
+ }
+ int columnMax = _accumulatedItemSizes[accIndex] - prevSize;
+ if (columnMax < _itemSizes[i])
+ {
+ _accumulatedItemSizes[accIndex] += (_itemSizes[i] - columnMax);
+ }
+ }
+ }
+
+ void UpdateInsertedSize(int inserted)
+ {
+ if (!_hasUnevenRows)
+ return;
+
+ _cached.Insert(inserted, false);
+ _itemSizes.Insert(inserted, BaseItemSize);
+ BuildAccumulatedSize();
+ }
+
+ void UpdateRemovedSize(int removed)
+ {
+ if (!_hasUnevenRows)
+ return;
+
+ _itemSizes.RemoveAt(removed);
+ _cached.RemoveAt(removed);
+ BuildAccumulatedSize();
+ }
+
+ void UpdateAccumulatedItemSize(int index, int diff)
+ {
+ for (int i = index; i < _accumulatedItemSizes.Count; i++)
+ {
+ _accumulatedItemSizes[i] += diff;
+ }
+
+ if (_scrollCanvasSize.Width > 0 && _scrollCanvasSize.Height > 0)
+ {
+ if (IsHorizontal)
+ {
+ _scrollCanvasSize.Width += diff;
+ }
+ else
+ {
+ _scrollCanvasSize.Height += diff;
+ }
+ }
+ }
+
+ int GetMaxItemSize(int index)
+ {
+ int columnStart = (index / Span) * Span;
+ int columnEnd = columnStart + Span - 1;
+ int max = 0;
+ for (int i = columnStart; i <= columnEnd && i < _itemSizes.Count; i++)
+ {
+ max = Math.Max(max, _itemSizes[i]);
+ }
+ return max;
+ }
+
+
int GetStartIndex(Rect bound, int itemSize)
{
return ViewPortStartPoint(bound) / itemSize * Span;
}
+ int GetStartIndex(Rect bound)
+ {
+ if (!_hasUnevenRows)
+ {
+ return GetStartIndex(bound, BaseItemSize);
+ }
+
+ return FindFirstGreaterOrEqualTo(_accumulatedItemSizes, ViewPortStartPoint(bound)) * Span;
+ }
+
int GetEndIndex(Rect bound, int itemSize)
{
return (int)Math.Ceiling(ViewPortEndPoint(bound) / (double)itemSize) * Span;
}
+ int GetEndIndex(Rect bound)
+ {
+ if (!_hasUnevenRows)
+ {
+ return GetEndIndex(bound, BaseItemSize);
+ }
+
+ return FindFirstGreaterOrEqualTo(_accumulatedItemSizes, ViewPortEndPoint(bound)) * Span;
+ }
+
int ViewPortStartPoint(Rect viewPort)
{
return IsHorizontal ? viewPort.X : viewPort.Y;
return IsHorizontal ? viewPort.Width : viewPort.Height;
}
+ static int FindFirstGreaterOrEqualTo(IList<int> data, int value)
+ {
+ if (data.Count == 0)
+ return 0;
+
+ int start = 0;
+ int end = data.Count - 1;
+ while (start < end)
+ {
+ int mid = (start + end) / 2;
+ if (data[mid] < value)
+ {
+ start = mid + 1;
+ }
+ else
+ {
+ end = mid - 1;
+ }
+ }
+ if (data[start] < value)
+ {
+ start++;
+ }
+ return start;
+ }
+
class RealizedItem
{
public ViewHolder View { get; set; }
void ItemUpdated(int index);
+ void ItemSourceUpdated();
+
void Reset();
+
+ void ItemMeasureInvalidated(int index);
}
}
namespace Xamarin.Forms.Platform.Tizen.Native
{
+ public interface IEmptyAdaptor { }
public abstract class ItemAdaptor : INotifyCollectionChanged
{
return _itemsSource.IndexOf(item);
}
+ public virtual object GetViewCategory(int index)
+ {
+ return this;
+ }
+
public abstract EvasObject CreateNativeView(EvasObject parent);
+ public abstract EvasObject CreateNativeView(int index, EvasObject parent);
+
public abstract void RemoveNativeView(EvasObject native);
public abstract void SetBinding(EvasObject view, int index);
+ public abstract void UnBinding(EvasObject view);
public abstract ESize MeasureItem(int widthConstraint, int heightConstraint);
+
+ public abstract ESize MeasureItem(int index, int widthConstraint, int heightConstraint);
}
}
+using System;
using System.Collections;
using System.Collections.Generic;
+using System.Globalization;
using ElmSharp;
using ESize = ElmSharp.Size;
using XLabel = Xamarin.Forms.Label;
{
public class ItemDefaultTemplateAdaptor : ItemTemplateAdaptor
{
+ class ToTextConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value?.ToString() ?? string.Empty;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
+ }
+
public ItemDefaultTemplateAdaptor(ItemsView itemsView) : base(itemsView)
{
ItemTemplate = new DataTemplate(() =>
{
+ var label = new XLabel
+ {
+ TextColor = Color.Black,
+ };
+ label.SetBinding(XLabel.TextProperty, new Binding(".", converter: new ToTextConverter()));
+
return new StackLayout
{
BackgroundColor = Color.White,
Padding = 30,
Children =
{
- new XLabel()
+ label
}
};
});
}
- public override void SetBinding(EvasObject native, int index)
- {
- ((GetTemplatedView(native) as StackLayout).Children[0] as XLabel).Text = this[index].ToString();
- }
-
- public override ESize MeasureItem(int widthConstraint, int heightConstraint)
- {
- var view = (View)ItemTemplate.CreateContent();
- if (Count > 0)
- {
- ((view as StackLayout).Children[0] as XLabel).Text = this[0].ToString();
- }
- var renderer = Platform.GetOrCreateRenderer(view);
- var request = view.Measure(Forms.ConvertToScaledDP(widthConstraint), Forms.ConvertToScaledDP(heightConstraint), MeasureFlags.IncludeMargins).Request;
- renderer.Dispose();
- return request.ToPixel();
- }
}
public class ItemTemplateAdaptor : ItemAdaptor
{
Dictionary<EvasObject, View> _nativeFormsTable = new Dictionary<EvasObject, View>();
+ Dictionary<object, View> _dataBindedViewTable = new Dictionary<object, View>();
ItemsView _itemsView;
public ItemTemplateAdaptor(ItemsView itemsView) : base(itemsView.ItemsSource)
return _nativeFormsTable[evasObject];
}
- public override EvasObject CreateNativeView(EvasObject parent)
+ public override object GetViewCategory(int index)
{
- var view = ItemTemplate.CreateContent() as View;
+ if (ItemTemplate is DataTemplateSelector selector)
+ {
+ return selector.SelectTemplate(this[index], _itemsView);
+ }
+ return base.GetViewCategory(index);
+ }
+
+ public override EvasObject CreateNativeView(int index, EvasObject parent)
+ {
+ View view = null;
+ if (ItemTemplate is DataTemplateSelector selector)
+ {
+ view = selector.SelectTemplate(this[index], _itemsView).CreateContent() as View;
+ }
+ else
+ {
+ view = ItemTemplate.CreateContent() as View;
+ }
var renderer = Platform.GetOrCreateRenderer(view);
var native = Platform.GetOrCreateRenderer(view).NativeView;
view.Parent = _itemsView;
return native;
}
+ public override EvasObject CreateNativeView(EvasObject parent)
+ {
+ return CreateNativeView(0, parent);
+ }
+
public override void RemoveNativeView(EvasObject native)
{
if (_nativeFormsTable.TryGetValue(native, out View view))
{
+ ResetBindedView(view);
Platform.GetRenderer(view)?.Dispose();
_nativeFormsTable.Remove(native);
}
{
if (_nativeFormsTable.TryGetValue(native, out View view))
{
+ ResetBindedView(view);
view.BindingContext = this[index];
+ _dataBindedViewTable[this[index]] = view;
+
+ view.MeasureInvalidated += OnItemMeasureInvalidated;
+ }
+ }
+
+ public override void UnBinding(EvasObject native)
+ {
+ if (_nativeFormsTable.TryGetValue(native, out View view))
+ {
+ view.MeasureInvalidated -= OnItemMeasureInvalidated;
+ ResetBindedView(view);
}
}
public override ESize MeasureItem(int widthConstraint, int heightConstraint)
{
- var view = ItemTemplate.CreateContent() as View;
- var renderer = Platform.GetOrCreateRenderer(view);
- view.Parent = _itemsView;
- if (Count > 0)
- view.BindingContext = this[0];
- var request = view.Measure(Forms.ConvertToScaledDP(widthConstraint), Forms.ConvertToScaledDP(heightConstraint), MeasureFlags.IncludeMargins).Request;
- renderer.Dispose();
+ return MeasureItem(0, widthConstraint, heightConstraint);
+ }
+
+ public override ESize MeasureItem(int index, int widthConstraint, int heightConstraint)
+ {
+ if (_dataBindedViewTable.TryGetValue(this[index], out View createdView) && createdView != null)
+ {
+ return createdView.Measure(Forms.ConvertToScaledDP(widthConstraint), Forms.ConvertToScaledDP(heightConstraint), MeasureFlags.IncludeMargins).Request.ToPixel();
+ }
- return request.ToPixel();
+ View view = null;
+ if (ItemTemplate is DataTemplateSelector selector)
+ {
+ view = selector.SelectTemplate(this[index], _itemsView).CreateContent() as View;
+ }
+ else
+ {
+ view = ItemTemplate.CreateContent() as View;
+ }
+ using (var renderer = Platform.GetOrCreateRenderer(view))
+ {
+ view.Parent = _itemsView;
+ if (Count > index)
+ view.BindingContext = this[index];
+ var request = view.Measure(Forms.ConvertToScaledDP(widthConstraint), Forms.ConvertToScaledDP(heightConstraint), MeasureFlags.IncludeMargins).Request;
+ return request.ToPixel();
+ }
}
+ void ResetBindedView(View view)
+ {
+ if (view.BindingContext != null && _dataBindedViewTable.ContainsKey(view.BindingContext))
+ {
+ _dataBindedViewTable[view.BindingContext] = null;
+ }
+ }
+
+ void OnItemMeasureInvalidated(object sender, EventArgs e)
+ {
+ var data = (sender as View)?.BindingContext ?? null;
+ int index = GetItemIndex(data);
+ if (index != -1)
+ {
+ CollectionView.ItemMeasureInvalidated(index);
+ }
+ }
}
}
bool _isLayouting;
Rect _last;
Dictionary<int, RealizedItem> _realizedItem = new Dictionary<int, RealizedItem>();
+ List<int> _itemSizes;
+ List<bool> _cached;
+ List<int> _accumulatedItemSizes;
- public LinearLayoutManager(bool isHorizontal)
+ bool _hasUnevenRows;
+ int _baseItemSize;
+
+ public LinearLayoutManager(bool isHorizontal) : this(isHorizontal, ItemSizingStrategy.MeasureFirstItem) { }
+
+ public LinearLayoutManager(bool isHorizontal, ItemSizingStrategy sizingStrategy)
{
IsHorizontal = isHorizontal;
+ _hasUnevenRows = sizingStrategy == ItemSizingStrategy.MeasureAllItems;
}
public bool IsHorizontal { get; }
{
Reset();
_allocatedSize = size;
- _scrollCanvasSize = new ESize(0, 0);
+ InitializeMeasureCache();
}
ESize _scrollCanvasSize;
public ESize GetScrollCanvasSize()
{
+ if (CollectionView.Count == 0 || _allocatedSize.Width <= 0 || _allocatedSize.Height <= 0)
+ {
+ return _allocatedSize;
+ }
+
+
if (_scrollCanvasSize.Width > 0 && _scrollCanvasSize.Height > 0)
return _scrollCanvasSize;
- var itemCount = CollectionView.Count;
- var itemSize = CollectionView.GetItemSize();
+ int totalItemSize = 0;
+
+ if (_hasUnevenRows)
+ {
+ totalItemSize = _accumulatedItemSizes[_accumulatedItemSizes.Count - 1];
+ }
+ else
+ {
+ totalItemSize = BaseItemSize * CollectionView.Count;
+ }
+
if (IsHorizontal)
{
- return _scrollCanvasSize = new ESize(itemCount * itemSize.Width, _allocatedSize.Height);
+ _scrollCanvasSize = new ESize(totalItemSize, _allocatedSize.Height);
}
else
{
- return _scrollCanvasSize = new ESize(_allocatedSize.Width, itemCount * itemSize.Height);
+ _scrollCanvasSize = new ESize(_allocatedSize.Width, totalItemSize);
+ }
+
+ return _scrollCanvasSize;
+ }
+
+ int BaseItemSize
+ {
+ get
+ {
+ if (_baseItemSize == 0)
+ {
+ if (_allocatedSize.Width <= 0 || _allocatedSize.Height <= 0)
+ return 0;
+
+ var itemBound = CollectionView.GetItemSize(ItemWidthConstraint, ItemHeightConstraint);
+ _baseItemSize = IsHorizontal ? itemBound.Width : itemBound.Height;
+ }
+ return _baseItemSize;
}
}
+ int ItemWidthConstraint => IsHorizontal ? _allocatedSize.Width * 100 : _allocatedSize.Width;
+ int ItemHeightConstraint => IsHorizontal ? _allocatedSize.Height : _allocatedSize.Height * 100;
+
bool ShouldRearrange(Rect viewport)
{
if (_isLayouting)
return true;
var diff = IsHorizontal ? Math.Abs(_last.X - viewport.X) : Math.Abs(_last.Y - viewport.Y);
- var margin = IsHorizontal ? CollectionView.GetItemSize().Width : CollectionView.GetItemSize().Height;
- if (diff > margin)
+ if (diff > BaseItemSize)
return true;
return false;
{
return;
}
+
_isLayouting = true;
_last = bound;
- var size = CollectionView.GetItemSize();
- var itemSize = IsHorizontal ? size.Width : size.Height;
- int startIndex = Math.Max(GetStartIndex(bound, itemSize) - 2, 0);
- int endIndex = Math.Min(GetEndIndex(bound, itemSize) + 2, CollectionView.Count - 1);
+ int startIndex = Math.Max(GetStartIndex(bound) - 2, 0);
+ int endIndex = Math.Min(GetEndIndex(bound) + 2, CollectionView.Count - 1);
foreach (var index in _realizedItem.Keys.ToList())
{
-
if (index < startIndex || index > endIndex)
{
CollectionView.UnrealizeView(_realizedItem[index].View);
}
}
+ UpdateInsertedSize(inserted);
+
_scrollCanvasSize = new ESize(0, 0);
}
_realizedItem.Remove(last);
}
+ UpdateRemovedSize(removed);
+
_scrollCanvasSize = new ESize(0, 0);
}
public Rect GetItemBound(int index)
{
- var size = CollectionView.GetItemSize();
- if (IsHorizontal)
+ int itemSize = 0;
+ int startPoint = 0;
+
+ if (!_hasUnevenRows)
+ {
+ itemSize = BaseItemSize;
+ startPoint = itemSize * index;
+ }
+ else if (index >= _itemSizes.Count)
{
- size.Height = _allocatedSize.Height;
+ return new Rect(0, 0, 0, 0);
+ }
+ else if (_cached[index])
+ {
+ itemSize = _itemSizes[index];
+ startPoint = _accumulatedItemSizes[index] - itemSize;
}
else
{
- size.Width = _allocatedSize.Width;
+ var measured = CollectionView.GetItemSize(index, ItemWidthConstraint, ItemHeightConstraint);
+ itemSize = IsHorizontal ? measured.Width : measured.Height;
+
+ if (itemSize != _itemSizes[index])
+ {
+ UpdateAccumulatedItemSize(index, itemSize - _itemSizes[index]);
+ _itemSizes[index] = itemSize;
+
+ CollectionView.ContentSizeUpdated();
+ }
+ startPoint = _accumulatedItemSizes[index] - itemSize;
+ _cached[index] = true;
}
- return
- IsHorizontal ?
- new Rect(index * size.Width, 0, size.Width, size.Height) :
- new Rect(0, index * size.Height, size.Width, size.Height);
+
+ return IsHorizontal ?
+ new Rect(startPoint, 0, itemSize, _allocatedSize.Height) :
+ new Rect(0, startPoint, _allocatedSize.Width, itemSize);
}
public void Reset()
{
- foreach (var realizedItem in _realizedItem.Values)
+ foreach (var realizedItem in _realizedItem.Values.ToList())
{
CollectionView.UnrealizeView(realizedItem.View);
}
_scrollCanvasSize = new ESize(0, 0);
}
+ public void ItemSourceUpdated()
+ {
+ InitializeMeasureCache();
+ }
+
+ public void ItemMeasureInvalidated(int index)
+ {
+ if (_realizedItem.ContainsKey(index))
+ {
+ CollectionView.RequestLayoutItems();
+ }
+ if (_hasUnevenRows)
+ {
+ if (_cached.Count > index)
+ _cached[index] = false;
+ }
+ }
+
+ void InitializeMeasureCache()
+ {
+ _baseItemSize = 0;
+ _scrollCanvasSize = new ESize(0, 0);
+
+ if (!_hasUnevenRows)
+ return;
+
+ if (_allocatedSize.Width <= 0 || _allocatedSize.Height <= 0)
+ return;
+
+ int n = CollectionView.Count;
+ _itemSizes = new List<int>();
+ _cached = new List<bool>();
+ _accumulatedItemSizes = new List<int>();
+
+ for (int i = 0; i < n; i++)
+ {
+ _cached.Add(false);
+ _itemSizes.Add(BaseItemSize);
+ _accumulatedItemSizes.Add((i > 0 ? _accumulatedItemSizes[i - 1] : 0) + _itemSizes[i]);
+ }
+ }
+
int GetStartIndex(Rect bound, int itemSize)
{
return ViewPortStartPoint(bound) / itemSize;
}
+ int GetStartIndex(Rect bound)
+ {
+ if (!_hasUnevenRows)
+ {
+ return GetStartIndex(bound, BaseItemSize);
+ }
+
+ return FindFirstGreaterOrEqualTo(_accumulatedItemSizes, ViewPortStartPoint(bound));
+ }
+
int GetEndIndex(Rect bound, int itemSize)
{
return (int)Math.Ceiling(ViewPortEndPoint(bound) / (double)itemSize);
}
+ int GetEndIndex(Rect bound)
+ {
+ if (!_hasUnevenRows)
+ {
+ return GetEndIndex(bound, BaseItemSize);
+ }
+
+ return FindFirstGreaterOrEqualTo(_accumulatedItemSizes, ViewPortEndPoint(bound));
+ }
+
int ViewPortStartPoint(Rect viewPort)
{
return IsHorizontal ? viewPort.X : viewPort.Y;
return IsHorizontal ? viewPort.Width : viewPort.Height;
}
+ void UpdateAccumulatedItemSize(int index, int diff)
+ {
+ for (int i = index; i < _accumulatedItemSizes.Count; i++)
+ {
+ _accumulatedItemSizes[i] += diff;
+ }
+
+ if (_scrollCanvasSize.Width > 0 && _scrollCanvasSize.Height > 0)
+ {
+
+ if (IsHorizontal)
+ {
+ _scrollCanvasSize.Width += diff;
+ }
+ else
+ {
+ _scrollCanvasSize.Height += diff;
+ }
+ }
+ }
+
+ void UpdateRemovedSize(int removed)
+ {
+ if (!_hasUnevenRows)
+ return;
+ var removedSize = _itemSizes[removed];
+ _itemSizes.RemoveAt(removed);
+ UpdateAccumulatedItemSize(removed, -removedSize);
+ _accumulatedItemSizes.RemoveAt(removed);
+ _cached.RemoveAt(removed);
+ }
+
+ void UpdateInsertedSize(int inserted)
+ {
+ if (!_hasUnevenRows)
+ return;
+
+ _cached.Insert(inserted, false);
+ _itemSizes.Insert(inserted, BaseItemSize);
+ _accumulatedItemSizes.Insert(inserted, 0);
+ _accumulatedItemSizes[inserted] = inserted > 0 ? _accumulatedItemSizes[inserted - 1] : 0;
+ UpdateAccumulatedItemSize(inserted, BaseItemSize);
+ }
+
+ static int FindFirstGreaterOrEqualTo(IList<int> data, int value)
+ {
+ if (data.Count == 0)
+ return 0;
+
+ int start = 0;
+ int end = data.Count - 1;
+ while (start < end)
+ {
+ int mid = (start + end) / 2;
+ if (data[mid] < value)
+ {
+ start = mid + 1;
+ }
+ else
+ {
+ end = mid - 1;
+ }
+ }
+ if (data[start] < value)
+ {
+ start++;
+ }
+ return start;
+ }
+
class RealizedItem
{
public ViewHolder View { get; set; }
using System.Collections.Generic;
-using ElmSharp;
+using System.Linq;
namespace Xamarin.Forms.Platform.Tizen.Native
{
_pool.AddLast(view);
}
+ public ViewHolder GetRecyclerView(object category)
+ {
+ var holder = _pool.Where(d => d.ViewCategory == category).FirstOrDefault();
+ if (holder != null)
+ _pool.Remove(holder);
+ return holder;
+ }
+
public ViewHolder GetRecyclerView()
{
if (_pool.First != null)
Initialize(parent);
}
+ public object ViewCategory { get; set; }
public EColor FocusedColor { get; set; }
public EColor SelectedColor { get; set; }
namespace Xamarin.Forms.Platform.Tizen
{
- public class FrameRenderer : ViewRenderer<Frame, Native.Canvas>
+ public class FrameRenderer : LayoutRenderer
{
const int _thickness = 2;
const int _shadow_shift = 2;
RegisterPropertyHandler(Frame.HasShadowProperty, UpdateShadowVisibility);
}
- protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
+ protected override void OnElementChanged(ElementChangedEventArgs<Layout> e)
{
if (Control == null)
{
void UpdateColor()
{
- if (Element.BorderColor.IsDefault)
+ if ((Element as Frame).BorderColor.IsDefault)
_frame.Color = s_DefaultColor;
else
- _frame.Color = Element.BorderColor.ToNative();
+ _frame.Color = (Element as Frame).BorderColor.ToNative();
}
void UpdateShadowVisibility()
{
- if (Element.HasShadow)
+ if ((Element as Frame).HasShadow)
_shadow.Show();
else
_shadow.Hide();
RegisterPropertyHandler(ItemsView.ItemsSourceProperty, UpdateItemsSource);
RegisterPropertyHandler(ItemsView.ItemTemplateProperty, UpdateAdaptor);
RegisterPropertyHandler(ItemsView.ItemsLayoutProperty, UpdateItemsLayout);
+ RegisterPropertyHandler(ItemsView.ItemSizingStrategyProperty, UpdateSizingStrategy);
RegisterPropertyHandler(SelectableItemsView.SelectedItemProperty, UpdateSelectedItem);
RegisterPropertyHandler(SelectableItemsView.SelectionModeProperty, UpdateSelectionMode);
}
{
if (Element.ItemsLayout != null)
{
- Control.LayoutManager = Element.ItemsLayout.ToLayoutManager();
+ Control.LayoutManager = Element.ItemsLayout.ToLayoutManager(Element.ItemSizingStrategy);
Control.SnapPointsType = (Element.ItemsLayout as ItemsLayout)?.SnapPointsType ?? SnapPointsType.None;
Element.ItemsLayout.PropertyChanged += OnLayoutPropertyChanged;
}
}
+ void UpdateSizingStrategy(bool initialize)
+ {
+ if (initialize)
+ {
+ return;
+ }
+ Control.LayoutManager = Element.ItemsLayout.ToLayoutManager(Element.ItemSizingStrategy);
+ }
+
void OnLayoutPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ItemsLayout.SnapPointsType))
static class ItemsLayoutExtension
{
- public static ICollectionViewLayoutManager ToLayoutManager(this IItemsLayout layout)
+ public static ICollectionViewLayoutManager ToLayoutManager(this IItemsLayout layout, ItemSizingStrategy sizing = ItemSizingStrategy.MeasureFirstItem)
{
switch (layout)
{
case ListItemsLayout listItemsLayout:
- return new LinearLayoutManager(listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal);
+ return new LinearLayoutManager(listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal, sizing);
case GridItemsLayout gridItemsLayout:
- return new GridLayoutManager(gridItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal, gridItemsLayout.Span);
+ return new GridLayoutManager(gridItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal, gridItemsLayout.Span, sizing);
default:
break;
}