[Tizen] Add CollectionView Tizen Renderer (#5364) fixes #3172

* [Tizen] Add CollectionView Tizen Renderer (#259)

* Tizen CollectionView implementation initial commit

 - first working version
 - only support LinearLayout(listview)

* Add more feature

 * Implement INotifyCollectionChanged
 * Implement EmptyView

* Implemnt SnapPoints, ScrollTo

* Fix MakeVisible option

* Add GridLayoutManager (#271)

* Fix GetScrollCanvasSize issue

* Enhance ItemAdaptor

 - Measure Item size with data binding
  Item size is determined with first item
 - Extract ItemDefaultTemplateAdaptor from ItemTemplateAdaptor

* Remove debug message

* Implement SelectableItemsView

* Fix ghost view issue

* Fix TV profile button effect issue

* Clean up code space and refactoring ScrollTo method

* Fix collectionView emptyview (#277)
This commit is contained in:
Seungkeun Lee 2019-03-14 20:14:06 +09:00 коммит произвёл Rui Marinho
Родитель 409271bbfa
Коммит 07ada19a7d
12 изменённых файлов: 1670 добавлений и 2 удалений

Просмотреть файл

@ -0,0 +1,482 @@
using System;
using System.Collections.Specialized;
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
{
public class CollectionView : EBox, ICollectionViewController
{
RecyclerPool _pool = new RecyclerPool();
ICollectionViewLayoutManager _layoutManager;
ItemAdaptor _adaptor;
EBox _innerLayout;
Dictionary<ViewHolder, int> _viewHolderIndexTable = new Dictionary<ViewHolder, int>();
ViewHolder _lastSelectedViewHolder;
int _selectedItemIndex = -1;
CollectionViewSelectionMode _selectionMode = CollectionViewSelectionMode.None;
bool _requestLayoutItems = false;
SnapPointsType _snapPoints;
ESize _itemSize = new ESize(-1, -1);
public CollectionView(EvasObject parent) : base(parent)
{
SetLayoutCallback(OnLayout);
Scroller = CreateScroller(parent);
Scroller.Show();
PackEnd(Scroller);
Scroller.Scrolled += OnScrolled;
_innerLayout = new EBox(parent);
_innerLayout.SetLayoutCallback(OnInnerLayout);
_innerLayout.Show();
Scroller.SetContent(_innerLayout);
}
public CollectionViewSelectionMode SelectionMode
{
get => _selectionMode;
set
{
_selectionMode = value;
UpdateSelectionMode();
}
}
public int SelectedItemIndex
{
get => _selectedItemIndex;
set
{
if (_selectedItemIndex != value)
{
_selectedItemIndex = value;
UpdateSelectedItemIndex();
}
}
}
public SnapPointsType SnapPointsType
{
get => _snapPoints;
set
{
_snapPoints = value;
UpdateSnapPointsType(_snapPoints);
}
}
protected EScroller Scroller { get; }
public ICollectionViewLayoutManager LayoutManager
{
get => _layoutManager;
set
{
OnLayoutManagerChanging();
_layoutManager = value;
OnLayoutManagerChanged();
}
}
public ItemAdaptor Adaptor
{
get => _adaptor;
set
{
OnAdaptorChanging();
_adaptor = value;
_adaptor.CollectionView = this;
OnAdaptorChanged();
}
}
int ICollectionViewController.Count => Adaptor?.Count ?? 0;
EPoint ICollectionViewController.ParentPosition => new EPoint
{
X = Scroller.Geometry.X - Scroller.CurrentRegion.X,
Y = Scroller.Geometry.Y - Scroller.CurrentRegion.Y
};
ESize AllocatedSize { get; set; }
Rect ViewPort => Scroller.CurrentRegion;
public void ScrollTo(int index, ScrollToPosition position = ScrollToPosition.MakeVisible, bool animate = true)
{
var itemBound = LayoutManager.GetItemBound(index);
int itemStart;
int itemEnd;
int scrollStart;
int scrollEnd;
int itemPadding = 0;
int itemSize;
int viewportSize;
if (LayoutManager.IsHorizontal)
{
itemStart = itemBound.Left;
itemEnd = itemBound.Right;
itemSize = itemBound.Width;
scrollStart = Scroller.CurrentRegion.Left;
scrollEnd = Scroller.CurrentRegion.Right;
viewportSize = AllocatedSize.Width;
}
else
{
itemStart = itemBound.Top;
itemEnd = itemBound.Bottom;
itemSize = itemBound.Height;
scrollStart = Scroller.CurrentRegion.Top;
scrollEnd = Scroller.CurrentRegion.Bottom;
viewportSize = AllocatedSize.Height;
}
if (position == ScrollToPosition.MakeVisible)
{
if (itemStart < scrollStart)
{
position = ScrollToPosition.Start;
}
else if (itemEnd > scrollEnd)
{
position = ScrollToPosition.End;
}
else
{
// already visible
return;
}
}
if (itemSize < viewportSize)
{
switch (position)
{
case ScrollToPosition.Center:
itemPadding = (viewportSize - itemSize) / 2;
break;
case ScrollToPosition.End:
itemPadding = (viewportSize - itemSize);
break;
}
itemSize = viewportSize;
}
if (LayoutManager.IsHorizontal)
{
itemBound.X -= itemPadding;
itemBound.Width = itemSize;
}
else
{
itemBound.Y -= itemPadding;
itemBound.Height = itemSize;
}
Scroller.ScrollTo(itemBound, animate);
}
public void ScrollTo(object item, ScrollToPosition position = ScrollToPosition.MakeVisible, bool animate = true)
{
ScrollTo(Adaptor.GetItemIndex(item), position, animate);
}
void ICollectionViewController.RequestLayoutItems() => RequestLayoutItems();
ESize ICollectionViewController.GetItemSize()
{
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.Width = Math.Max(_itemSize.Width, 10);
_itemSize.Height = Math.Max(_itemSize.Height, 10);
if (_snapPoints != SnapPointsType.None)
{
Scroller.SetPageSize(_itemSize.Width, _itemSize.Height);
}
return _itemSize;
}
ViewHolder ICollectionViewController.RealizeView(int index)
{
if (Adaptor == null)
return null;
var holder = _pool.GetRecyclerView();
if (holder != null)
{
holder.Show();
}
else
{
var content = Adaptor.CreateNativeView(this);
holder = new ViewHolder(this);
holder.RequestSelected += OnRequestItemSelection;
holder.Content = content;
_innerLayout.PackEnd(holder);
}
Adaptor.SetBinding(holder.Content, index);
_viewHolderIndexTable[holder] = index;
if (index == SelectedItemIndex)
{
OnRequestItemSelection(holder, EventArgs.Empty);
}
return holder;
}
void OnRequestItemSelection(object sender, EventArgs e)
{
if (SelectionMode == CollectionViewSelectionMode.None)
return;
if (_lastSelectedViewHolder != null)
{
_lastSelectedViewHolder.State = ViewHolderState.Normal;
}
_lastSelectedViewHolder = sender as ViewHolder;
if (_lastSelectedViewHolder != null)
{
_lastSelectedViewHolder.State = ViewHolderState.Selected;
if (_viewHolderIndexTable.TryGetValue(_lastSelectedViewHolder, out int index))
{
_selectedItemIndex = index;
Adaptor?.SendItemSelected(index);
}
}
}
void ICollectionViewController.UnrealizeView(ViewHolder view)
{
_viewHolderIndexTable.Remove(view);
view.ResetState();
view.Hide();
_pool.AddRecyclerView(view);
}
protected virtual EScroller CreateScroller(EvasObject parent)
{
return new EScroller(parent);
}
void UpdateSelectedItemIndex()
{
if (SelectionMode == CollectionViewSelectionMode.None)
return;
ViewHolder holder = null;
foreach (var item in _viewHolderIndexTable)
{
if (item.Value == SelectedItemIndex)
{
holder = item.Key;
break;
}
}
OnRequestItemSelection(holder, EventArgs.Empty);
}
void UpdateSelectionMode()
{
if (SelectionMode == CollectionViewSelectionMode.None)
{
if (_lastSelectedViewHolder != null)
{
_lastSelectedViewHolder.State = ViewHolderState.Normal;
_lastSelectedViewHolder = null;
}
_selectedItemIndex = -1;
}
}
void OnLayoutManagerChanging()
{
_layoutManager?.Reset();
}
void OnLayoutManagerChanged()
{
if (_layoutManager == null)
return;
_layoutManager.CollectionView = this;
_layoutManager.SizeAllocated(AllocatedSize);
RequestLayoutItems();
}
void OnAdaptorChanging()
{
_layoutManager?.Reset();
if (Adaptor != null)
{
_pool.Clear(Adaptor);
(Adaptor as INotifyCollectionChanged).CollectionChanged -= OnCollectionChanged;
Adaptor.CollectionView = null;
}
}
void OnAdaptorChanged()
{
if (_adaptor == null)
return;
_itemSize = new ESize(-1, -1);
(Adaptor as INotifyCollectionChanged).CollectionChanged += OnCollectionChanged;
RequestLayoutItems();
if (LayoutManager != null)
{
var itemSize = (this as ICollectionViewController).GetItemSize();
}
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
int idx = e.NewStartingIndex;
foreach (var item in e.NewItems)
{
LayoutManager.ItemInserted(idx++);
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
int idx = e.OldStartingIndex;
foreach (var item in e.OldItems)
{
LayoutManager.ItemRemoved(idx);
}
}
else if (e.Action == NotifyCollectionChangedAction.Move)
{
LayoutManager.ItemRemoved(e.OldStartingIndex);
LayoutManager.ItemInserted(e.NewStartingIndex);
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
LayoutManager.ItemUpdated(e.NewStartingIndex);
}
else if (e.Action == NotifyCollectionChangedAction.Reset)
{
LayoutManager.Reset();
}
RequestLayoutItems();
}
Rect _lastGeometry;
void OnLayout()
{
if (_lastGeometry == Geometry)
{
return;
}
_lastGeometry = Geometry;
Scroller.Geometry = Geometry;
Scroller.ScrollBlock = ScrollBlock.None;
AllocatedSize = Geometry.Size;
_itemSize = new ESize(-1, -1);
if (_adaptor != null && _layoutManager != null)
{
_layoutManager?.SizeAllocated(Geometry.Size);
_layoutManager?.LayoutItems(ViewPort);
}
}
void RequestLayoutItems()
{
if (!_requestLayoutItems)
{
_requestLayoutItems = true;
Device.BeginInvokeOnMainThread(() =>
{
_requestLayoutItems = false;
if (_adaptor != null && _layoutManager != null)
{
OnInnerLayout();
_layoutManager?.LayoutItems(ViewPort, true);
}
});
}
}
void OnInnerLayout()
{
var size = _layoutManager.GetScrollCanvasSize();
_innerLayout.MinimumWidth = size.Width;
_innerLayout.MinimumHeight = size.Height;
}
void OnScrolled(object sender, EventArgs e)
{
_layoutManager.LayoutItems(Scroller.CurrentRegion);
}
void UpdateSnapPointsType(SnapPointsType snapPoints)
{
var itemSize = new ESize(0, 0);
switch (snapPoints)
{
case SnapPointsType.None:
Scroller.HorizontalPageScrollLimit = 0;
Scroller.VerticalPageScrollLimit = 0;
break;
case SnapPointsType.MandatorySingle:
Scroller.HorizontalPageScrollLimit = 1;
Scroller.VerticalPageScrollLimit = 1;
itemSize = (this as ICollectionViewController).GetItemSize();
break;
case SnapPointsType.Mandatory:
Scroller.HorizontalPageScrollLimit = 0;
Scroller.VerticalPageScrollLimit = 0;
itemSize = (this as ICollectionViewController).GetItemSize();
break;
}
Scroller.SetPageSize(itemSize.Width, itemSize.Height);
}
}
public interface ICollectionViewController
{
EPoint ParentPosition { get; }
ViewHolder RealizeView(int index);
void UnrealizeView(ViewHolder view);
void RequestLayoutItems();
int Count { get; }
ESize GetItemSize();
}
public enum CollectionViewSelectionMode
{
None,
Single,
}
}

Просмотреть файл

@ -0,0 +1,61 @@
using System.Collections;
using System.Collections.Generic;
using ESize = ElmSharp.Size;
using XLabel = Xamarin.Forms.Label;
namespace Xamarin.Forms.Platform.Tizen.Native
{
public class EmptyItemAdaptor : ItemTemplateAdaptor
{
static DataTemplate s_defaultEmptyTemplate = new DataTemplate(typeof(EmptyView));
public EmptyItemAdaptor(ItemsView itemsView, IEnumerable items, DataTemplate template) : base(itemsView, items, template)
{
}
public static EmptyItemAdaptor Create(ItemsView itemsView)
{
DataTemplate template = null;
if (itemsView.EmptyView is View emptyView)
{
template = new DataTemplate(() =>
{
return emptyView;
});
}
else
{
template = itemsView.EmptyViewTemplate ?? s_defaultEmptyTemplate;
}
var empty = new List<object>
{
itemsView.EmptyView ?? new object()
};
return new EmptyItemAdaptor(itemsView, empty, template);
}
public override ElmSharp.Size MeasureItem(int widthConstraint, int heightConstraint)
{
return new ESize(widthConstraint, heightConstraint);
}
class EmptyView : StackLayout
{
public EmptyView()
{
HorizontalOptions = LayoutOptions.FillAndExpand;
VerticalOptions = LayoutOptions.FillAndExpand;
Children.Add(
new XLabel
{
Text = "No items found",
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
HorizontalTextAlignment = Xamarin.Forms.TextAlignment.Center,
VerticalTextAlignment = Xamarin.Forms.TextAlignment.Center,
}
);
}
}
}
}

Просмотреть файл

@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ElmSharp;
using ESize = ElmSharp.Size;
namespace Xamarin.Forms.Platform.Tizen.Native
{
public class GridLayoutManager : ICollectionViewLayoutManager
{
ESize _allocatedSize;
ESize _scrollCanvasSize;
bool _isLayouting;
Rect _last;
Dictionary<int, RealizedItem> _realizedItem = new Dictionary<int, RealizedItem>();
public GridLayoutManager(bool isHorizontal, int span = 1)
{
IsHorizontal = isHorizontal;
Span = span;
}
public int Span { get; internal set; }
public bool IsHorizontal { get; }
public ICollectionViewController CollectionView { get; set; }
public void SizeAllocated(ESize size)
{
Reset();
_allocatedSize = size;
_scrollCanvasSize = new ESize(0, 0);
}
public ESize GetScrollCanvasSize()
{
if (_scrollCanvasSize.Width > 0 && _scrollCanvasSize.Height > 0)
return _scrollCanvasSize;
var itemCount = CollectionView.Count;
var itemSize = CollectionView.GetItemSize();
if (IsHorizontal)
{
return _scrollCanvasSize = new ESize((int)Math.Ceiling(itemCount / (double)Span) * itemSize.Width , _allocatedSize.Height);
}
else
{
return _scrollCanvasSize = new ESize(_allocatedSize.Width, (int)Math.Ceiling(itemCount / (double)Span) * itemSize.Height);
}
}
bool ShouldRearrange(Rect viewport)
{
if (_isLayouting)
return false;
if (_last.Size != viewport.Size)
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)
return true;
return false;
}
public void LayoutItems(Rect bound, bool force)
{
// TODO : need to optimization. it was frequently called with similar bound value.
if (!ShouldRearrange(bound) && !force)
{
return;
}
_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);
foreach (var index in _realizedItem.Keys.ToList())
{
if (index < startIndex || index > endIndex)
{
CollectionView.UnrealizeView(_realizedItem[index].View);
_realizedItem.Remove(index);
}
}
var parent = CollectionView.ParentPosition;
for (int i = startIndex; i <= endIndex; i++)
{
EvasObject itemView = null;
if (!_realizedItem.ContainsKey(i))
{
var view = CollectionView.RealizeView(i);
_realizedItem[i] = new RealizedItem
{
View = view,
Index = i,
};
itemView = view;
}
else
{
itemView = _realizedItem[i].View;
}
var itemBound = GetItemBound(i);
itemBound.X += parent.X;
itemBound.Y += parent.Y;
itemView.Geometry = itemBound;
}
_isLayouting = false;
}
public void UpdateSpan(int span)
{
Span = span;
_scrollCanvasSize = new ESize(0, 0);
CollectionView.RequestLayoutItems();
}
public void ItemInserted(int inserted)
{
var items = _realizedItem.Keys.OrderByDescending(key => key);
foreach (var index in items)
{
if (index >= inserted)
{
_realizedItem[index + 1] = _realizedItem[index];
}
}
if (_realizedItem.ContainsKey(inserted))
{
_realizedItem.Remove(inserted);
}
else
{
var last = items.LastOrDefault();
if (last >= inserted)
{
_realizedItem.Remove(last);
}
}
_scrollCanvasSize = new ESize(0, 0);
}
public void ItemRemoved(int removed)
{
if (_realizedItem.ContainsKey(removed))
{
CollectionView.UnrealizeView(_realizedItem[removed].View);
_realizedItem.Remove(removed);
}
var items = _realizedItem.Keys.OrderBy(key => key);
foreach (var index in items)
{
if (index > removed)
{
_realizedItem[index - 1] = _realizedItem[index];
}
}
var last = items.LastOrDefault();
if (last > removed)
{
_realizedItem.Remove(last);
}
_scrollCanvasSize = new ESize(0, 0);
}
public void ItemUpdated(int index)
{
if (_realizedItem.ContainsKey(index))
{
var bound = _realizedItem[index].View.Geometry;
CollectionView.UnrealizeView(_realizedItem[index].View);
var view = CollectionView.RealizeView(index);
_realizedItem[index].View = view;
view.Geometry = bound;
}
}
public Rect GetItemBound(int index)
{
var size = CollectionView.GetItemSize();
if (IsHorizontal)
{
size.Height = _allocatedSize.Height / Span;
}
else
{
size.Width = _allocatedSize.Width / Span;
}
int rowIndex = index / Span;
int colIndex = index % Span;
var colSize = IsHorizontal ? size.Height : size.Width;
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);
}
public void Reset()
{
foreach (var realizedItem in _realizedItem.Values)
{
CollectionView.UnrealizeView(realizedItem.View);
}
_realizedItem.Clear();
_scrollCanvasSize = new ESize(0, 0);
}
int GetStartIndex(Rect bound, int itemSize)
{
return ViewPortStartPoint(bound) / itemSize * Span;
}
int GetEndIndex(Rect bound, int itemSize)
{
return (int)Math.Ceiling(ViewPortEndPoint(bound) / (double)itemSize) * Span;
}
int ViewPortStartPoint(Rect viewPort)
{
return IsHorizontal ? viewPort.X : viewPort.Y;
}
int ViewPortEndPoint(Rect viewPort)
{
return ViewPortStartPoint(viewPort) + ViewPortSize(viewPort);
}
int ViewPortSize(Rect viewPort)
{
return IsHorizontal ? viewPort.Width : viewPort.Height;
}
class RealizedItem
{
public ViewHolder View { get; set; }
public int Index { get; set; }
}
}
}

Просмотреть файл

@ -0,0 +1,28 @@
using ElmSharp;
using ESize = ElmSharp.Size;
namespace Xamarin.Forms.Platform.Tizen.Native
{
public interface ICollectionViewLayoutManager
{
ICollectionViewController CollectionView { get; set; }
bool IsHorizontal { get; }
void SizeAllocated(ESize size);
ESize GetScrollCanvasSize();
void LayoutItems(Rect bound, bool force = false);
Rect GetItemBound(int index);
void ItemInserted(int index);
void ItemRemoved(int index);
void ItemUpdated(int index);
void Reset();
}
}

Просмотреть файл

@ -0,0 +1,100 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using ElmSharp;
using ESize = ElmSharp.Size;
namespace Xamarin.Forms.Platform.Tizen.Native
{
public abstract class ItemAdaptor : INotifyCollectionChanged
{
IList _itemsSource;
public CollectionView CollectionView { get; set; }
protected ItemAdaptor(IEnumerable items)
{
SetItemsSource(items);
}
public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;
public virtual void SendItemSelected(int index)
{
ItemSelected?.Invoke(this, new SelectedItemChangedEventArgs(this[index], index));
}
public void RequestItemSelected(object item)
{
if (CollectionView != null)
{
CollectionView.SelectedItemIndex = _itemsSource.IndexOf(item);
}
}
protected void SetItemsSource(IEnumerable items)
{
switch (items)
{
case IList list:
_itemsSource = list;
_observableCollection = list as INotifyCollectionChanged;
break;
case IEnumerable<object> generic:
_itemsSource = new List<object>(generic);
break;
case IEnumerable _:
_itemsSource = new List<object>();
foreach (var item in items)
{
_itemsSource.Add(item);
}
break;
}
}
public object this[int index]
{
get
{
return _itemsSource[index];
}
}
public int Count => _itemsSource.Count;
INotifyCollectionChanged _observableCollection;
event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged
{
add
{
if (_observableCollection != null)
{
_observableCollection.CollectionChanged += value;
}
}
remove
{
if (_observableCollection != null)
{
_observableCollection.CollectionChanged -= value;
}
}
}
public int GetItemIndex(object item)
{
return _itemsSource.IndexOf(item);
}
public abstract EvasObject CreateNativeView(EvasObject parent);
public abstract void RemoveNativeView(EvasObject native);
public abstract void SetBinding(EvasObject view, int index);
public abstract ESize MeasureItem(int widthConstraint, int heightConstraint);
}
}

Просмотреть файл

@ -0,0 +1,112 @@
using System.Collections;
using System.Collections.Generic;
using ElmSharp;
using ESize = ElmSharp.Size;
using XLabel = Xamarin.Forms.Label;
namespace Xamarin.Forms.Platform.Tizen.Native
{
public class ItemDefaultTemplateAdaptor : ItemTemplateAdaptor
{
public ItemDefaultTemplateAdaptor(ItemsView itemsView) : base(itemsView)
{
ItemTemplate = new DataTemplate(() =>
{
return new StackLayout
{
BackgroundColor = Color.White,
Padding = 30,
Children =
{
new XLabel()
}
};
});
}
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>();
ItemsView _itemsView;
public ItemTemplateAdaptor(ItemsView itemsView) : base(itemsView.ItemsSource)
{
ItemTemplate = itemsView.ItemTemplate;
_itemsView = itemsView;
}
protected ItemTemplateAdaptor(ItemsView itemsView, IEnumerable items, DataTemplate template) : base(items)
{
ItemTemplate = template;
_itemsView = itemsView;
}
protected DataTemplate ItemTemplate { get; set; }
protected View GetTemplatedView(EvasObject evasObject)
{
return _nativeFormsTable[evasObject];
}
public override EvasObject CreateNativeView(EvasObject parent)
{
var view = ItemTemplate.CreateContent() as View;
var renderer = Platform.GetOrCreateRenderer(view);
var native = Platform.GetOrCreateRenderer(view).NativeView;
view.Parent = _itemsView;
(renderer as LayoutRenderer)?.RegisterOnLayoutUpdated();
_nativeFormsTable[native] = view;
return native;
}
public override void RemoveNativeView(EvasObject native)
{
if (_nativeFormsTable.TryGetValue(native, out View view))
{
Platform.GetRenderer(view)?.Dispose();
_nativeFormsTable.Remove(native);
}
}
public override void SetBinding(EvasObject native, int index)
{
if (_nativeFormsTable.TryGetValue(native, out View view))
{
view.BindingContext = this[index];
}
}
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 request.ToPixel();
}
}
}

