Add GridView 17/110317/15
authorSungHyun Min <shyun.min@samsung.com>
Fri, 13 Jan 2017 12:10:10 +0000 (21:10 +0900)
committerSungHyun Min <shyun.min@samsung.com>
Tue, 21 Feb 2017 09:21:33 +0000 (18:21 +0900)
- depend on
    https://review.tizen.org/gerrit/#/c/110812/
    https://review.tizen.org/gerrit/#/c/110246/

- RFC: http://suprem.sec.samsung.net/confluence/display/SPTDTLC/%5BFormsTizen%5D+RFC+7+-+GridView

Change-Id: I3917d34c7068660b9b63902b5e76dd3ec8bb36cc
Signed-off-by: SungHyun Min <shyun.min@samsung.com>
15 files changed:
Tizen.Xamarin.Forms.Extension.Renderer/Cells/GridViewCellRenderer.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension.Renderer/Cells/GridViewTypeCellRenderer.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension.Renderer/GridViewRenderer.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension.Renderer/Tizen.Xamarin.Forms.Extension.Renderer.csproj
Tizen.Xamarin.Forms.Extension/Cells/GridViewCell.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension/EnumerableExtensions.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension/GridView.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension/GridViewEnums.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension/GridViewEventArgs.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension/ItemsView.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension/ListProxy.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension/Properties/AssemblyInfo.cs
Tizen.Xamarin.Forms.Extension/ReadOnlyListAdapter.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension/TemplatedItemsList.cs [new file with mode: 0644]
Tizen.Xamarin.Forms.Extension/Tizen.Xamarin.Forms.Extension.csproj