Просмотреть файл

@ -0,0 +1,240 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ElmSharp;
using ESize = ElmSharp.Size;
namespace Xamarin.Forms.Platform.Tizen.Native
{
public class LinearLayoutManager : ICollectionViewLayoutManager
{
ESize _allocatedSize;
bool _isLayouting;
Rect _last;
Dictionary<int, RealizedItem> _realizedItem = new Dictionary<int, RealizedItem>();
public LinearLayoutManager(bool isHorizontal)
{
IsHorizontal = isHorizontal;
}
public bool IsHorizontal { get; }
public ICollectionViewController CollectionView { get; set; }
public void SizeAllocated(ESize size)
{
Reset();
_allocatedSize = size;
_scrollCanvasSize = new ESize(0, 0);
}
ESize _scrollCanvasSize;
public ESize GetScrollCanvasSize()
{
if (_scrollCanvasSize.Width > 0 && _scrollCanvasSize.Height > 0)
return _scrollCanvasSize;
var itemCount = CollectionView.Count;
var itemSize = CollectionView.GetItemSize();
if (IsHorizontal)
{
return _scrollCanvasSize = new ESize(itemCount * itemSize.Width, _allocatedSize.Height);
}
else
{
return _scrollCanvasSize = new ESize(_allocatedSize.Width, itemCount * itemSize.Height);
}
}
bool ShouldRearrange(Rect viewport)
{
if (_isLayouting)
return false;
if (_last.Size != viewport.Size)
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)
return true;
return false;
}
public void LayoutItems(Rect bound, bool force)
{
// TODO : need to optimization. it was frequently called with similar bound value.
if (!ShouldRearrange(bound) && !force)
{
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);
foreach (var index in _realizedItem.Keys.ToList())
{
if (index < startIndex || index > endIndex)
{
CollectionView.UnrealizeView(_realizedItem[index].View);
_realizedItem.Remove(index);
}
}
var parent = CollectionView.ParentPosition;
for (int i = startIndex; i <= endIndex; i++)
{
EvasObject itemView = null;
if (!_realizedItem.ContainsKey(i))
{
var view = CollectionView.RealizeView(i);
_realizedItem[i] = new RealizedItem
{
View = view,
Index = i,
};
itemView = view;
}
else
{
itemView = _realizedItem[i].View;
}
var itemBound = GetItemBound(i);
itemBound.X += parent.X;
itemBound.Y += parent.Y;
itemView.Geometry = itemBound;
}
_isLayouting = false;
}
public void ItemInserted(int inserted)
{
var items = _realizedItem.Keys.OrderByDescending(key => key);
foreach (var index in items)
{
if (index >= inserted)
{
_realizedItem[index + 1] = _realizedItem[index];
}
}
if (_realizedItem.ContainsKey(inserted))
{
_realizedItem.Remove(inserted);
}
else
{
var last = items.LastOrDefault();
if (last >= inserted)
{
_realizedItem.Remove(last);
}
}
_scrollCanvasSize = new ESize(0, 0);
}
public void ItemRemoved(int removed)
{
if (_realizedItem.ContainsKey(removed))
{
CollectionView.UnrealizeView(_realizedItem[removed].View);
_realizedItem.Remove(removed);
}
var items = _realizedItem.Keys.OrderBy(key => key);
foreach (var index in items)
{
if (index > removed)
{
_realizedItem[index - 1] = _realizedItem[index];
}
}
var last = items.LastOrDefault();
if (last > removed)
{
_realizedItem.Remove(last);
}
_scrollCanvasSize = new ESize(0, 0);
}
public void ItemUpdated(int index)
{
if (_realizedItem.ContainsKey(index))
{
var bound = _realizedItem[index].View.Geometry;
CollectionView.UnrealizeView(_realizedItem[index].View);
var view = CollectionView.RealizeView(index);
_realizedItem[index].View = view;
view.Geometry = bound;
}
}
public Rect GetItemBound(int index)
{
var size = CollectionView.GetItemSize();
if (IsHorizontal)
{
size.Height = _allocatedSize.Height;
}
else
{
size.Width = _allocatedSize.Width;
}
return
IsHorizontal ?
new Rect(index * size.Width, 0, size.Width, size.Height) :
new Rect(0, index * size.Height, size.Width, size.Height);
}
public void Reset()
{
foreach (var realizedItem in _realizedItem.Values)
{
CollectionView.UnrealizeView(realizedItem.View);
}
_realizedItem.Clear();
_scrollCanvasSize = new ESize(0, 0);
}
int GetStartIndex(Rect bound, int itemSize)
{
return ViewPortStartPoint(bound) / itemSize;
}
int GetEndIndex(Rect bound, int itemSize)
{
return (int)Math.Ceiling(ViewPortEndPoint(bound) / (double)itemSize);
}
int ViewPortStartPoint(Rect viewPort)
{
return IsHorizontal ? viewPort.X : viewPort.Y;
}
int ViewPortEndPoint(Rect viewPort)
{
return ViewPortStartPoint(viewPort) + ViewPortSize(viewPort);
}
int ViewPortSize(Rect viewPort)
{
return IsHorizontal ? viewPort.Width : viewPort.Height;
}
class RealizedItem
{
public ViewHolder View { get; set; }
public int Index { get; set; }
}
}
}