diff --git a/Tizen.Xamarin.Forms.Extension.Renderer/Cells/GridViewCellRenderer.cs b/Tizen.Xamarin.Forms.Extension.Renderer/Cells/GridViewCellRenderer.cs
new file mode 100644 (file)
index 0000000..6723dce
--- /dev/null
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+using Tizen.Xamarin.Forms.Extension;
+using ElmSharp;
+
+namespace Tizen.Xamarin.Forms.Extension.Renderer
+{
+    public class GridViewCellRenderer : CellRenderer
+    {
+        public GridViewCellRenderer() : base("default")
+        {
+            IconPart = "elm.swallow.icon";
+        }
+
+        protected string IconPart { get; set; }
+
+        protected override EvasObject OnGetContent(Cell cell, string part)
+        {
+            if (part == IconPart)
+            {
+                var viewCell = cell as GridViewCell;
+                if (viewCell != null)
+                {
+                    var renderer = Platform.GetOrCreateRenderer(viewCell.View);
+                    int height = FindCellContentHeight(viewCell);
+                    renderer.NativeView.MinimumHeight = height;
+                    return renderer.NativeView;
+                }
+                return null;
+            }
+            return null;
+        }
+
+        protected override bool OnCellPropertyChanged(Cell cell, string property, Dictionary<string, EvasObject> realizedView)
+        {
+            if (property == "View")
+            {
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/Tizen.Xamarin.Forms.Extension.Renderer/Cells/GridViewTypeCellRenderer.cs b/Tizen.Xamarin.Forms.Extension.Renderer/Cells/GridViewTypeCellRenderer.cs
new file mode 100644 (file)
index 0000000..ee726db
--- /dev/null
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+using Tizen.Xamarin.Forms.Extension;
+using ElmSharp;
+using NImage = Xamarin.Forms.Platform.Tizen.Native.Image;
+using TForms = Xamarin.Forms.Platform.Tizen.Forms;
+
+namespace Tizen.Xamarin.Forms.Extension.Renderer
+{
+    public class TypeGridViewCellRenderer<TCell> : CellRenderer where TCell : TypeGridViewCell
+    {
+        protected string IconPart { get; set; }
+
+        protected string EndPart { get; set; }
+
+        public TypeGridViewCellRenderer(string type) : base(type)
+        {
+            IconPart = "elm.swallow.icon";
+            EndPart = "elm.swallow.end";
+        }
+
+        protected override Span OnGetText(Cell cell, string part)
+        {
+            TCell type1Cell = cell as TCell;
+            return new Span()
+            {
+                Text = type1Cell.Text,
+                FontSize = -1
+            };
+        }
+
+        protected override EvasObject OnGetContent(Cell cell, string part)
+        {
+            if (part == IconPart)
+            {
+                TCell typeCell = cell as TCell;
+                var image = new NImage(TForms.Context.MainWindow);
+                var task = image.LoadFromImageSourceAsync(ImageSource.FromFile(typeCell.Content));
+                return image;
+            }
+
+            if (part == EndPart)
+            {
+                TCell typeCell = cell as TCell;
+                if (typeCell.IsBadgeVisible)
+                {
+                    var badge = new ElmSharp.Layout(TForms.Context.MainWindow);
+                    badge.SetTheme("layout", "badge", "default");
+                    badge.SetPartText("elm.text", typeCell.BadgeCount.ToString());
+                    return badge;
+                }
+            }
+            return null;
+        }
+
+        protected override bool OnCellPropertyChanged(Cell cell, string property, Dictionary<string, EvasObject> realizedView)
+        {
+            if (property == TypeGridViewCell.TextProperty.PropertyName)
+            {
+                return true;
+            }
+            else if (property == TypeGridViewCell.BadgeCountProperty.PropertyName || property == TypeGridViewCell.IsBadgeVisibleProperty.PropertyName)
+            {
+                var typeCell = cell as TCell;
+                EvasObject badge;
+                realizedView.TryGetValue(EndPart, out badge);
+                if (badge == null)
+                    return true;
+
+                if (typeCell.IsBadgeVisible)
+                {
+                    (badge as ElmSharp.Layout)?.SetPartText("elm.text", typeCell.BadgeCount.ToString());
+                    badge.Show();
+                }
+                else
+                {
+                    badge.Hide();
+                }
+                return false;
+            }
+            else if (property == TypeGridViewCell.ContentProperty.PropertyName)
+            {
+                EvasObject img;
+                var typeCell = cell as TCell;
+                realizedView.TryGetValue(IconPart, out img);
+                (img as NImage)?.LoadFromImageSourceAsync(typeCell.Content);
+                return false;
+            }
+            return false;
+        }
+    }
+
+    public class Type1GridViewCellRenderer : TypeGridViewCellRenderer<Type1GridViewCell>
+    {
+        public Type1GridViewCellRenderer() : base("type1")
+        {
+        }
+    }
+
+    public class Type2GridViewCellRenderer : TypeGridViewCellRenderer<Type2GridViewCell>
+    {
+        public Type2GridViewCellRenderer() : base("type2")
+        {
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/Tizen.Xamarin.Forms.Extension.Renderer/GridViewRenderer.cs b/Tizen.Xamarin.Forms.Extension.Renderer/GridViewRenderer.cs
new file mode 100644 (file)
index 0000000..75870ff
--- /dev/null
@@ -0,0 +1,347 @@
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Reflection;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+using Tizen.Xamarin.Forms.Extension;
+using Tizen.Xamarin.Forms.Extension.Renderer;
+using ElmSharp;
+
+using ItemContext = Xamarin.Forms.Platform.Tizen.Native.ListView.ItemContext;
+using TForms = Xamarin.Forms.Platform.Tizen.Forms;
+
+[assembly: ExportRenderer(typeof(GridView), typeof(GridViewRenderer))]
+namespace Tizen.Xamarin.Forms.Extension.Renderer
+{
+    class GridViewRenderer : ViewRenderer<GridView, GenGrid>, IDisposable
+    {
+        readonly EventHandler<GrdiViewScrollToRequestedEventArgs> _scrollToRequested;
+        readonly NotifyCollectionChangedEventHandler _collectionChanged;
+
+        readonly IDictionary<Type, Type> _cellRendererMappingTable = new Dictionary<Type, Type>();
+        readonly IDictionary<Type, CellRenderer> _cellRendererCache = new Dictionary<Type, CellRenderer>();
+        readonly List<ItemContext> _itemContextList = new List<ItemContext>();
+
+        int _defaultItemHeight = 100;
+        int _defaultItemWidth = 100;
+
+        public GridViewRenderer()
+        {
+            _scrollToRequested = OnScrollToRequested;
+            _collectionChanged = OnCollectionChanged;
+
+            RegisterPropertyHandler(GridView.ColumnSpacingProperty, UpdateColumnSpacing);
+            RegisterPropertyHandler(GridView.RowSpacingProperty, UpdateRowSpacing);
+            RegisterPropertyHandler(GridView.ItemHeightProperty, UpdateItemHeight);
+            RegisterPropertyHandler(GridView.ItemWidthProperty, UpdateItemWidth);
+            RegisterPropertyHandler(GridView.SelectionModeProperty, UpdateSelectionMode);
+            RegisterPropertyHandler(GridView.CanSelectionMultipleProperty, UpdateSelectionMultiple);
+            RegisterPropertyHandler(GridView.IsHighlightEffectEnabledProperty, UpdateHighlight);
+            RegisterPropertyHandler(GridView.FillItemsProperty, UpdateFillItem);
+            RegisterPropertyHandler(GridView.OrientationProperty, UpdateOrientation);
+            RegisterPropertyHandler(GridView.ItemsSourceProperty, UpdateSource);
+
+            // It is not possible to get registered cell renderers from Xamarin.Forms.Core.
+            // The renderer that is used in GridView should be registered here.
+            _cellRendererMappingTable[typeof(GridViewCell)] = typeof(GridViewCellRenderer);
+            _cellRendererMappingTable[typeof(Type1GridViewCell)] = typeof(Type1GridViewCellRenderer);
+            _cellRendererMappingTable[typeof(Type2GridViewCell)] = typeof(Type2GridViewCellRenderer);
+        }
+
+        protected override void OnElementChanged(ElementChangedEventArgs<GridView> e)
+        {
+            if (Control == null)
+            {
+                SetNativeControl(new GenGrid(TForms.Context.MainWindow));
+            }
+
+            if (e.OldElement != null)
+            {
+                e.OldElement.ScrollToRequested -= _scrollToRequested;
+                e.OldElement.TemplatedItems.CollectionChanged -= _collectionChanged;
+                Control.ItemRealized -= ItemRealizedHandler;
+                Control.ItemUnrealized -= ItemUnrealizedHandler;
+                Control.ItemPressed -= ItemPressedHandler;
+                Control.ItemSelected -= ItemSelectedHandler;
+            }
+
+            if (e.NewElement != null)
+            {
+                e.NewElement.ScrollToRequested += _scrollToRequested;
+                e.NewElement.TemplatedItems.CollectionChanged += _collectionChanged;
+                Control.ItemRealized += ItemRealizedHandler;
+                Control.ItemUnrealized += ItemUnrealizedHandler;
+                Control.ItemPressed += ItemPressedHandler;
+                Control.ItemSelected += ItemSelectedHandler;
+            }
+
+            base.OnElementChanged(e);
+        }
+
+        void UpdateColumnSpacing()
+        {
+            Control.WeightY = Element.ColumnSpacing;
+        }
+
+        void UpdateRowSpacing()
+        {
+            Control.WeightX = Element.RowSpacing;
+        }
+
+        void UpdateItemHeight()
+        {
+            if (Element.ItemHeight < 0)
+            {
+                Control.ItemHeight = _defaultItemHeight;
+            }
+            else
+            {
+                Control.ItemHeight = Element.ItemHeight;
+            }
+        }
+
+        void UpdateItemWidth()
+        {
+            if (Element.ItemWidth < 0)
+            {
+                Control.ItemWidth = _defaultItemWidth;
+            }
+            else
+            {
+                Control.ItemWidth = Element.ItemWidth;
+            }
+        }
+
+        void UpdateSelectionMode()
+        {
+            switch (Element.SelectionMode)
+            {
+                default:
+                case GridViewSelectionMode.Default:
+                    Control.SelectionMode = GenGridSelectionMode.Default;
+                    break;
+                case GridViewSelectionMode.Always:
+                    Control.SelectionMode = GenGridSelectionMode.Always;
+                    break;
+                case GridViewSelectionMode.DisplayOnly:
+                    Control.SelectionMode = GenGridSelectionMode.DisplayOnly;
+                    break;
+                case GridViewSelectionMode.None:
+                    Control.SelectionMode = GenGridSelectionMode.None;
+                    break;
+            }
+        }
+
+        void UpdateSelectionMultiple()
+        {
+            Control.MultipleSelection = Element.CanSelectionMultiple;
+        }
+
+        void UpdateHighlight()
+        {
+            Control.IsHighlight = Element.IsHighlightEffectEnabled;
+        }
+
+        void UpdateFillItem()
+        {
+            Control.FillItems = Element.FillItems;
+        }
+
+        void UpdateOrientation()
+        {
+            Control.IsHorizontal = (Element.Orientation == GridViewOrientation.Horizontal);
+        }
+
+        void ItemSelectedHandler(object sender, GenGridItemEventArgs e)
+        {
+            GenGridItem item = e.Item;
+
+            if (item != null)
+            {
+                int index = -1;
+                index = Element.TemplatedItems.IndexOf((item.Data as ItemContext).Cell);
+                Element.NotifyItemSelected(index);
+            }
+        }
+
+        void ItemPressedHandler(object sender, GenGridItemEventArgs e)
+        {
+            GenGridItem item = e.Item;
+
+            if (item != null)
+            {
+                int index = -1;
+                index = Element.TemplatedItems.IndexOf((item.Data as ItemContext).Cell);
+                Element.NotifyItemTapped(index);
+            }
+        }
+
+        void OnScrollToRequested(object sender, GrdiViewScrollToRequestedEventArgs e)
+        {
+            var index = Element.TemplatedItems.GetGlobalIndexOfItem(e.Item);
+            var cell = Element.TemplatedItems[index];
+            GenGridItem item = GetItemContext(cell)?.Item as GenGridItem;
+            if (item != null)
+                Control.ScrollTo(item, e.Position.ToNative(), e.IsAnimated);
+        }
+
+        void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            if (e.Action == NotifyCollectionChangedAction.Add)
+            {
+                Cell before = null;
+                if (e.NewStartingIndex + e.NewItems.Count < Element.TemplatedItems.Count)
+                {
+                    before = Element.TemplatedItems[e.NewStartingIndex + e.NewItems.Count];
+                }
+                foreach (var data in e.NewItems)
+                {
+                    AddItem(data as Cell, before);
+                }
+            }
+            else if (e.Action == NotifyCollectionChangedAction.Remove)
+            {
+                foreach (var data in e.OldItems)
+                {
+                    ItemContext itemXContext = GetItemContext(data as Cell);
+                    itemXContext?.Item?.Delete();
+                }
+            }
+            else if (e.Action == NotifyCollectionChangedAction.Reset)
+            {
+                UpdateSource();
+            }
+        }
+
+        void UpdateSource()
+        {
+            Control.Clear();
+            AddSource(Element.TemplatedItems);
+        }
+
+        void AddSource(IEnumerable source, Cell beforeCell = null)
+        {
+            foreach (var data in source)
+            {
+                AddItem(data as Cell, beforeCell);
+            }
+        }
+
+        void AddItem(Cell cell, Cell beforeCell = null)
+        {
+            CellRenderer renderer = GetCellRenderer(cell);
+
+            ItemContext itemContext = new ItemContext();
+            itemContext.Cell = cell;
+            itemContext.Renderer = renderer;
+
+            if (beforeCell != null)
+            {
+                GenGridItem beforeItem = GetItemContext(beforeCell)?.Item as GenGridItem;
+                itemContext.Item = Control.InsertBefore(renderer.Class, itemContext, beforeItem);
+            }
+            else
+            {
+                itemContext.Item = Control.Append(renderer.Class, itemContext);
+            }
+
+            cell.PropertyChanged += OnCellPropertyChanged;
+            (cell as ICellController).ForceUpdateSizeRequested += OnForceUpdateSizeRequested;
+            itemContext.Item.Deleted += ItemDeletedHandler;
+            _itemContextList.Add(itemContext);
+        }
+
+        void ItemDeletedHandler(object sender, EventArgs e)
+        {
+            ItemContext itemContext = (sender as GenGridItem).Data as ItemContext;
+            if (itemContext.Cell != null)
+            {
+                itemContext.Cell.PropertyChanged -= OnCellPropertyChanged;
+                (itemContext.Cell as ICellController).ForceUpdateSizeRequested -= OnForceUpdateSizeRequested;
+            }
+            _itemContextList.Remove(itemContext);
+        }
+
+        Type LookupHandlerType(Type viewType)
+        {
+            Type type = viewType;
+
+            while (true)
+            {
+                if (_cellRendererMappingTable.ContainsKey(type))
+                    return _cellRendererMappingTable[type];
+
+                type = type.GetTypeInfo().BaseType;
+                if (type == null)
+                    break;
+            }
+            return null;
+        }
+
+        CellRenderer GetCellRenderer(Cell cell, bool isGroup = false)
+        {
+            Type type = cell.GetType();
+            var cache = _cellRendererCache;
+            if (cache.ContainsKey(type))
+                return cache[type];
+
+            var handlerType = LookupHandlerType(type);
+
+            if (handlerType == null)
+                throw new ArgumentNullException("Unsupported cell type");
+
+            return cache[type] = (CellRenderer)Activator.CreateInstance(handlerType);
+        }
+
+        ItemContext GetItemContext(Cell cell)
+        {
+            if (cell == null)
+            {
+                return null;
+            }
+            else
+            {
+                return _itemContextList.Find(X => X.Cell == cell);
+            }
+        }
+
+        void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            var cell = sender as Cell;
+            var context = GetItemContext(cell);
+            context.Renderer.SendCellPropertyChanged(cell, context.Item, e.PropertyName);
+        }
+
+        void OnForceUpdateSizeRequested(object sender, EventArgs e)
+        {
+            var cell = sender as Cell;
+            var itemContext = GetItemContext(cell);
+            if (itemContext.Item != null)
+                itemContext.Item.Update();
+        }
+
+        void ItemRealizedHandler(object sender, GenGridItemEventArgs evt)
+        {
+            ItemContext itemContext = (evt.Item.Data as ItemContext);
+
+            if (itemContext != null && itemContext.Cell != null)
+            {
+                (itemContext.Cell as ICellController).SendAppearing();
+            }
+        }
+
+        void ItemUnrealizedHandler(object sender, GenGridItemEventArgs evt)
+        {
+            ItemContext itemContext = (evt.Item.Data as ItemContext);
+            if (itemContext != null && itemContext.Cell != null)
+            {
+                (itemContext.Cell as ICellController).SendDisappearing();
+                itemContext.Renderer?.SendUnrealizedCell(itemContext.Cell);
+            }
+        }
+    }
+}
+
index e852d86..6c07bec 100755 (executable)
   <ItemGroup>\r
     <Compile Include="BackgroundRenderer.cs" />\r
     <Compile Include="Cells\DoubleLabelCellRenderer.cs" />\r
+    <Compile Include="Cells\GridViewCellRenderer.cs" />\r
+    <Compile Include="Cells\GridViewTypeCellRenderer.cs" />\r
     <Compile Include="Cells\MultilineCellRenderer.cs" />\r
     <Compile Include="Cells\TypeCellRenderer.cs" />\r
     <Compile Include="RadioButtonRenderer.cs" />\r
+    <Compile Include="GridViewRenderer.cs" />\r
     <Compile Include="TizenExtension.cs" />\r
     <Compile Include="Properties\AssemblyInfo.cs" />\r
   </ItemGroup>\r
diff --git a/Tizen.Xamarin.Forms.Extension/Cells/GridViewCell.cs b/Tizen.Xamarin.Forms.Extension/Cells/GridViewCell.cs
new file mode 100644 (file)
index 0000000..c23bc55
--- /dev/null
@@ -0,0 +1,148 @@
+using System;
+using Xamarin.Forms;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+    /// <summary>
+    /// This interface is for internal use by platform renderers.
+    /// </summary>
+    public interface IGridVeiwCell { }
+
+    /// <summary>
+    /// GridViewCell containing a developer-defined View.
+    /// <remarks>
+    /// </remarks>
+    /// </summary>
+    /// <br>
+    /// Type1GridViewCell
+    /// <table border=2 style="text-align:center;border-collapse:collapse;">
+    ///        <tr>
+    ///            <th height = 100 width = 100 bgcolor ="#5cda5e">(View)</th>
+    ///            <th height = 100 width = 100 bgcolor ="#5cda5e">(View)</th>
+    ///            <th height = 100 width = 100 bgcolor ="#5cda5e">(View)</th>
+    ///        </tr>
+    /// </table>
+    /// <br>
+    public class GridViewCell : ViewCell, IGridVeiwCell { }
+
+    /// <summary>
+    /// TypeGridViewCell containing Text, Content and Badge.
+    /// </summary>
+    /// <remarks>
+    /// TypeGridViewCell is a abstract class inherited from a cell.<br>
+    /// Type1GridViewCell Class and Type2GridViewCell Class are used to inherit this class.<br>
+    /// Properties are used equally and are only slightly different position.<br>
+    /// Each property is displayed in the specified position.<br>
+    /// The specified position is shown below.<br>
+    /// <br>
+    /// Type1GridViewCell
+    /// <table border=2 style="text-align:center;border-collapse:collapse;">
+    ///        <tr>
+    ///            <th height = 80 width = 100 bgcolor ="#5cd1d8"></th>
+    ///            <th height = 80 width = 100 bgcolor ="#5cd1d8"></th>
+    ///            <th height = 80 width = 100 bgcolor ="#5cd1d8"></th>
+    ///        </tr>
+    ///        <tr>
+    ///            <th height = 20 width = 100 style =" vertical-align:top;">Text</th>
+    ///            <th height = 20 width = 100 style =" vertical-align:top;">Text</th>
+    ///            <th height = 20 width = 100 style =" vertical-align:top;">Text</th>
+    ///        </tr>
+    /// </table>
+    /// <br>
+    /// Type2GridViewCell
+    /// <table border=2 style="text-align:bottom;border-collapse:collapse;">
+    ///        <tr>
+    ///            <th height = 100 width = 100 bgcolor ="#5cd1d8" style =" vertical-align:bottom;"> Text</th>
+    ///            <th height = 100 width = 100 bgcolor ="#5cd1d8" style =" vertical-align:bottom;"> Text</th>
+    ///            <th height = 100 width = 100 bgcolor ="#5cd1d8" style =" vertical-align:bottom;"> Text</th>
+    ///        </tr>
+    /// </table>
+    /// Type2GridViewCell is almost same with Type1GridViewCell except for the position of Text.
+    /// </remarks>
+    public abstract class TypeGridViewCell : Cell, IGridVeiwCell
+    {
+        public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(GridViewCell), "");
+
+        public static readonly BindableProperty ContentProperty = BindableProperty.Create("Content", typeof(string), typeof(GridViewCell), "");
+
+        public static readonly BindableProperty IsBadgeVisibleProperty = BindableProperty.Create("IsBadgeVisible", typeof(bool), typeof(GridViewCell), false);
+
+        public static readonly BindableProperty BadgeCountProperty = BindableProperty.Create("BadgeCount", typeof(int), typeof(GridViewCell), default(int));
+
+        /// <summary>
+        /// Gets or sets the the Text displayed as the content of Item.
+        /// </summary>
+        public string Text
+        {
+            get { return (string)GetValue(TextProperty); }
+            set { SetValue(TextProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the Comtent for Item.
+        /// </summary>
+        public string Content
+        {
+            get { return (string)GetValue(ContentProperty); }
+            set { SetValue(ContentProperty, value); }
+        }
+
+        /// <summary>
+        /// true or false, to indicate whether the badge is displayed on the right top side of the item
+        /// </summary>
+        public bool IsBadgeVisible
+        {
+            get { return (bool)GetValue(IsBadgeVisibleProperty); }
+            set { SetValue(IsBadgeVisibleProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the number that is displayed on the badge.
+        /// </summary>
+        public int BadgeCount
+        {
+            get { return (int)GetValue(BadgeCountProperty); }
+            set { SetValue(BadgeCountProperty, value); }
+        }
+    }
+    /// <summary>
+    /// Type1GridViewCell is a class inherited from a TypeGridViewCell.
+    /// </summary>
+    /// <remarks>
+    /// Each property is displayed in the specified position.<br>
+    /// The specified position is shown below.<br>
+    /// </remarks>
+    /// <br>
+    /// Type1GridViewCell
+    /// <table border=2 style="text-align:center;border-collapse:collapse;">
+    ///        <tr>
+    ///            <th height = 80 width = 100 bgcolor ="#5cd1d8"></th>
+    ///            <th height = 80 width = 100 bgcolor ="#5cd1d8"></th>
+    ///            <th height = 80 width = 100 bgcolor ="#5cd1d8"></th>
+    ///        </tr>
+    ///        <tr>
+    ///            <th height = 20 width = 100 style =" vertical-align:top;">Text</th>
+    ///            <th height = 20 width = 100 style =" vertical-align:top;">Text</th>
+    ///            <th height = 20 width = 100 style =" vertical-align:top;">Text</th>
+    ///        </tr>
+    /// </table>
+    public class Type1GridViewCell : TypeGridViewCell { }
+
+    /// <summary>
+    /// Type2GridViewCell is a class inherited from a TypeGridViewCell.
+    /// </summary>
+    /// <remarks>
+    /// Each property is displayed in the specified position.<br>
+    /// The specified position is shown below.<br>
+    /// </remarks>
+    /// <br>
+    /// Type2GridViewCell
+    /// <table border=2 style="text-align:bottom;border-collapse:collapse;">
+    ///        <tr>
+    ///            <th height = 100 width = 100 bgcolor ="#5cd1d8" style =" vertical-align:bottom;"> Text</th>
+    ///            <th height = 100 width = 100 bgcolor ="#5cd1d8" style =" vertical-align:bottom;"> Text</th>
+    ///            <th height = 100 width = 100 bgcolor ="#5cd1d8" style =" vertical-align:bottom;"> Text</th>
+    ///        </tr>
+    /// </table>
+    public class Type2GridViewCell : TypeGridViewCell { }
+}
\ No newline at end of file
diff --git a/Tizen.Xamarin.Forms.Extension/EnumerableExtensions.cs b/Tizen.Xamarin.Forms.Extension/EnumerableExtensions.cs
new file mode 100644 (file)
index 0000000..ee814d9
--- /dev/null
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Collections;
+using Xamarin.Forms;
+using Xamarin.Forms.Internals;
+using System.Collections.Specialized;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+    internal static class EnumerableExtensions
+    {
+        internal static int IndexOf<T>(this IEnumerable<T> enumerable, T item)
+        {
+            if (enumerable == null)
+                throw new ArgumentNullException("enumerable");
+
+            var i = 0;
+            foreach (T element in enumerable)
+            {
+                if (Equals(element, item))
+                    return i;
+
+                i++;
+            }
+
+            return -1;
+        }
+
+        public static NotifyCollectionChangedEventArgsEx WithCount(this NotifyCollectionChangedEventArgs e, int count)
+        {
+            switch (e.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Add, e.NewItems, e.NewStartingIndex);
+
+                case NotifyCollectionChangedAction.Remove:
+                    return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Remove, e.OldItems, e.OldStartingIndex);
+
+                case NotifyCollectionChangedAction.Move:
+                    return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Move, e.OldItems, e.NewStartingIndex, e.OldStartingIndex);
+
+                case NotifyCollectionChangedAction.Replace:
+                    return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Replace, e.NewItems, e.OldItems, e.OldStartingIndex);
+
+                default:
+                    return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Reset);
+            }
+        }
+
+        public static object CreateContent(this DataTemplate self, object item, BindableObject container)
+        {
+            var selector = self as DataTemplateSelector;
+            if (selector != null)
+            {
+                self = selector.SelectTemplate(item, container);
+            }
+            return self.CreateContent();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Tizen.Xamarin.Forms.Extension/GridView.cs b/Tizen.Xamarin.Forms.Extension/GridView.cs
new file mode 100644 (file)
index 0000000..8083283
--- /dev/null
@@ -0,0 +1,321 @@
+using System;
+using System.Collections;
+using System.Diagnostics;
+using Xamarin.Forms;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+    /// <summary>
+    /// An ItemsView that displays a collection of contents like a table.
+    /// </summary>
+    /// <example>
+    /// <code>
+    /// public class MyObject
+    /// {
+    ///     public string Text { get; set; }
+    ///     public string ImagePath { get; set; }
+    ///     public bool IsBadgeVisible { get; set; }
+    ///     public int Count { get; set; }
+    /// }
+    /// public class GridViewTest : ContentPage
+    /// {
+    ///     public GridViewTest()
+    ///     {
+    ///         List<MyObject> MyData = new List<MyObject>();
+    ///         for (int i = 0; i < 50; i++)
+    ///         {
+    ///             MyData.Add(new MyObject { Text = "AAAA", ImagePath = "1.png", IsBadgeVisible = false, Count = 0);
+    ///             MyData.Add(new MyObject { Text = "BBBB", ImagePath = "1.png", IsBadgeVisible = false, Count = 0);
+    ///             MyData.Add(new MyObject { Text = "CCCC", ImagePath = "1.png", IsBadgeVisible = false, Count = 0);
+    ///             MyData.Add(new MyObject { Text = "DDDD", ImagePath = "1.png", IsBadgeVisible = false, Count = 0);
+    ///         }
+    ///         GridView view = new GridView
+    ///         {
+    ///             ItemColumn = new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) },
+    ///             ItemRow = new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) },
+    ///             SelectionMode = GridViewSelectionMode.Default,
+    ///             CanSelectMultiple = true,
+    ///             IsHighlightEffectEnabled = true,
+    ///             ColumnSpacing = 0.5,
+    ///             RowSpacing = 0.5,
+    ///             ItemsSource = MyData,
+    ///             ItemTemplate = new DataTemplate(() =>
+    ///             {
+    ///                 Type1GridViewCell typeCell = new Type1GridViewCell();
+    ///                 typeCell.SetBinding(Type1GridViewCell.TextProperty, new Binding("Text"));
+    ///                 typeCell.SetBinding(Type1GridViewCell.ContentProperty, new Binding("ImagePath"));
+    ///                 typeCell.SetBinding(Type1GridViewCell.IsBadgeVisibleProperty, new Binding("IsBadgeVisible"));
+    ///                 typeCell.SetBinding(Type1GridViewCell.BadgeCountProperty, new Binding("Count"));
+    ///                 return typeCell;
+    ///             })
+    ///         };
+    ///         view.ItemSelected += (sender, e) =>
+    ///         {
+    ///             MyObject item = (MyObject)e.SelectedItem;
+    ///             item.Count++;
+    ///             if (item.Count > 0)
+    ///             {
+    ///                 item.IsBadgeVisible = true;
+    ///             }
+    ///         };
+    ///         this.Content = new StackLayout
+    ///         {
+    ///             Children =
+    ///             {
+    ///                 view
+    ///             }
+    ///         };
+    ///     }
+    /// }
+    /// </code>
+    /// </example>
+    public class GridView : ItemsView<Cell>, IListViewController
+    {
+        const int DefaultItemWidth = -1;
+        const int DefaultItemHeight = -1;
+
+        public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create("ColumnSpacing", typeof(double), typeof(GridView), default(double));
+
+        public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create("RowSpacing", typeof(double), typeof(GridView), default(double));
+
+        public static readonly BindableProperty ItemHeightProperty = BindableProperty.Create("ItemHeight", typeof(int), typeof(GridView), DefaultItemHeight);
+
+        public static readonly BindableProperty ItemWidthProperty = BindableProperty.Create("ItemWidth", typeof(int), typeof(GridView), DefaultItemWidth);
+
+        public static readonly BindableProperty SelectionModeProperty = BindableProperty.Create("SelectionMode", typeof(GridViewSelectionMode), typeof(GridView), GridViewSelectionMode.Default);
+
+        public static readonly BindableProperty CanSelectionMultipleProperty = BindableProperty.Create("CanSelectionMultiple", typeof(bool), typeof(GridView), false);
+
+        public static readonly BindableProperty IsHighlightEffectEnabledProperty = BindableProperty.Create("IsHighlight", typeof(bool), typeof(GridView), true);
+
+        public static readonly BindableProperty FillItemsProperty = BindableProperty.Create("FillItems", typeof(bool), typeof(GridView), default(bool));
+
+        public static readonly BindableProperty OrientationProperty = BindableProperty.Create("Orientation", typeof(GridViewOrientation), typeof(GridView), GridViewOrientation.Vertical);
+
+        public event EventHandler<ItemVisibilityEventArgs> ItemAppearing;
+
+        public event EventHandler<ItemVisibilityEventArgs> ItemDisappearing;
+
+        public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;
+
+        public event EventHandler<TappedItemChangedEventArgs> ItemTapped;
+
+        public event EventHandler<GrdiViewScrollToRequestedEventArgs> ScrollToRequested;
+
+        /// <summary>
+        /// Identifiers of the direction for which the GridView expands while placing its items.
+        /// </summary>
+        public GridViewOrientation Orientation
+        {
+            get { return (GridViewOrientation)GetValue(OrientationProperty); }
+            set { SetValue(OrientationProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value that indicates whether the content fills the area for item.
+        /// By default, this value is false, the item's grid is not filled and the content is centered with the alignment.
+        /// </summary>
+        public bool FillItems
+        {
+            get { return (bool)GetValue(FillItemsProperty); }
+            set { SetValue(FillItemsProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value that indicates whether the multiple selection is allowed.
+        /// By default, the value is false.
+        /// </summary>
+        public bool CanSelectionMultiple
+        {
+            get { return (bool)GetValue(CanSelectionMultipleProperty); }
+            set { SetValue(CanSelectionMultipleProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value that indicates whether the highlight effect is enabled.
+        /// </summary>
+        public bool IsHighlightEffectEnabled
+        {
+            get { return (bool)GetValue(IsHighlightEffectEnabledProperty); }
+            set { SetValue(IsHighlightEffectEnabledProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the height of the items.
+        /// </summary>
+        public int ItemHeight
+        {
+            get { return (int)GetValue(ItemHeightProperty); }
+            set { SetValue(ItemHeightProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the width of the items.
+        /// </summary>
+        public int ItemWidth
+        {
+            get { return (int)GetValue(ItemWidthProperty); }
+            set { SetValue(ItemWidthProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets colum spacing of the GridView.
+        /// </summary>
+        public double ColumnSpacing
+        {
+            get { return (double)GetValue(ColumnSpacingProperty); }
+            set { SetValue(ColumnSpacingProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets row spacing of the GridView.
+        /// </summary>
+        public double RowSpacing
+        {
+            get { return (double)GetValue(RowSpacingProperty); }
+            set { SetValue(RowSpacingProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets select mode.
+        /// </summary>
+        public GridViewSelectionMode SelectionMode
+        {
+            get { return (GridViewSelectionMode)GetValue(SelectionModeProperty); }
+            set { SetValue(SelectionModeProperty, value); }
+        }
+
+        /// <summary>
+        /// Creates and initializes a new instance of the GridView class.
+        /// </summary>
+        public GridView() : base()
+        {
+            VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand;
+        }
+
+        /// <summary>
+        /// Scrolls the GridView to the item.
+        /// </summary>
+        /// <param name="item">The item from your GridView.ItemsSource to scroll to.</param>
+        /// <param name="position">How the item should be positioned on screen.</param>
+        /// <param name="animated">Whether or not the scroll should be animated.</param>
+        public void ScrollTo(object item, ScrollToPosition position, bool animated)
+        {
+            if (!Enum.IsDefined(typeof(ScrollToPosition), position))
+                throw new ArgumentException("position is not a valid ScrollToPosition", "position");
+
+            var args = new GrdiViewScrollToRequestedEventArgs(item, position, animated);
+            OnScrollToRequested(args);
+        }
+
+        protected override Cell CreateDefault(object item)
+        {
+            return new Type1GridViewCell { Text = item.ToString() };
+        }
+
+        protected override void SetupContent(Cell content, int index)
+        {
+            base.SetupContent(content, index);
+            content.Parent = this;
+        }
+
+        protected override void UnhookContent(Cell content)
+        {
+            base.UnhookContent(content);
+            content.Parent = null;
+        }
+
+        internal void NotifyItemSelected(int index)
+        {
+            var cell = TemplatedItems[index];
+            if (cell != null)
+            {
+                ItemSelected?.Invoke(this, new SelectedItemChangedEventArgs(cell.BindingContext));
+            }
+        }
+
+        internal void NotifyItemTapped(int index)
+        {
+            var cell = TemplatedItems[index];
+            if (cell != null)
+            {
+                //It can't invoke tapped event in Cell.
+                ItemTapped?.Invoke(this, new TappedItemChangedEventArgs(cell.BindingContext));
+            }
+        }
+
+        void OnScrollToRequested(GrdiViewScrollToRequestedEventArgs e)
+        {
+            EventHandler<GrdiViewScrollToRequestedEventArgs> handler = ScrollToRequested;
+            if (handler != null)
+                handler(this, e);
+        }
+
+        Cell IListViewController.CreateDefaultCell(object item)
+        {
+            return CreateDefault(item);
+        }
+
+        void IListViewController.SendCellAppearing(Cell cell)
+        {
+            EventHandler<ItemVisibilityEventArgs> handler = ItemAppearing;
+            if (handler != null)
+                handler(this, new ItemVisibilityEventArgs(cell.BindingContext));
+        }
+
+        void IListViewController.SendCellDisappearing(Cell cell)
+        {
+            EventHandler<ItemVisibilityEventArgs> handler = ItemDisappearing;
+            if (handler != null)
+                handler(this, new ItemVisibilityEventArgs(cell.BindingContext));
+        }
+
+        // Below Properties and methods are NOT used in GridView.
+        // For interacting with Cell(Xamarin.Form.Core) implementing IListViewController is needed,
+        // but these are unnecessary in GridView.
+        #region IListViewController
+
+        event EventHandler<ScrollToRequestedEventArgs> IListViewController.ScrollToRequested
+        {
+            add
+            {
+                Debug.WriteLine("");
+            }
+
+            remove
+            {
+                Debug.WriteLine("");
+            }
+        }
+
+        Element IListViewController.FooterElement { get; }
+
+        Element IListViewController.HeaderElement { get; }
+
+        bool IListViewController.RefreshAllowed { get; }
+
+        ListViewCachingStrategy IListViewController.CachingStrategy { get; }
+
+        void IListViewController.SendRefreshing()
+        {
+            throw new NotImplementedException();
+        }
+
+        string IListViewController.GetDisplayTextFromGroup(object cell)
+        {
+            throw new NotImplementedException();
+        }
+
+        void IListViewController.NotifyRowTapped(int index, int inGroupIndex, Cell cell)
+        {
+            throw new NotImplementedException();
+        }
+
+        void IListViewController.NotifyRowTapped(int index, Cell cell)
+        {
+            throw new NotImplementedException();
+        }
+
+        #endregion
+    }
+}
diff --git a/Tizen.Xamarin.Forms.Extension/GridViewEnums.cs b/Tizen.Xamarin.Forms.Extension/GridViewEnums.cs
new file mode 100644 (file)
index 0000000..4499467
--- /dev/null
@@ -0,0 +1,43 @@
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+    /// <summary>
+    /// Enumeration of identifiers for item's select mode.
+    /// </summary>
+    public enum GridViewSelectionMode
+    {
+        /// <summary>
+        /// The item will only call their selection func and callback when first becoming selected.<br>
+        /// Any further clicks will do nothing, unless you set always select mode.
+        /// </summary>
+        Default,
+        /// <summary>
+        /// This means that, even if selected, every click calls the selected callbacks.
+        /// </summary>
+        Always,
+        /// <summary>
+        /// This turns off the ability to select the item entirely and they neither appear selected nor call selected callback functions.
+        /// </summary>
+        None,
+        /// <summary>
+        /// This applies the no-finger-size rule(Clickable objects should be bigger than human touch point device (your finger) for some touch or small screen devices.) with "None". 
+        /// So it is enabled, the item can be shrink than predefined finger-size value. And the item will be updated.
+        /// </summary>
+        DisplayOnly
+    }
+
+    /// <summary>
+    /// Enumeration of identifiers of the direction for which the GridView expands while placing its items.
+    /// </summary>
+    public enum GridViewOrientation
+    {
+        /// <summary>
+        /// The GridView expands hotizontally.
+        /// </summary>
+        Horizontal,
+        /// <summary>
+        /// The GridView expands vertically.
+        /// </summary>
+        Vertical
+    }
+}
\ No newline at end of file
diff --git a/Tizen.Xamarin.Forms.Extension/GridViewEventArgs.cs b/Tizen.Xamarin.Forms.Extension/GridViewEventArgs.cs
new file mode 100644 (file)
index 0000000..58fd25b
--- /dev/null
@@ -0,0 +1,49 @@
+using System;
+using Xamarin.Forms;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+    /// <summary>
+    /// Arguments for the event that is raised when a scroll is requested.
+    /// </summary>
+    public class GrdiViewScrollToRequestedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// An element to scroll to.
+        /// </summary>
+        public object Item { get; private set; }
+
+        /// <summary>
+        /// An enumeration value that describes which part of an element to scroll to.
+        /// </summary>
+        public ScrollToPosition Position { get; private set; }
+
+        /// <summary>
+        /// Gets a value that tells whether the scroll operation should be animated.
+        /// </summary>
+        public bool IsAnimated { get; private set; }
+
+        internal GrdiViewScrollToRequestedEventArgs(object item, ScrollToPosition position, bool animated)
+        {
+            Item = item;
+            Position = position;
+            IsAnimated = animated;
+        }
+    }
+
+    /// <summary>
+    /// Arguments for the event that is raised when item is tapped.
+    /// </summary>
+    public class TappedItemChangedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// An element that is tapped.
+        /// </summary>
+        public object TappedItem { get; }
+
+        internal TappedItemChangedEventArgs(object item)
+        {
+            TappedItem = item;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Tizen.Xamarin.Forms.Extension/ItemsView.cs b/Tizen.Xamarin.Forms.Extension/ItemsView.cs
new file mode 100644 (file)
index 0000000..f5c9a3f
--- /dev/null
@@ -0,0 +1,84 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Xamarin.Forms;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+    /// <summary>
+    /// A base class for a view that contains a templated list of items.<br>
+    /// This class is for internal use by platfrom renderer.
+    /// </summary>
+    /// <typeparam name="TVisual">The type of visual that the ItemsView<TVisual>.ItemsSource items will be templated into.</typeparam>
+    public abstract class ItemsView<TVisual> : View, ITemplatedItemsView<TVisual> where TVisual : BindableObject
+    {
+        public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(ItemsView<TVisual>), null, propertyChanged: OnItemsSourceChanged);
+
+        public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(ItemsView<TVisual>), null);
+        /// <summary>
+        /// Gets or sets the source of items to template and display.
+        /// </summary>
+        public IEnumerable ItemsSource
+        {
+            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
+            set { SetValue(ItemsSourceProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the DataTemplate to apply to the ItemsView<TVisual>.ItemsSource.
+        /// </summary>
+        public DataTemplate ItemTemplate
+        {
+            get { return (DataTemplate)GetValue(ItemTemplateProperty); }
+            set { SetValue(ItemTemplateProperty, value); }
+        }
+
+        IListProxy ITemplatedItemsView<TVisual>.ListProxy
+        {
+            get { return TemplatedItems.ListProxy; }
+        }
+
+        ITemplatedItemsList<TVisual> ITemplatedItemsView<TVisual>.TemplatedItems { get { return TemplatedItems; } }
+
+        internal TemplatedItemsList<ItemsView<TVisual>, TVisual> TemplatedItems { get; }
+
+        internal ItemsView()
+        {
+            TemplatedItems = new TemplatedItemsList<ItemsView<TVisual>, TVisual>(this, ItemsSourceProperty, ItemTemplateProperty);
+        }
+
+        TVisual IItemsView<TVisual>.CreateDefault(object item)
+        {
+            return CreateDefault(item);
+        }
+
+        protected abstract TVisual CreateDefault(object item);
+
+        protected virtual void SetupContent(TVisual content, int index)
+        {
+        }
+
+        protected virtual void UnhookContent(TVisual content)
+        {
+        }
+
+        void IItemsView<TVisual>.SetupContent(TVisual content, int index)
+        {
+            SetupContent(content, index);
+        }
+
+        void IItemsView<TVisual>.UnhookContent(TVisual content)
+        {
+            UnhookContent(content);
+        }
+
+        static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
+        {
+            var element = newValue as Element;
+            if (element == null)
+                return;
+            element.Parent = (Element)bindable;
+        }
+    }
+}
diff --git a/Tizen.Xamarin.Forms.Extension/ListProxy.cs b/Tizen.Xamarin.Forms.Extension/ListProxy.cs
new file mode 100644 (file)
index 0000000..d0593e3
--- /dev/null
@@ -0,0 +1,455 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using Xamarin.Forms;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+    internal sealed class ListProxy : IReadOnlyList<object>, IListProxy, INotifyCollectionChanged
+    {
+        readonly ICollection _collection;
+        readonly IList _list;
+        readonly int _windowSize;
+
+        IEnumerator _enumerator;
+        int _enumeratorIndex;
+
+        bool _finished;
+        HashSet<int> _indexesCounted;
+
+        Dictionary<int, object> _items;
+        int _version;
+
+        int _windowIndex;
+
+        public event NotifyCollectionChangedEventHandler CollectionChanged;
+        public event EventHandler CountChanged;
+
+        public IEnumerable ProxiedEnumerable { get; }
+
+        public int Count
+        {
+            get
+            {
+                if (_collection != null)
+                    return _collection.Count;
+
+                EnsureWindowCreated();
+
+                if (_indexesCounted != null)
+                    return _indexesCounted.Count;
+
+                return 0;
+            }
+        }
+
+        public object this[int index]
+        {
+            get
+            {
+                object value;
+                if (!TryGetValue(index, out value))
+                    throw new ArgumentOutOfRangeException("index");
+
+                return value;
+            }
+        }
+
+        internal ListProxy(IEnumerable enumerable, int windowSize = int.MaxValue)
+        {
+            _windowSize = windowSize;
+
+            ProxiedEnumerable = enumerable;
+            _collection = enumerable as ICollection;
+
+            if (_collection == null && enumerable is IReadOnlyCollection<object>)
+                _collection = new ReadOnlyListAdapter((IReadOnlyCollection<object>)enumerable);
+
+            _list = enumerable as IList;
+            if (_list == null && enumerable is IReadOnlyList<object>)
+                _list = new ReadOnlyListAdapter((IReadOnlyList<object>)enumerable);
+
+            var changed = enumerable as INotifyCollectionChanged;
+            if (changed != null)
+                new WeakNotifyProxy(this, changed);
+        }
+
+        public IEnumerator<object> GetEnumerator()
+        {
+            return new ProxyEnumerator(this);
+        }
+
+        public bool Contains(object item)
+        {
+            if (_list != null)
+                return _list.Contains(item);
+
+            EnsureWindowCreated();
+
+            if (_items != null)
+                return _items.Values.Contains(item);
+
+            return false;
+        }
+
+        public int IndexOf(object item)
+        {
+            if (_list != null)
+                return _list.IndexOf(item);
+
+            EnsureWindowCreated();
+
+            if (_items != null)
+            {
+                foreach (KeyValuePair<int, object> kvp in _items)
+                {
+                    if (Equals(kvp.Value, item))
+                        return kvp.Key;
+                }
+            }
+
+            return -1;
+        }
+
+        public void Clear()
+        {
+            _version++;
+            _finished = false;
+            _windowIndex = 0;
+            _enumeratorIndex = 0;
+
+            if (_enumerator != null)
+            {
+                var dispose = _enumerator as IDisposable;
+                if (dispose != null)
+                    dispose.Dispose();
+
+                _enumerator = null;
+            }
+
+            if (_items != null)
+                _items.Clear();
+            if (_indexesCounted != null)
+                _indexesCounted.Clear();
+
+            OnCountChanged();
+            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        void ClearRange(int index, int clearCount)
+        {
+            if (_items == null)
+                return;
+
+            for (int i = index; i < index + clearCount; i++)
+                _items.Remove(i);
+        }
+
+        bool CountIndex(int index)
+        {
+            if (_collection != null)
+                return false;
+
+            // A collection is used in case TryGetValue is called out of order.
+            if (_indexesCounted == null)
+                _indexesCounted = new HashSet<int>();
+
+            if (_indexesCounted.Contains(index))
+                return false;
+
+            _indexesCounted.Add(index);
+            return true;
+        }
+
+        void EnsureWindowCreated()
+        {
+            if (_items != null && _items.Count > 0)
+                return;
+
+            object value;
+            TryGetValue(0, out value);
+        }
+
+        void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            Action action;
+            if (_list == null)
+            {
+                action = Clear;
+            }
+            else
+            {
+                action = () =>
+                {
+                    _version++;
+                    OnCollectionChanged(e);
+                };
+            }
+            e = e.WithCount(Count);
+            action();
+        }
+
+        void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+        {
+            NotifyCollectionChangedEventHandler changed = CollectionChanged;
+            changed?.Invoke(this, e);
+        }
+
+        void OnCountChanged()
+        {
+            EventHandler changed = CountChanged;
+            changed?.Invoke(this, EventArgs.Empty);
+        }
+
+        bool TryGetValue(int index, out object value)
+        {
+            value = null;
+
+            if (_list != null)
+            {
+                object indexedValue = null;
+                var inRange = false;
+                Action getFromList = () =>
+                {
+                    if (index >= _list.Count)
+                        return;
+
+                    indexedValue = _list[index];
+                    inRange = true;
+                };
+
+                getFromList();
+
+                value = indexedValue;
+                return inRange;
+            }
+
+            if (_collection != null && index >= _collection.Count)
+                return false;
+            if (_items != null)
+            {
+                bool found = _items.TryGetValue(index, out value);
+                if (found || _finished)
+                    return found;
+            }
+
+            if (index >= _windowIndex + _windowSize)
+            {
+                int newIndex = index - _windowSize / 2;
+                ClearRange(_windowIndex, newIndex - _windowIndex);
+                _windowIndex = newIndex;
+            }
+            else if (index < _windowIndex)
+            {
+                int clearIndex = _windowIndex;
+                int clearSize = _windowSize;
+                if (clearIndex <= index + clearSize)
+                {
+                    int diff = index + clearSize - clearIndex;
+                    clearIndex += diff + 1;
+                    clearSize -= diff;
+                }
+
+                ClearRange(clearIndex, clearSize);
+                _windowIndex = 0;
+
+                var dispose = _enumerator as IDisposable;
+                if (dispose != null)
+                    dispose.Dispose();
+
+                _enumerator = null;
+                _enumeratorIndex = 0;
+            }
+
+            if (_enumerator == null)
+                _enumerator = ProxiedEnumerable.GetEnumerator();
+            if (_items == null)
+                _items = new Dictionary<int, object>();
+
+            var countChanged = false;
+            int end = _windowIndex + _windowSize;
+
+            for (; _enumeratorIndex < end; _enumeratorIndex++)
+            {
+                var moved = false;
+                Action move = () =>
+                {
+                    try
+                    {
+                        moved = _enumerator.MoveNext();
+                    }
+                    catch (InvalidOperationException ioex)
+                    {
+                        throw new InvalidOperationException("You must call UpdateNonNotifyingList() after updating a list that does not implement INotifyCollectionChanged", ioex);
+                    }
+
+                    if (!moved)
+                    {
+                        var dispose = _enumerator as IDisposable;
+                        if (dispose != null)
+                            dispose.Dispose();
+
+                        _enumerator = null;
+                        _enumeratorIndex = 0;
+                        _finished = true;
+                    }
+                };
+
+                move();
+
+                if (!moved)
+                    break;
+
+                if (CountIndex(_enumeratorIndex))
+                    countChanged = true;
+
+                if (_enumeratorIndex >= _windowIndex)
+                    _items.Add(_enumeratorIndex, _enumerator.Current);
+            }
+
+            if (countChanged)
+                OnCountChanged();
+
+            return _items.TryGetValue(index, out value);
+        }
+
+        class WeakNotifyProxy
+        {
+            readonly WeakReference<INotifyCollectionChanged> _weakCollection;
+            readonly WeakReference<ListProxy> _weakProxy;
+
+            public WeakNotifyProxy(ListProxy proxy, INotifyCollectionChanged incc)
+            {
+                incc.CollectionChanged += OnCollectionChanged;
+
+                _weakProxy = new WeakReference<ListProxy>(proxy);
+                _weakCollection = new WeakReference<INotifyCollectionChanged>(incc);
+            }
+
+            void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+            {
+                ListProxy proxy;
+                if (!_weakProxy.TryGetTarget(out proxy))
+                {
+                    INotifyCollectionChanged collection;
+                    if (_weakCollection.TryGetTarget(out collection))
+                        collection.CollectionChanged -= OnCollectionChanged;
+
+                    return;
+                }
+
+                proxy.OnCollectionChanged(sender, e);
+            }
+        }
+
+        class ProxyEnumerator : IEnumerator<object>
+        {
+            readonly ListProxy _proxy;
+            readonly int _version;
+
+            int _index;
+
+            public ProxyEnumerator(ListProxy proxy)
+            {
+                _proxy = proxy;
+                _version = proxy._version;
+            }
+
+            public void Dispose()
+            {
+            }
+
+            public bool MoveNext()
+            {
+                if (_proxy._version != _version)
+                    throw new InvalidOperationException();
+
+                object value;
+                bool next = _proxy.TryGetValue(_index++, out value);
+                if (next)
+                    Current = value;
+
+                return next;
+            }
+
+            public void Reset()
+            {
+                _index = 0;
+                Current = null;
+            }
+
+            public object Current { get; private set; }
+        }
+
+        // Below properties and methods are NOT used in GridView.
+        // For porting ItemsView from Xamarin.Forms, implementing IListProxy(Xamarin.Form) is needed for type checking,
+        // but these are unnecessary in GridView.
+        #region IListProxy
+
+        bool ICollection.IsSynchronized
+        {
+            get { return false; }
+        }
+
+        object ICollection.SyncRoot
+        {
+            get { throw new NotSupportedException(); }
+        }
+
+        void ICollection.CopyTo(Array array, int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        object IList.this[int index]
+        {
+            get { return this[index]; }
+            set { throw new NotSupportedException(); }
+        }
+
+        bool IList.IsReadOnly
+        {
+            get { return true; }
+        }
+
+        bool IList.IsFixedSize
+        {
+            get { return false; }
+        }
+
+        int IList.Add(object item)
+        {
+            throw new NotSupportedException();
+        }
+
+        void IList.Remove(object item)
+        {
+            throw new NotSupportedException();
+        }
+
+        void IList.Insert(int index, object item)
+        {
+            throw new NotSupportedException();
+        }
+
+        void IList.RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        void IList.Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        #endregion
+    }
+}
+
index c0742f8..b298784 100644 (file)
@@ -11,3 +11,5 @@ using System.Runtime.CompilerServices;
 [assembly: AssemblyTrademark("")]
 [assembly: CompilationRelaxations(8)]
 [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
+
+[assembly: InternalsVisibleTo("Tizen.Xamarin.Forms.Extension.Renderer")]
\ No newline at end of file
diff --git a/Tizen.Xamarin.Forms.Extension/ReadOnlyListAdapter.cs b/Tizen.Xamarin.Forms.Extension/ReadOnlyListAdapter.cs
new file mode 100644 (file)
index 0000000..fca2c15
--- /dev/null
@@ -0,0 +1,106 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+    internal sealed class ReadOnlyListAdapter : IList
+    {
+        readonly IReadOnlyCollection<object> _collection;
+        readonly IReadOnlyList<object> _list;
+
+        public ReadOnlyListAdapter(IReadOnlyList<object> list)
+        {
+            _list = list;
+            _collection = list;
+        }
+
+        public ReadOnlyListAdapter(IReadOnlyCollection<object> collection)
+        {
+            _collection = collection;
+        }
+
+        // Below properties and methods are NOT used in GridView.
+        // For porting ItemsView from Xamarin.Forms, implementing IList is needed for type checking,
+        // but these are unnecessary in GridView.
+        #region IList
+
+        public void CopyTo(Array array, int index)
+        {
+            throw new NotImplementedException();
+        }
+
+        public int Count
+        {
+            get { return _collection.Count; }
+        }
+
+        public bool IsSynchronized
+        {
+            get { throw new NotImplementedException(); }
+        }
+
+        public object SyncRoot
+        {
+            get { throw new NotImplementedException(); }
+        }
+
+        public IEnumerator GetEnumerator()
+        {
+            return _collection.GetEnumerator();
+        }
+
+        public int Add(object value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool Contains(object value)
+        {
+            return _list.Contains(value);
+        }
+
+        public int IndexOf(object value)
+        {
+            return _list.IndexOf(value);
+        }
+
+        public void Insert(int index, object value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool IsFixedSize
+        {
+            get { throw new NotImplementedException(); }
+        }
+
+        public bool IsReadOnly
+        {
+            get { return true; }
+        }
+
+        public object this[int index]
+        {
+            get { return _list[index]; }
+            set { throw new NotImplementedException(); }
+        }
+
+        public void Remove(object value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotImplementedException();
+        }
+        #endregion
+    }
+}
\ No newline at end of file
diff --git a/Tizen.Xamarin.Forms.Extension/TemplatedItemsList.cs b/Tizen.Xamarin.Forms.Extension/TemplatedItemsList.cs
new file mode 100644 (file)
index 0000000..e2ed81f
--- /dev/null
@@ -0,0 +1,630 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using Xamarin.Forms;
+using Xamarin.Forms.Internals;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+    internal sealed class TemplatedItemsList<TView, TItem> : BindableObject, ITemplatedItemsList<TItem>, IList, IDisposable
+                                                where TView : BindableObject, IItemsView<TItem>
+                                                where TItem : BindableObject
+    {
+        internal static readonly BindablePropertyKey ListProxyPropertyKey = BindableProperty.CreateReadOnly("ListProxy", typeof(ListProxy), typeof(TemplatedItemsList<TView, TItem>), null,
+            propertyChanged: OnListProxyChanged);
+
+        static readonly BindableProperty IndexProperty = BindableProperty.Create("Index", typeof(int), typeof(TItem), -1);
+
+        readonly BindableProperty _itemSourceProperty;
+        readonly BindableProperty _itemTemplateProperty;
+
+        readonly TView _itemsView;
+
+        readonly List<TItem> _templatedObjects = new List<TItem>();
+
+        bool _disposed;
+
+        internal TemplatedItemsList(TView itemsView, BindableProperty itemSourceProperty, BindableProperty itemTemplateProperty)
+        {
+            if (itemsView == null)
+                throw new ArgumentNullException("itemsView");
+            if (itemSourceProperty == null)
+                throw new ArgumentNullException("itemSourceProperty");
+            if (itemTemplateProperty == null)
+                throw new ArgumentNullException("itemTemplateProperty");
+
+            _itemsView = itemsView;
+            _itemsView.PropertyChanged += BindableOnPropertyChanged;
+
+            _itemSourceProperty = itemSourceProperty;
+            _itemTemplateProperty = itemTemplateProperty;
+
+            IEnumerable source = GetItemsViewSource();
+            if (source != null)
+                ListProxy = new ListProxy(source);
+            else
+                ListProxy = new ListProxy(new object[0]);
+        }
+
+        internal TemplatedItemsList(TemplatedItemsList<TView, TItem> parent, IEnumerable itemSource, TView itemsView, BindableProperty itemTemplateProperty, int windowSize = int.MaxValue)
+        {
+            if (itemsView == null)
+                throw new ArgumentNullException("itemsView");
+            if (itemTemplateProperty == null)
+                throw new ArgumentNullException("itemTemplateProperty");
+
+            Parent = parent;
+
+            _itemsView = itemsView;
+            _itemsView.PropertyChanged += BindableOnPropertyChanged;
+            _itemTemplateProperty = itemTemplateProperty;
+
+            if (itemSource != null)
+            {
+                ListProxy = new ListProxy(itemSource, windowSize);
+                ListProxy.CollectionChanged += OnProxyCollectionChanged;
+            }
+            else
+                ListProxy = new ListProxy(new object[0]);
+        }
+
+        public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+        event PropertyChangedEventHandler ITemplatedItemsList<TItem>.PropertyChanged
+        {
+            add { PropertyChanged += value; }
+            remove { PropertyChanged -= value; }
+        }
+
+        public event NotifyCollectionChangedEventHandler GroupedCollectionChanged;
+
+        event NotifyCollectionChangedEventHandler ITemplatedItemsList<TItem>.GroupedCollectionChanged
+        {
+            add { GroupedCollectionChanged += value; }
+            remove { GroupedCollectionChanged -= value; }
+        }
+
+        public int Count
+        {
+            get { return ListProxy.Count; }
+        }
+
+        public TItem this[int index]
+        {
+            get { return GetOrCreateContent(index, ListProxy[index]); }
+        }
+
+        object ITemplatedItemsList<TItem>.BindingContext
+        {
+            get { return BindingContext; }
+        }
+
+        IEnumerable ITemplatedItemsList<TItem>.ItemsSource
+        {
+            get { return ListProxy.ProxiedEnumerable; }
+        }
+
+        public TemplatedItemsList<TView, TItem> Parent { get; }
+
+        public BindableProperty ProgressiveLoadingProperty { get; set; }
+
+        public IListProxy ListProxy
+        {
+            get { return (IListProxy)GetValue(ListProxyPropertyKey.BindableProperty); }
+            private set { SetValue(ListProxyPropertyKey, value); }
+        }
+
+        IListProxy ITemplatedItemsList<TItem>.ListProxy
+        {
+            get { return ListProxy; }
+        }
+
+        DataTemplate ItemTemplate
+        {
+            get { return (DataTemplate)_itemsView.GetValue(_itemTemplateProperty); }
+        }
+
+        bool ProgressiveLoading
+        {
+            get { return (ProgressiveLoadingProperty != null) && (bool)_itemsView.GetValue(ProgressiveLoadingProperty); }
+        }
+
+        bool ICollection.IsSynchronized
+        {
+            get { return false; }
+        }
+
+        object ICollection.SyncRoot
+        {
+            get { return this; }
+        }
+
+        void ICollection.CopyTo(Array array, int arrayIndex)
+        {
+            throw new NotImplementedException();
+        }
+
+        bool IList.IsFixedSize
+        {
+            get { return false; }
+        }
+
+        bool IList.IsReadOnly
+        {
+            get { return true; }
+        }
+
+        object IList.this[int index]
+        {
+            get { return this[index]; }
+            set { throw new NotSupportedException(); }
+        }
+
+        int IList.Add(object item)
+        {
+            throw new NotSupportedException();
+        }
+
+        void IList.Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        bool IList.Contains(object item)
+        {
+            throw new NotImplementedException();
+        }
+
+        int IList.IndexOf(object item)
+        {
+            return IndexOf((TItem)item);
+        }
+
+        void IList.Insert(int index, object item)
+        {
+            throw new NotSupportedException();
+        }
+
+        void IList.Remove(object item)
+        {
+            throw new NotSupportedException();
+        }
+
+        void IList.RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        void IDisposable.Dispose()
+        {
+            if (_disposed)
+                return;
+
+            _itemsView.PropertyChanged -= BindableOnPropertyChanged;
+
+            for (var i = 0; i < _templatedObjects.Count; i++)
+            {
+                TItem item = _templatedObjects[i];
+                if (item != null)
+                    UnhookItem(item);
+            }
+
+            _disposed = true;
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public IEnumerator<TItem> GetEnumerator()
+        {
+            var i = 0;
+            foreach (object item in ListProxy)
+                yield return GetOrCreateContent(i++, item);
+        }
+
+        public int GetDescendantCount()
+        {
+            return Count;
+        }
+
+        public int GetGlobalIndexOfItem(object item)
+        {
+            return ListProxy.IndexOf(item);
+        }
+
+        public int IndexOf(TItem item)
+        {
+            return GetIndex(item);
+        }
+
+        TItem ITemplatedItemsList<TItem>.UpdateContent(TItem content, int index)
+        {
+            return UpdateContent(content, index);
+        }
+
+        internal TItem CreateContent(int index, object item, bool insert = false)
+        {
+            TItem content = ItemTemplate != null ? (TItem)ItemTemplate.CreateContent(item, _itemsView) : _itemsView.CreateDefault(item);
+
+            content = UpdateContent(content, index, item);
+
+            for (int i = _templatedObjects.Count; i <= index; i++)
+                _templatedObjects.Add(null);
+
+            if (!insert)
+                _templatedObjects[index] = content;
+            else
+                _templatedObjects.Insert(index, content);
+
+            return content;
+        }
+
+        int GetIndex(TItem item)
+        {
+            if (item == null)
+                throw new ArgumentNullException("item");
+
+            return (int)item.GetValue(IndexProperty);
+        }
+
+        TItem GetOrCreateContent(int index, object item)
+        {
+            TItem content;
+            if (_templatedObjects.Count <= index || (content = _templatedObjects[index]) == null)
+                content = CreateContent(index, item);
+
+            return content;
+        }
+
+        TItem UpdateContent(TItem content, int index, object item)
+        {
+            content.BindingContext = item;
+
+            SetIndex(content, index);
+
+            _itemsView.SetupContent(content, index);
+
+            return content;
+        }
+
+        TItem UpdateContent(TItem content, int index)
+        {
+            object item = ListProxy[index];
+            return UpdateContent(content, index, item);
+        }
+
+        void BindableOnPropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            if (_itemSourceProperty != null && e.PropertyName == _itemSourceProperty.PropertyName)
+                OnItemsSourceChanged();
+            else if (e.PropertyName == _itemTemplateProperty.PropertyName)
+                OnItemTemplateChanged();
+            else if (ProgressiveLoadingProperty != null && e.PropertyName == ProgressiveLoadingProperty.PropertyName)
+                OnInfiniteScrollingChanged();
+        }
+
+        IList ConvertContent(int startingIndex, IList items, bool forceCreate = false, bool setIndex = false)
+        {
+            var contentItems = new List<TItem>(items.Count);
+            for (var i = 0; i < items.Count; i++)
+            {
+                int index = i + startingIndex;
+                TItem content = !forceCreate ? GetOrCreateContent(index, items[i]) : CreateContent(index, items[i]);
+                if (setIndex)
+                    SetIndex(content, index);
+
+                contentItems.Add(content);
+            }
+
+            return contentItems;
+        }
+
+        IEnumerable GetItemsViewSource()
+        {
+            return (IEnumerable)_itemsView.GetValue(_itemSourceProperty);
+        }
+
+        void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+        {
+            NotifyCollectionChangedEventHandler changed = CollectionChanged;
+            if (changed != null)
+                changed(this, e);
+        }
+
+        void OnInfiniteScrollingChanged()
+        {
+            OnItemsSourceChanged();
+        }
+
+        void OnInnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            NotifyCollectionChangedEventHandler handler = GroupedCollectionChanged;
+            if (handler != null)
+                handler(sender, e);
+        }
+
+        void OnItemsSourceChanged(bool fromGrouping = false)
+        {
+            ListProxy.CollectionChanged -= OnProxyCollectionChanged;
+
+            IEnumerable itemSource = GetItemsViewSource();
+            if (itemSource == null)
+                ListProxy = new ListProxy(new object[0]);
+            else
+                ListProxy = new ListProxy(itemSource);
+
+            ListProxy.CollectionChanged += OnProxyCollectionChanged;
+            OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+        }
+
+        void OnItemTemplateChanged()
+        {
+            if (ListProxy.Count == 0)
+                return;
+
+            OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+        }
+
+        static void OnListProxyChanged(BindableObject bindable, object oldValue, object newValue)
+        {
+            var til = (TemplatedItemsList<TView, TItem>)bindable;
+            til.OnPropertyChanged("ItemsSource");
+        }
+
+        void OnProxyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            OnProxyCollectionChanged(sender, e, true);
+        }
+
+        void OnProxyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, bool fixWindows = true)
+        {
+            int count = Count;
+            var ex = e as NotifyCollectionChangedEventArgsEx;
+            if (ex != null)
+                count = ex.Count;
+
+            var maxindex = 0;
+            if (e.NewStartingIndex >= 0 && e.NewItems != null)
+                maxindex = Math.Max(maxindex, e.NewStartingIndex + e.NewItems.Count);
+            if (e.OldStartingIndex >= 0 && e.OldItems != null)
+                maxindex = Math.Max(maxindex, e.OldStartingIndex + e.OldItems.Count);
+            if (maxindex > _templatedObjects.Count)
+                _templatedObjects.InsertRange(_templatedObjects.Count, Enumerable.Repeat<TItem>(null, maxindex - _templatedObjects.Count));
+
+            switch (e.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    if (e.NewStartingIndex >= 0)
+                    {
+                        for (int i = e.NewStartingIndex; i < _templatedObjects.Count; i++)
+                            SetIndex(_templatedObjects[i], i + e.NewItems.Count);
+
+                        _templatedObjects.InsertRange(e.NewStartingIndex, Enumerable.Repeat<TItem>(null, e.NewItems.Count));
+
+                        IList items = ConvertContent(e.NewStartingIndex, e.NewItems, true, true);
+                        e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Add, items, e.NewStartingIndex);
+                    }
+                    else
+                    {
+                        goto case NotifyCollectionChangedAction.Reset;
+                    }
+
+                    break;
+
+                case NotifyCollectionChangedAction.Move:
+                    if (e.NewStartingIndex < 0 || e.OldStartingIndex < 0)
+                        goto case NotifyCollectionChangedAction.Reset;
+
+                    bool movingForward = e.OldStartingIndex < e.NewStartingIndex;
+
+                    if (movingForward)
+                    {
+                        int moveIndex = e.OldStartingIndex;
+                        for (int i = moveIndex + e.OldItems.Count; i <= e.NewStartingIndex; i++)
+                            SetIndex(_templatedObjects[i], moveIndex++);
+                    }
+                    else
+                    {
+                        for (var i = 0; i < e.OldStartingIndex - e.NewStartingIndex; i++)
+                        {
+                            TItem item = _templatedObjects[i + e.NewStartingIndex];
+                            if (item != null)
+                                SetIndex(item, GetIndex(item) + e.OldItems.Count);
+                        }
+                    }
+
+                    TItem[] itemsToMove = _templatedObjects.Skip(e.OldStartingIndex).Take(e.OldItems.Count).ToArray();
+
+                    _templatedObjects.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
+                    _templatedObjects.InsertRange(e.NewStartingIndex, itemsToMove);
+                    for (var i = 0; i < itemsToMove.Length; i++)
+                        SetIndex(itemsToMove[i], e.NewStartingIndex + i);
+
+                    e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Move, itemsToMove, e.NewStartingIndex, e.OldStartingIndex);
+                    break;
+
+                case NotifyCollectionChangedAction.Remove:
+                    if (e.OldStartingIndex >= 0)
+                    {
+                        int removeIndex = e.OldStartingIndex;
+                        for (int i = removeIndex + e.OldItems.Count; i < _templatedObjects.Count; i++)
+                            SetIndex(_templatedObjects[i], removeIndex++);
+
+                        var items = new TItem[e.OldItems.Count];
+                        for (var i = 0; i < items.Length; i++)
+                        {
+                            TItem item = _templatedObjects[e.OldStartingIndex + i];
+                            if (item == null)
+                                continue;
+
+                            UnhookItem(item);
+                            items[i] = item;
+                        }
+
+                        _templatedObjects.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
+                        e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Remove, items, e.OldStartingIndex);
+                    }
+                    else
+                    {
+                        goto case NotifyCollectionChangedAction.Reset;
+                    }
+                    break;
+
+                case NotifyCollectionChangedAction.Replace:
+                    if (e.NewStartingIndex >= 0)
+                    {
+                        IList oldItems = ConvertContent(e.NewStartingIndex, e.OldItems);
+                        IList newItems = ConvertContent(e.NewStartingIndex, e.NewItems, true, true);
+
+                        for (var i = 0; i < oldItems.Count; i++)
+                        {
+                            UnhookItem((TItem)oldItems[i]);
+                        }
+
+                        e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Replace, newItems, oldItems, e.NewStartingIndex);
+                    }
+                    else
+                    {
+                        goto case NotifyCollectionChangedAction.Reset;
+                    }
+
+                    break;
+
+                case NotifyCollectionChangedAction.Reset:
+                    e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Reset);
+                    UnhookAndClear();
+                    break;
+
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+
+            OnCollectionChanged(e);
+        }
+
+        static void SetIndex(TItem item, int index)
+        {
+            if (item == null)
+                return;
+
+            item.SetValue(IndexProperty, index);
+        }
+
+        void SplitCollectionChangedItems(NotifyCollectionChangedEventArgs e)
+        {
+            switch (e.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    if (e.NewStartingIndex < 0)
+                        goto default;
+
+                    for (var i = 0; i < e.NewItems.Count; i++)
+                        OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, e.NewItems[i], e.NewStartingIndex + i), false);
+
+                    break;
+
+                case NotifyCollectionChangedAction.Remove:
+                    if (e.OldStartingIndex < 0)
+                        goto default;
+
+                    for (var i = 0; i < e.OldItems.Count; i++)
+                        OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, e.OldItems[i], e.OldStartingIndex + i), false);
+
+                    break;
+
+                case NotifyCollectionChangedAction.Replace:
+                    if (e.OldStartingIndex < 0)
+                        goto default;
+
+                    for (var i = 0; i < e.OldItems.Count; i++)
+                        OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, e.NewItems[i], e.OldItems[i], e.OldStartingIndex + i), false);
+
+                    break;
+
+                default:
+                    OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset), false);
+                    break;
+            }
+        }
+
+        void UnhookAndClear()
+        {
+            for (var i = 0; i < _templatedObjects.Count; i++)
+            {
+                TItem item = _templatedObjects[i];
+                if (item == null)
+                    continue;
+
+                UnhookItem(item);
+            }
+
+            _templatedObjects.Clear();
+        }
+
+        void UnhookItem(TItem item)
+        {
+            SetIndex(item, -1);
+            _itemsView.UnhookContent(item);
+
+            item.BindingContext = null;
+        }
+
+        // Below properties and methods are NOT used in GridView.
+        // For porting ItemsView from Xamarin.Forms, implementing ITemplatedItemsList(Xamarin.Forms) is needed for type checking,
+        // but these are unnecessary in GridView.
+        #region ITemplatedItemsList
+
+        IReadOnlyList<string> ITemplatedItemsList<TItem>.ShortNames
+        {
+            get { throw new NotImplementedException(); }
+        }
+
+        string ITemplatedItemsList<TItem>.Name
+        {
+            get { throw new NotImplementedException(); }
+            set { throw new NotImplementedException(); }
+        }
+
+        TItem ITemplatedItemsList<TItem>.HeaderContent
+        {
+            get { throw new NotImplementedException(); }
+        }
+
+        int ITemplatedItemsList<TItem>.GetGlobalIndexForGroup(ITemplatedItemsList<TItem> group)
+        {
+            throw new NotImplementedException();
+        }
+
+        ITemplatedItemsList<TItem> ITemplatedItemsList<TItem>.GetGroup(int index)
+        {
+            throw new NotImplementedException();
+        }
+
+        Tuple<int, int> ITemplatedItemsList<TItem>.GetGroupAndIndexOfItem(object item)
+        {
+            throw new NotImplementedException();
+        }
+
+        Tuple<int, int> ITemplatedItemsList<TItem>.GetGroupAndIndexOfItem(object group, object item)
+        {
+            throw new NotImplementedException();
+        }
+
+        int ITemplatedItemsList<TItem>.GetGroupIndexFromGlobal(int globalIndex, out int leftOver)
+        {
+            throw new NotImplementedException();
+        }
+
+        TItem ITemplatedItemsList<TItem>.UpdateHeader(TItem content, int groupIndex)
+        {
+            throw new NotImplementedException();
+        }
+
+        #endregion
+    }
+}
\ No newline at end of file
index dfd1d65..5269638 100755 (executable)
   <ItemGroup>\r
     <Compile Include="Background.cs" />\r
     <Compile Include="BackgroundOptions.cs" />\r
+    <Compile Include="Cells\GridViewCell.cs" />\r
     <Compile Include="Cells\Type1Cell.cs" />\r
     <Compile Include="Cells\DoubleLabelCell.cs" />\r
     <Compile Include="Cells\Type2Cell.cs" />\r
     <Compile Include="Cells\BaseTypeCell.cs" />\r
     <Compile Include="Cells\MultilineCell.cs" />\r
+    <Compile Include="EnumerableExtensions.cs" />\r
+    <Compile Include="GridView.cs" />\r
+    <Compile Include="GridViewEnums.cs" />\r
+    <Compile Include="GridViewEventArgs.cs" />\r
+    <Compile Include="ItemsView.cs" />\r
+    <Compile Include="ListProxy.cs" />\r
     <Compile Include="Properties\AssemblyInfo.cs" />\r
     <Compile Include="RadioButton.cs" />\r
     <Compile Include="SelectedEventArgs.cs" />\r
+    <Compile Include="ReadOnlyListAdapter.cs" />\r
+    <Compile Include="TemplatedItemsList.cs" />\r
   </ItemGroup>\r
   <ItemGroup>\r
     <Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">\r
   <Target Name="AfterBuild">
   </Target>
   -->\r
-</Project>
\ No newline at end of file
+</Project>