Просмотреть файл

@ -0,0 +1,35 @@
using System.Collections.Generic;
using ElmSharp;
namespace Xamarin.Forms.Platform.Tizen.Native
{
class RecyclerPool
{
LinkedList<ViewHolder> _pool = new LinkedList<ViewHolder>();
public void Clear(ItemAdaptor adaptor)
{
foreach (var item in _pool)
{
adaptor.RemoveNativeView(item);
}
_pool.Clear();
}
public void AddRecyclerView(ViewHolder view)
{
_pool.AddLast(view);
}
public ViewHolder GetRecyclerView()
{
if (_pool.First != null)
{
var fisrt = _pool.First;
_pool.RemoveFirst();
return fisrt.Value;
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,171 @@
using System;
using ElmSharp;
using ERectangle = ElmSharp.Rectangle;
using EColor = ElmSharp.Color;
namespace Xamarin.Forms.Platform.Tizen.Native
{
public enum ViewHolderState
{
Normal,
Selected,
}
public class ViewHolder : Box
{
static readonly EColor s_defaultFocusEffectColor = EColor.FromRgba(244, 244, 244, 200);
static readonly EColor s_defaultSelectedColor = EColor.FromRgba(227, 242, 253, 200);
ERectangle _background;
Button _focusArea;
EvasObject _content;
ViewHolderState _state;
public ViewHolder(EvasObject parent) : base(parent)
{
Initialize(parent);
}
public EColor FocusedColor { get; set; }
public EColor SelectedColor { get; set; }
EColor EffectiveFocusedColor => FocusedColor == EColor.Default ? s_defaultFocusEffectColor : FocusedColor;
EColor EffectiveSelectedColor => SelectedColor == EColor.Default ? s_defaultSelectedColor : FocusedColor;
EColor FocusSelectedColor
{
get
{
var color1 = EffectiveFocusedColor;
var color2 = EffectiveSelectedColor;
return new EColor(
(color1.R + color2.R) / 2,
(color1.G + color2.G) / 2,
(color1.B + color2.B) / 2,
(color1.A + color2.A) / 2);
}
}
public EvasObject Content
{
get
{
return _content;
}
set
{
if (_content != null)
{
UnPack(_content);
}
_content = value;
if (_content != null)
{
PackAfter(_content, _background);
_content.StackBelow(_focusArea);
}
}
}
public ViewHolderState State
{
get { return _state; }
set
{
_state = value;
UpdateState();
}
}
public event EventHandler Selected;
public event EventHandler RequestSelected;
public void ResetState()
{
State = ViewHolderState.Normal;
_background.Color = EColor.Transparent;
}
protected void SendSelected()
{
Selected?.Invoke(this, EventArgs.Empty);
}
protected void Initialize(EvasObject parent)
{
SetLayoutCallback(OnLayout);
_background = new ERectangle(parent)
{
Color = EColor.Transparent
};
_background.Show();
_focusArea = new Button(parent);
_focusArea.Color = EColor.Transparent;
_focusArea.BackgroundColor = EColor.Transparent;
_focusArea.SetPartColor("effect", EColor.Transparent);
_focusArea.Clicked += OnClicked;
_focusArea.Focused += OnFocused;
_focusArea.Unfocused += OnFocused;
_focusArea.KeyUp += OnKeyUp;
_focusArea.RepeatEvents = true;
_focusArea.Show();
PackEnd(_background);
PackEnd(_focusArea);
FocusedColor = EColor.Default;
Show();
}
protected virtual void OnFocused(object sender, EventArgs e)
{
if (_focusArea.IsFocused)
{
_background.Color = State == ViewHolderState.Selected ? FocusSelectedColor : EffectiveFocusedColor;
}
else
{
_background.Color = State == ViewHolderState.Selected ? EffectiveSelectedColor : EColor.Transparent;
}
}
protected virtual void OnClicked(object sender, EventArgs e)
{
RequestSelected?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnLayout()
{
_background.Geometry = Geometry;
_focusArea.Geometry = Geometry;
if (_content != null)
{
_content.Geometry = Geometry;
}
}
protected virtual void UpdateState()
{
if (State == ViewHolderState.Normal)
{
_background.Color = _focusArea.IsFocused ? EffectiveFocusedColor : EColor.Transparent;
} else
{
_background.Color = _focusArea.IsFocused ? FocusSelectedColor : SelectedColor;
SendSelected();
}
}
void OnKeyUp(object sender, EvasKeyEventArgs e)
{
if (e.KeyName == "Enter" && _focusArea.IsFocused)
{
RequestSelected?.Invoke(this, EventArgs.Empty);
}
}
}
}

Просмотреть файл

@ -36,6 +36,7 @@ using System.Reflection;
[assembly: ExportRenderer(typeof(NativeViewWrapper), typeof(NativeViewWrapperRenderer))]
[assembly: ExportRenderer(typeof(WebView), typeof(WebViewRenderer))]
[assembly: ExportRenderer(typeof(ImageButton), typeof(ImageButtonRenderer))]
[assembly: ExportRenderer(typeof(ItemsView), typeof(ItemsViewRenderer))]
[assembly: ExportImageSourceHandler(typeof(FileImageSource), typeof(FileImageSourceHandler))]
[assembly: ExportImageSourceHandler(typeof(StreamImageSource), typeof(StreamImageSourceHandler))]

Просмотреть файл

@ -0,0 +1,182 @@
using System.Collections.Specialized;
using System.Linq;
using Xamarin.Forms.Platform.Tizen.Native;
namespace Xamarin.Forms.Platform.Tizen
{
public class ItemsViewRenderer : ViewRenderer<ItemsView, Native.CollectionView>
{
INotifyCollectionChanged _observableSource;
public ItemsViewRenderer()
{
RegisterPropertyHandler(ItemsView.ItemsSourceProperty, UpdateItemsSource);
RegisterPropertyHandler(ItemsView.ItemTemplateProperty, UpdateAdaptor);
RegisterPropertyHandler(ItemsView.ItemsLayoutProperty, UpdateItemsLayout);
RegisterPropertyHandler(SelectableItemsView.SelectedItemProperty, UpdateSelectedItem);
RegisterPropertyHandler(SelectableItemsView.SelectionModeProperty, UpdateSelectionMode);
}
protected override void OnElementChanged(ElementChangedEventArgs<ItemsView> e)
{
if (Control == null)
{
SetNativeControl(new Native.CollectionView(Forms.NativeParent));
}
if (e.NewElement != null)
{
e.NewElement.ScrollToRequested += OnScrollToRequest;
}
base.OnElementChanged(e);
UpdateAdaptor(false);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Element != null)
{
Element.ScrollToRequested -= OnScrollToRequest;
Element.ItemsLayout.PropertyChanged -= OnLayoutPropertyChanged;
}
if (_observableSource != null)
{
_observableSource.CollectionChanged -= OnCollectionChanged;
}
}
base.Dispose(disposing);
}
void UpdateSelectedItem(bool initialize)
{
if (initialize)
return;
if (Element is SelectableItemsView selectable)
{
Control?.Adaptor?.RequestItemSelected(selectable.SelectedItem);
}
}
void UpdateSelectionMode()
{
if (Element is SelectableItemsView selectable)
{
Control.SelectionMode = selectable.SelectionMode == SelectionMode.None ? CollectionViewSelectionMode.None : CollectionViewSelectionMode.Single;
}
}
void OnScrollToRequest(object sender, ScrollToRequestEventArgs e)
{
if (e.Mode == ScrollToMode.Position)
{
Control.ScrollTo(e.Index, e.ScrollToPosition, e.IsAnimated);
}
else
{
Control.ScrollTo(e.Item, e.ScrollToPosition, e.IsAnimated);
}
}
void UpdateItemsSource(bool initialize)
{
if (Element.ItemsSource is INotifyCollectionChanged collectionChanged)
{
if (_observableSource != null)
{
_observableSource.CollectionChanged -= OnCollectionChanged;
}
_observableSource = collectionChanged;
_observableSource.CollectionChanged += OnCollectionChanged;
}
UpdateAdaptor(initialize);
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (Element.ItemsSource == null || !Element.ItemsSource.Cast<object>().Any())
{
Control.Adaptor = EmptyItemAdaptor.Create(Element);
}
else
{
if (Control.Adaptor is EmptyItemAdaptor)
{
UpdateAdaptor(false);
}
}
}
void UpdateAdaptor(bool initialize)
{
if (!initialize)
{
if (Element.ItemsSource == null || !Element.ItemsSource.Cast<object>().Any())
{
Control.Adaptor = EmptyItemAdaptor.Create(Element);
}
else if (Element.ItemTemplate == null)
{
Control.Adaptor = new ItemDefaultTemplateAdaptor(Element);
}
else
{
Control.Adaptor = new ItemTemplateAdaptor(Element);
Control.Adaptor.ItemSelected += OnItemSelectedFromUI;
}
}
}
void OnItemSelectedFromUI(object sender, SelectedItemChangedEventArgs e)
{
if (Element is SelectableItemsView selectableItemsView)
{
selectableItemsView.SelectedItem = e.SelectedItem;
}
}
void UpdateItemsLayout()
{
if (Element.ItemsLayout != null)
{
Control.LayoutManager = Element.ItemsLayout.ToLayoutManager();
Control.SnapPointsType = (Element.ItemsLayout as ItemsLayout)?.SnapPointsType ?? SnapPointsType.None;
Element.ItemsLayout.PropertyChanged += OnLayoutPropertyChanged;
}
}
void OnLayoutPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ItemsLayout.SnapPointsType))
{
Control.SnapPointsType = (Element.ItemsLayout as ItemsLayout)?.SnapPointsType ?? SnapPointsType.None;
}
else if (e.PropertyName == nameof(GridItemsLayout.Span))
{
((GridLayoutManager)(Control.LayoutManager)).UpdateSpan(((GridItemsLayout)Element.ItemsLayout).Span);
}
}
}
static class ItemsLayoutExtension
{
public static ICollectionViewLayoutManager ToLayoutManager(this IItemsLayout layout)
{
switch (layout)
{
case ListItemsLayout listItemsLayout:
return new LinearLayoutManager(listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal);
case GridItemsLayout gridItemsLayout:
return new GridLayoutManager(gridItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal, gridItemsLayout.Span);
default:
break;
}
return new LinearLayoutManager(false);
}
}
}

Просмотреть файл

@ -598,12 +598,12 @@ namespace Xamarin.Forms.Platform.Tizen
static double ComputeAbsoluteX(VisualElement e)
{
return e.X + ((e.RealParent is VisualElement) && !(e.RealParent is ListView) ? Forms.ConvertToScaledDP(Platform.GetRenderer(e.RealParent).GetNativeContentGeometry().X) : 0.0);
return e.X + ((e.RealParent is VisualElement) && !(e.RealParent is ListView || e.RealParent is ItemsView) ? Forms.ConvertToScaledDP(Platform.GetRenderer(e.RealParent).GetNativeContentGeometry().X) : 0.0);
}
static double ComputeAbsoluteY(VisualElement e)
{
return e.Y + ((e.RealParent is VisualElement) && !(e.RealParent is ListView) ? Forms.ConvertToScaledDP(Platform.GetRenderer(e.RealParent).GetNativeContentGeometry().Y) : 0.0);
return e.Y + ((e.RealParent is VisualElement) && !(e.RealParent is ListView || e.RealParent is ItemsView) ? Forms.ConvertToScaledDP(Platform.GetRenderer(e.RealParent).GetNativeContentGeometry().Y) : 0.0);
}
static Point ComputeAbsolutePoint(VisualElement e)