Implements view update on NotifyCollectionChanged in collectionView. (#2957)
authorSangHyeon Jade Lee <sh10233.lee@samsung.com>
Thu, 29 Apr 2021 12:16:55 +0000 (21:16 +0900)
committerdongsug-song <35130733+dongsug-song@users.noreply.github.com>
Fri, 7 May 2021 05:58:58 +0000 (14:58 +0900)
* [NUI] Implements view layout update on NotifyCollectionChanged in
collectionView.

When Collection is updated,
View can pick the notify event with updated info.
this patch implement normal collection updates including

- single item inserted
- single item removed
- single item moved
- group insrted
- group removed
- group moved

and apply samples regarding new implemented feature.

* [NUI] Change parameter name to fix warnings

* [NUI] fix ahead patch typos

12 files changed:
src/Tizen.NUI.Components/Controls/RecyclerView/CollectionView.cs
src/Tizen.NUI.Components/Controls/RecyclerView/ICollectionChangedNotifier.cs
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableGroupedSource.cs
src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs
src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/ItemsLayouter.cs
src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs
src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewGridSample.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewLinearSample.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Gallery.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewGridGroupSample.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewLinearGroupSample.cs

index 1eddf02..2fdbc35 100755 (executable)
@@ -17,6 +17,7 @@ using System;
 using System.Linq;
 using System.Collections;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.Windows.Input;
 using System.ComponentModel;
 using Tizen.NUI.BaseComponents;
@@ -200,6 +201,20 @@ namespace Tizen.NUI.Components
             }
             set
             {
+                if (itemsSource != null)
+                {
+                    // Clearing old data!
+                    if (itemsSource is INotifyCollectionChanged prevNotifyCollectionChanged)
+                    {
+                        prevNotifyCollectionChanged.CollectionChanged -= CollectionChanged;
+                    }
+                    itemsLayouter.Clear();
+                    if (selectedItem != null) selectedItem = null;
+                    if (selectedItems != null)
+                    {
+                        selectedItems.Clear();
+                    }
+                }
 
                 itemsSource = value;
                 if (value == null)
@@ -208,6 +223,11 @@ namespace Tizen.NUI.Components
                     //layouter.Clear()
                     return;
                 }
+                if (itemsSource is INotifyCollectionChanged newNotifyCollectionChanged)
+                {
+                    newNotifyCollectionChanged.CollectionChanged += CollectionChanged;
+                }
+
                 if (InternalItemSource != null) InternalItemSource.Dispose();
                 InternalItemSource = ItemsSourceFactory.Create(this);
 
@@ -269,7 +289,8 @@ namespace Tizen.NUI.Components
                 {
                     if (layouterStyle != null)
                     {
-                        itemsLayouter.Padding = new Extents(layouterStyle.Padding);
+                        if (layouterStyle.Padding != null)
+                            itemsLayouter.Padding = new Extents(layouterStyle.Padding);
                     }
                 }
                 Init();
@@ -491,6 +512,22 @@ namespace Tizen.NUI.Components
 
         /// <inheritdoc/>
         [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyDataSetChanged()
+        {
+            if (selectedItem != null)
+            {
+                selectedItem = null;
+            }
+            if (selectedItems != null)
+            {
+                selectedItems.Clear();
+            }
+
+            base.NotifyDataSetChanged();
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
         {
             View nextFocusedView = null;
@@ -727,6 +764,7 @@ namespace Tizen.NUI.Components
         // Realize and Decorate the item.
         internal override RecyclerViewItem RealizeItem(int index)
         {
+            RecyclerViewItem item;
             if (index == 0 && Header != null)
             {
                 Header.Show();
@@ -751,17 +789,17 @@ namespace Tizen.NUI.Components
                     {
                         groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
 
-                        groupHeader.ParentItemsView = this;
                         groupHeader.Template = templ;
                         groupHeader.isGroupHeader = true;
                         groupHeader.isGroupFooter = false;
                         ContentContainer.Add(groupHeader);
                     }
+                    groupHeader.ParentItemsView = this;
                     groupHeader.Index = index;
                     groupHeader.ParentGroup = context;
                     groupHeader.BindingContext = context;
                     //group selection?
-                    return groupHeader;
+                    item = groupHeader;
                 }
                 else if (InternalItemSource.IsGroupFooter(index))
                 {
@@ -772,48 +810,48 @@ namespace Tizen.NUI.Components
                     {
                         groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this);
 
-                        groupFooter.ParentItemsView = this;
                         groupFooter.Template = templ;
                         groupFooter.isGroupHeader = false;
                         groupFooter.isGroupFooter = true;
                         ContentContainer.Add(groupFooter);
                     }
+                    groupFooter.ParentItemsView = this;
                     groupFooter.Index = index;
                     groupFooter.ParentGroup = context;
                     groupFooter.BindingContext = context;
 
                     //group selection?
-                    return groupFooter;
+                    item = groupFooter;
                 }
-            }
-
-            RecyclerViewItem item = base.RealizeItem(index);
-            if (item != null)
-            {
-                if (isGrouped)
+                else
                 {
+                    item = base.RealizeItem(index);
                     item.ParentGroup = InternalItemSource.GetGroupParent(index);
                 }
+            }
+            else
+            {
+                item = base.RealizeItem(index);
+            }
 
-                switch (SelectionMode)
-                {
-                    case ItemSelectionMode.SingleSelection:
-                        if (item.BindingContext != null && item.BindingContext == SelectedItem)
-                        {
-                            item.IsSelected = true;
-                        }
-                        break;
+            switch (SelectionMode)
+            {
+                case ItemSelectionMode.SingleSelection:
+                    if (item.BindingContext != null && item.BindingContext == SelectedItem)
+                    {
+                        item.IsSelected = true;
+                    }
+                    break;
 
-                    case ItemSelectionMode.MultipleSelections:
-                        if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
-                        {
-                            item.IsSelected = true;
-                        }
-                        break;
-                    case ItemSelectionMode.None:
-                        item.IsSelectable = false;
-                        break;
-                }
+                case ItemSelectionMode.MultipleSelections:
+                    if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
+                    {
+                        item.IsSelected = true;
+                    }
+                    break;
+                case ItemSelectionMode.None:
+                    item.IsSelectable = false;
+                    break;
             }
             return item;
         }
@@ -833,12 +871,19 @@ namespace Tizen.NUI.Components
             }
             if (item.isGroupHeader || item.isGroupFooter)
             {
+                item.Index = -1;
+                item.ParentItemsView = null;
+                item.BindingContext = null; 
+                item.IsPressed = false;
+                item.IsSelected = false;
+                item.IsEnabled = true;
+                item.UpdateState();
+                //item.Relayout -= OnItemRelayout;
                 if (!recycle || !PushRecycleGroupCache(item))
                     Utility.Dispose(item);
                 return;
             }
 
-            item.IsSelected = false;
             base.UnrealizeItem(item, recycle);
         }
 
@@ -904,6 +949,7 @@ namespace Tizen.NUI.Components
                 ItemsLayouter.Initialize(this);
                 needInitalizeLayouter = false;
             }
+
             base.OnScrolling(source, args);
         }
 
@@ -921,6 +967,10 @@ namespace Tizen.NUI.Components
             if (type == DisposeTypes.Explicit)
             {
                 disposed = true;
+
+                // From now on, no need to use this properties,
+                // so remove reference, to push it into garbage collector.
+
                 if (InternalItemSource != null)
                 {
                     InternalItemSource.Dispose();
@@ -938,7 +988,16 @@ namespace Tizen.NUI.Components
                 }
                 groupHeaderTemplate = null;
                 groupFooterTemplate = null;
-                //
+
+                if (selectedItem != null) 
+                {
+                    selectedItem = null;
+                }
+                if (selectedItems != null)
+                {
+                    selectedItems.Clear();
+                    selectedItems = null;
+                }
             }
 
             base.Dispose(type);
@@ -1100,6 +1159,43 @@ namespace Tizen.NUI.Components
             }
             return viewItem;
         }
+        private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+        {
+            switch (args.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    break;
+                case NotifyCollectionChangedAction.Remove:
+                    // Clear removed items.
+                    if (args.OldItems != null)
+                    {
+                        if (args.OldItems.Contains(selectedItem))
+                        {
+                            selectedItem = null;
+                        }
+                        
+                        if (selectedItems != null)
+                        {
+                            foreach (object removed in args.OldItems)
+                            {
+                                if (selectedItems.Contains(removed))
+                                {
+                                    selectedItems.Remove(removed);
+                                }
+                            }
+                        }
+                    }
+                    break;
+                case NotifyCollectionChangedAction.Replace:
+                    break;
+                case NotifyCollectionChangedAction.Move:
+                    break;
+                case NotifyCollectionChangedAction.Reset:
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(args));
+            }
+        }
 
     }
 }
index d00695d..93abb8b 100644 (file)
@@ -57,6 +57,16 @@ namespace Tizen.NUI.Components
         void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition);
 
         /// <summary>
+        /// Notify the range of the observable items are moved from fromPosition to ToPosition.
+        /// </summary>
+        /// <param name="source"></param>
+        /// <param name="fromPosition"></param>
+        /// <param name="toPosition"></param>
+        /// <param name="count"></param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count);
+
+        /// <summary>
         /// Notify the range of observable items from start to end are changed.
         /// </summary>
         /// <param name="source"></param>
index a68f33c..a0247d5 100755 (executable)
@@ -186,6 +186,13 @@ namespace Tizen.NUI.Components
             notifier.NotifyItemMoved(this, localFromIndex, localToIndex);
         }
 
+        public void NotifyItemRangeMoved(IItemSource group, int localFromIndex, int localToIndex, int count)
+        {
+            localFromIndex = GetAbsolutePosition(group, localFromIndex);
+            localToIndex = GetAbsolutePosition(group, localToIndex);
+            notifier.NotifyItemRangeMoved(this, localFromIndex, localToIndex, count);
+        }
+
         public void NotifyItemRangeChanged(IItemSource group, int localStartIndex, int localEndIndex)
         {
             localStartIndex = GetAbsolutePosition(group, localStartIndex);
@@ -395,13 +402,17 @@ namespace Tizen.NUI.Components
 
         void Move(NotifyCollectionChangedEventArgs args)
         {
+            var itemCount = CountItemsInGroups(args.OldStartingIndex, args.OldItems.Count);
             var start = Math.Min(args.OldStartingIndex, args.NewStartingIndex);
-            var end = Math.Max(args.OldStartingIndex, args.NewStartingIndex) + args.NewItems.Count;
+            var end = Math.Max(args.OldStartingIndex, args.NewStartingIndex) + itemCount;
 
-            var itemCount = CountItemsInGroups(start, end - start);
-            var absolutePosition = GetAbsolutePosition(groups[start], 0);
+            var fromPosition = GetAbsolutePosition(groups[args.OldStartingIndex], 0);
+            var toPosition = GetAbsolutePosition(groups[args.NewStartingIndex], 0);
 
-            notifier.NotifyItemRangeChanged(this, absolutePosition, itemCount);
+            // RangeChanged give unspecified information about moves.
+            // use RangeMoved instead of rangeChanged.
+            //notifier.NotifyItemRangeChanged(this, absolutePosition, itemCount);
+            notifier.NotifyItemRangeMoved(this, fromPosition, toPosition, itemCount);
 
             UpdateGroupTracking();
         }
index 1f7e4e4..f3aba65 100755 (executable)
@@ -43,6 +43,7 @@ namespace Tizen.NUI.Components
         private float groupFooterSize;
         private Extents groupFooterMargin;
         private GroupInfo Visited;
+        private Timer requestLayoutTimer = null;
 
         /// <summary>
         /// Clean up ItemsLayouter.
@@ -383,10 +384,14 @@ namespace Tizen.NUI.Components
                 if (i >= prevFirstVisible && i <= prevLastVisible)
                 {
                     item = GetVisibleItem(i);
-                    if (item) continue;
+                    if (item != null && !force) continue;
+                }
+                if (item == null)
+                {
+                    item = colView.RealizeItem(i);
+                    if (item != null) VisibleItems.Add(item);
+                    else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]");
                 }
-                if (item == null) item = colView.RealizeItem(i);
-                VisibleItems.Add(item);
 
                 //item Position without Padding and Margin.
                 (float x, float y) = GetItemPosition(i);
@@ -410,6 +415,53 @@ namespace Tizen.NUI.Components
             //Console.WriteLine("Realize Done");
         }
 
+        /// <summary>
+        /// Clear the current screen and all properties.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void Clear()
+        {
+            // Clean Up
+            if (requestLayoutTimer != null)
+            {
+                requestLayoutTimer.Dispose();
+            }
+            if (groups != null)
+            {
+                 /*
+                foreach (GroupInfo group in groups)
+                {
+                    //group.ItemPosition?.Clear();
+                    // if Disposable?
+                    //group.Dispose();
+                }
+                */
+                groups.Clear();
+            }
+            if (headerMargin != null)
+            {
+                headerMargin.Dispose();
+                headerMargin = null;
+            }
+            if (footerMargin != null)
+            {
+                footerMargin.Dispose();
+                footerMargin = null;
+            }
+            if (groupHeaderMargin != null)
+            {
+                groupHeaderMargin.Dispose();
+                groupHeaderMargin = null;
+            }
+            if (groupFooterMargin != null)
+            {
+                groupFooterMargin.Dispose();
+                groupFooterMargin = null;
+            }
+
+            base.Clear();
+        }
+
         /// <inheritdoc/>
         public override void NotifyItemSizeChanged(RecyclerViewItem item)
         {
@@ -420,6 +472,710 @@ namespace Tizen.NUI.Components
 
         /// <Inheritdoc/>
         [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemInserted(IItemSource source, int startIndex)
+        {
+            // Insert Single item.
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            // Will be null if not a group.
+            float currentSize = 0;
+            IGroupableItemSource gSource = source as IGroupableItemSource;
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+
+            //2. Handle Group Case.
+            if (isGrouped && gSource != null)
+            {
+                GroupInfo groupInfo = null;
+                object groupParent = gSource.GetGroupParent(startIndex);
+                int parentIndex = gSource.GetPosition(groupParent);
+                if (gSource.HasHeader) parentIndex--;
+
+                // Check item is group parent or not
+                // if group parent, add new gorupinfo
+                if (gSource.IsHeader(startIndex))
+                {
+                    // This is childless group.
+                    // create new groupInfo!
+                    groupInfo = new GroupInfo()
+                    {
+                        GroupParent = groupParent,
+                        StartIndex = startIndex,
+                        Count = 1,
+                        GroupSize = groupHeaderSize,
+                    };
+
+                    if (parentIndex >= groups.Count)
+                    {
+                        groupInfo.GroupPosition = ScrollContentSize;
+                        groups.Add(groupInfo);
+                    }
+                    else
+                    {
+                        groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
+                        groups.Insert(parentIndex, groupInfo);
+                    }
+
+                    currentSize = groupHeaderSize;
+                }
+                else
+                {
+                    // If not group parent, add item into the groupinfo.
+                    if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts.");
+                    groupInfo = groups[parentIndex];//GetGroupInfo(groupParent);
+                    if (groupInfo == null) throw new Exception("Cannot find group information!");
+
+                    if (gSource.IsGroupFooter(startIndex))
+                    {
+                        // It doesn't make sence to adding footer by notify...
+                        // if GroupFooterTemplate is added,
+                        // need to implement on here.
+                    }
+                    else
+                    {
+                        if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                        {
+                            // Wrong! Grid Layouter do not support MeasureAll!
+                        }
+                        else
+                        {
+                            int pureCount = groupInfo.Count - 1 - (colView.GroupFooterTemplate == null? 0: 1);
+                            if (pureCount % spanSize == 0)
+                            {
+                                currentSize = StepCandidate;
+                                groupInfo.GroupSize += currentSize;
+                            }
+
+                        }
+                    }
+                    groupInfo.Count++;
+
+                }
+
+                if (parentIndex + 1 < groups.Count)
+                {
+                    for(int i = parentIndex + 1; i < groups.Count; i++)
+                    {
+                        groups[i].GroupPosition += currentSize;
+                        groups[i].StartIndex++;
+                    }
+                }
+            }
+            else
+            {
+                if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                {
+                    // Wrong! Grid Layouter do not support MeasureAll!
+                }
+                int pureCount = colView.InternalItemSource.Count - (hasHeader? 1: 0) - (hasFooter? 1: 0);
+
+                // Count comes after updated in ungrouped case!
+                if (pureCount % spanSize == 1)
+                {
+                    currentSize = StepCandidate;
+                }
+            }
+
+            // 3. Update Scroll Content Size
+            ScrollContentSize += currentSize;
+
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            // 4. Update Visible Items.
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (item.Index >= startIndex)
+                {
+                    item.Index++;
+                }
+            }
+
+            float scrollPosition = PrevScrollPosition;
+
+            /*
+            // Position Adjust
+            // Insertion above Top Visible!
+            if (startIndex <= topInScreenIndex)
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
+        {
+             // Insert Group
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            float currentSize = 0;
+            // Will be null if not a group.
+            IGroupableItemSource gSource = source as IGroupableItemSource;
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+
+            // 2. Handle Group Case
+            // Adding ranged items should all same new groups.
+            if (isGrouped && gSource != null)
+            {
+                GroupInfo groupInfo = null;
+                object groupParent = gSource.GetGroupParent(startIndex);
+                int parentIndex = gSource.GetPosition(groupParent);
+                if (gSource.HasHeader) parentIndex--;
+                int groupStartIndex = 0;
+                if (gSource.IsGroupHeader(startIndex))
+                {
+                    groupStartIndex = startIndex;
+                }
+                else
+                {
+                    //exception case!
+                    throw new Exception("Inserted wrong groups!");
+                }
+
+                for (int current = startIndex; current - startIndex < count; current++)
+                {
+                    // Check item is group parent or not
+                    // if group parent, add new gorupinfo
+                    if (groupStartIndex == current)
+                    {
+                        //create new groupInfo!
+                        groupInfo = new GroupInfo()
+                        {
+                            GroupParent = groupParent,
+                            StartIndex = current,
+                            Count = 1,
+                            GroupSize = groupHeaderSize,
+                        };
+                        currentSize += groupHeaderSize;
+
+                    }
+                    else
+                    {
+                        //if not group parent, add item into the groupinfo.
+                        //groupInfo = GetGroupInfo(groupStartIndex);
+                        if (groupInfo == null) throw new Exception("Cannot find group information!");
+                        groupInfo.Count++;
+
+                        if (gSource.IsGroupFooter(current))
+                        {
+                                groupInfo.GroupSize += groupFooterSize;
+                                currentSize += groupFooterSize;
+                        }
+                        else
+                        {
+                            if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                            {
+                                // Wrong! Grid Layouter do not support MeasureAll!
+                            }
+                            else
+                            {
+                                int index = current - groupInfo.StartIndex - 1; // groupHeader must always exist.
+                                if ((index % spanSize) == 0)
+                                {
+                                    groupInfo.GroupSize += StepCandidate;
+                                    currentSize += StepCandidate;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if (parentIndex >= groups.Count)
+                {
+                    groupInfo.GroupPosition = ScrollContentSize;
+                    groups.Add(groupInfo);
+                }
+                else
+                {
+                    groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
+                    groups.Insert(parentIndex, groupInfo);
+                }
+
+                // Update other below group's position
+                if (parentIndex + 1 < groups.Count)
+                {
+                    for(int i = parentIndex + 1; i < groups.Count; i++)
+                    {
+                        groups[i].GroupPosition += currentSize;
+                        groups[i].StartIndex += count;
+                    }
+                }
+
+                ScrollContentSize += currentSize;
+            }
+            else
+            {
+                throw new Exception("Cannot insert ungrouped range items!");
+            }
+
+            // 3. Update Scroll Content Size
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            // 4. Update Visible Items.
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (item.Index >= startIndex)
+                {
+                    item.Index += count;
+                }
+            }
+
+            // Position Adjust
+            float scrollPosition = PrevScrollPosition;
+            /*
+            // Insertion above Top Visible!
+            if (startIndex + count <= topInScreenIndex)
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemRemoved(IItemSource source, int startIndex)
+        {
+            // Remove Single
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            // Will be null if not a group.
+            float currentSize = 0;
+            IGroupableItemSource gSource = source as IGroupableItemSource;
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+
+            // 2. Handle Group Case
+            if (isGrouped && gSource != null)
+            {
+                int parentIndex = 0;
+                GroupInfo groupInfo = null;
+                foreach(GroupInfo cur in groups)
+                {
+                    if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex))
+                    {
+                        groupInfo = cur;
+                        break;
+                    }
+                    parentIndex++;
+                }
+                if (groupInfo == null) throw new Exception("Cannot find group information!");
+                // Check item is group parent or not
+                // if group parent, add new gorupinfo
+                if (groupInfo.StartIndex == startIndex)
+                {
+                    // This is empty group!
+                    // check group is empty.
+                    if (groupInfo.Count != 1)
+                    {
+                        throw new Exception("Cannot remove group parent");
+                    }
+                    currentSize = groupInfo.GroupSize;
+
+                    // Remove Group
+                    // groupInfo.Dispose();
+                    groups.Remove(groupInfo);
+                }
+                else
+                {
+                    groupInfo.Count--;
+
+                    // Skip footer case as footer cannot exist alone without header.
+                    if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                    {
+                        // Wrong! Grid Layouter do not support MeasureAll!
+                    }
+                    else
+                    {
+                        int pureCount = groupInfo.Count - 1 - (colView.GroupFooterTemplate == null? 0: 1);
+                        if (pureCount % spanSize == 0)
+                        {
+                                currentSize = StepCandidate;
+                                groupInfo.GroupSize -= currentSize;
+                        }
+                    }
+                }
+
+                for (int i = parentIndex + 1; i < groups.Count; i++)
+                {
+                    groups[i].GroupPosition -= currentSize;
+                    groups[i].StartIndex--;
+                }
+            }
+            else
+            {
+                if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                {
+                    // Wrong! Grid Layouter do not support MeasureAll!
+                }
+                int pureCount = colView.InternalItemSource.Count - (hasHeader? 1: 0) - (hasFooter? 1: 0);
+
+                // Count comes after updated in ungrouped case!
+                if (pureCount % spanSize == 0)
+                {
+                    currentSize = StepCandidate;
+                }
+                
+            }
+
+            ScrollContentSize -= currentSize;
+
+            // 3. Update Scroll Content Size
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            // 4. Update Visible Items.
+            RecyclerViewItem targetItem = null;
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (item.Index == startIndex)
+                {
+                    targetItem = item;
+                    colView.UnrealizeItem(item);
+                }
+                else if (item.Index > startIndex)
+                {
+                    item.Index--;
+                }
+            }
+            VisibleItems.Remove(targetItem);
+
+            // Position Adjust
+            float scrollPosition = PrevScrollPosition;
+            /*
+            // Insertion above Top Visible!
+            if (startIndex <= topInScreenIndex)
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
+        {
+            // Remove Group
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            // Will be null if not a group.
+            float currentSize = StepCandidate;
+            IGroupableItemSource gSource = source as IGroupableItemSource;
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+
+            // 1. Handle Group Case
+            if (isGrouped && gSource != null)
+            {
+                int parentIndex = 0;
+                GroupInfo groupInfo = null;
+                foreach(GroupInfo cur in groups)
+                {
+                    if ((cur.StartIndex == startIndex) && (cur.Count == count))
+                    {
+                        groupInfo = cur;
+                        break;
+                    }
+                    parentIndex++;
+                }
+                if (groupInfo == null) throw new Exception("Cannot find group information!");
+                // Check item is group parent or not
+                // if group parent, add new gorupinfo
+                currentSize = groupInfo.GroupSize;
+                if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                {
+                    // Wrong! Grid Layouter do not support MeasureAll!
+                }
+                // Remove Group
+                // groupInfo.Dispose();
+                groups.Remove(groupInfo);
+
+                for (int i = parentIndex; i < groups.Count; i++)
+                {
+                    groups[i].GroupPosition -= currentSize;
+                    groups[i].StartIndex -= count;
+                }
+            }
+            else
+            {
+                // It must group case! throw exception!
+                throw new Exception("Range remove must group remove!");
+            }
+
+            ScrollContentSize -= currentSize;
+
+            // 2. Update Scroll Content Size
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            // 3. Update Visible Items.
+            List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if ((item.Index >= startIndex)
+                    && (item.Index < startIndex + count))
+                {
+                    unrealizedItems.Add(item);
+                    colView.UnrealizeItem(item);
+                }
+                else if (item.Index >= startIndex + count)
+                {
+                    item.Index -= count;
+                }
+            }
+            VisibleItems.RemoveAll(unrealizedItems.Contains);
+            unrealizedItems.Clear();
+
+            // Position Adjust
+            float scrollPosition = PrevScrollPosition;
+            /*
+            // Insertion above Top Visible!
+            if (startIndex <= topInScreenIndex)
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
+        {
+            // Reorder Single
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            // Will be null if not a group.
+            float currentSize = StepCandidate;
+            int diff = toPosition - fromPosition;
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+            
+            // Move can only happen in it's own groups.
+            // so there will be no changes in position, startIndex in ohter groups.
+            // check visible item and update indexs.
+            int startIndex = ( diff > 0 ? fromPosition: toPosition);
+            int endIndex = (diff > 0 ? toPosition: fromPosition);
+
+            if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
+            {
+                foreach (RecyclerViewItem item in VisibleItems)
+                {
+                    if ((item.Index >= startIndex)
+                        && (item.Index <= endIndex))
+                    {
+                        if (item.Index == fromPosition) item.Index = toPosition;
+                        else
+                        {
+                            if (diff > 0) item.Index--;
+                            else item.Index++;
+                        }
+                    }
+                }
+            }
+
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            // Position Adjust
+            float scrollPosition = PrevScrollPosition;
+            /*
+            // Insertion above Top Visible!
+            if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
+                ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
+        {
+            // Reorder Groups
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            // Will be null if not a group.
+            float currentSize = StepCandidate;
+            int diff = toPosition - fromPosition;
+
+            int startIndex = ( diff > 0 ? fromPosition: toPosition);
+            int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1);
+
+            // 2. Handle Group Case
+            if (isGrouped)
+            {
+                int fromParentIndex = 0;
+                int toParentIndex = 0;
+                bool findFrom = false;
+                bool findTo = false;
+                GroupInfo fromGroup = null;
+                GroupInfo toGroup = null;
+
+                foreach(GroupInfo cur in groups)
+                {
+                    if ((cur.StartIndex == fromPosition) && (cur.Count == count))
+                    {
+                        fromGroup = cur;
+                        findFrom = true;
+                        if (findFrom && findTo) break;
+                    }
+                    else if (cur.StartIndex == toPosition)
+                    {
+                        toGroup = cur;
+                        findTo = true;
+                        if (findFrom && findTo) break;
+                    }
+                    if (!findFrom) fromParentIndex++;
+                    if (!findTo) toParentIndex++;
+                }
+                if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!");
+
+                fromGroup.StartIndex = toGroup.StartIndex;
+                fromGroup.GroupPosition = toGroup.GroupPosition;
+
+                endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1);
+
+                groups.Remove(fromGroup);
+                groups.Insert(toParentIndex, fromGroup);
+
+                int startGroup = (diff > 0? fromParentIndex: toParentIndex);
+                int endGroup =  (diff > 0? toParentIndex: fromParentIndex);
+                
+                for (int i = startGroup; i <= endGroup; i++)
+                {
+                    if (i == toParentIndex) continue;
+                    float prevPos = groups[i].GroupPosition;
+                    int prevIdx = groups[i].StartIndex;
+                    groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize;
+                    groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count;
+                }
+            }
+            else
+            {
+                //It must group case! throw exception!
+                throw new Exception("Range remove must group remove!");
+            }
+
+            // Move can only happen in it's own groups.
+            // so there will be no changes in position, startIndex in ohter groups.
+            // check visible item and update indexs.
+            if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
+            {
+                foreach (RecyclerViewItem item in VisibleItems)
+                {
+                    if ((item.Index >= startIndex)
+                        && (item.Index <= endIndex))
+                    {
+                        if ((item.Index >= fromPosition) && (item.Index < fromPosition + count))
+                        {
+                            item.Index = fromPosition - item.Index + toPosition;
+                        }
+                        else
+                        {
+                            if (diff > 0) item.Index -= count;
+                            else item.Index += count;
+                        }
+                    }
+                }
+            }
+
+            // Position Adjust
+            float scrollPosition = PrevScrollPosition;
+            /*
+            // Insertion above Top Visible!
+            if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
+                ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public override float CalculateLayoutOrientationSize()
         {
             //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
@@ -700,6 +1456,21 @@ namespace Tizen.NUI.Components
             return (sizeCandidate.Width - CandidateMargin.Start - CandidateMargin.End,
                     sizeCandidate.Height - CandidateMargin.Top - CandidateMargin.Bottom);
         }
+        private void DelayedRequestLayout(float scrollPosition , bool force = true)
+        {
+            if (requestLayoutTimer != null)
+            {
+                requestLayoutTimer.Dispose();
+            }
+
+            requestLayoutTimer = new Timer(1);
+            requestLayoutTimer.Interval = 1;
+            requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) =>
+            {
+                RequestLayout(scrollPosition, force);
+                return false;
+            });
+        }
 
         private RecyclerViewItem GetVisibleItem(int index)
         {
index afc5abb..a6ddf79 100755 (executable)
@@ -153,6 +153,11 @@ namespace Tizen.NUI.Components
                 if (ItemsView != null) ItemsView.UnrealizeItem(item, false);
             }
             VisibleItems.Clear();
+            if (CandidateMargin != null)
+            {
+                CandidateMargin.Dispose();
+                CandidateMargin = null;
+            }
             ItemsView = null;
             Container = null;
         }
@@ -226,6 +231,18 @@ namespace Tizen.NUI.Components
         }
 
         /// <summary>
+        /// Notify the range of the observable items are moved from fromPosition to ToPosition.
+        /// </summary>
+        /// <param name="source"></param>
+        /// <param name="fromPosition"></param>
+        /// <param name="toPosition"></param>
+        /// <param name="count"></param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
+        {
+        }
+
+        /// <summary>
         /// Notify the range of observable items from start to end are changed.
         /// </summary>
         /// <param name="source">Dataset source.</param>
index f639753..167fbc3 100755 (executable)
@@ -43,6 +43,7 @@ namespace Tizen.NUI.Components
         private float groupFooterSize;
         private Extents groupFooterMargin;
         private GroupInfo Visited;
+        private Timer requestLayoutTimer = null;
 
         /// <summary>
         /// Clean up ItemsLayouter.
@@ -57,14 +58,7 @@ namespace Tizen.NUI.Components
                 throw new ArgumentException("LinearLayouter only can be applied CollectionView.", nameof(view));
             }
             // 1. Clean Up
-            foreach (RecyclerViewItem item in VisibleItems)
-            {
-                colView.UnrealizeItem(item, false);
-            }
-            VisibleItems.Clear();
-            ItemPosition.Clear();
-            ItemSize.Clear();
-            groups.Clear();
+            Clear();
 
             FirstVisible = 0;
             LastVisible = 0;
@@ -347,6 +341,14 @@ namespace Tizen.NUI.Components
         {
             // Layouting is only possible after once it initialized.
             if (!IsInitialized) return;
+
+            if (requestLayoutTimer != null)
+            {
+                requestLayoutTimer.Dispose();
+                requestLayoutTimer = null;
+                force = true;
+            }
+
             int LastIndex = colView.InternalItemSource.Count - 1;
 
             if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
@@ -384,6 +386,7 @@ namespace Tizen.NUI.Components
                 }
             }
             VisibleItems.RemoveAll(unrealizedItems.Contains);
+            unrealizedItems.Clear();
 
             // 3. Realize and placing visible items.
             for (int i = FirstVisible; i <= LastVisible; i++)
@@ -393,11 +396,15 @@ namespace Tizen.NUI.Components
                 if (i >= prevFirstVisible && i <= prevLastVisible)
                 {
                     item = GetVisibleItem(i);
-                    if (item) continue;
+                    if (item != null && !force) continue;
                 }
-                if (item == null) item = colView.RealizeItem(i);
-                VisibleItems.Add(item);
 
+                if (item == null)
+                {
+                    item = colView.RealizeItem(i);
+                    if (item != null) VisibleItems.Add(item);
+                    else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]");
+                }
                 // 5. Placing item.
                 (float posX, float posY) = GetItemPosition(i);
                 item.Position = new Position(posX, posY);
@@ -411,8 +418,65 @@ namespace Tizen.NUI.Components
                     item.Size = new Size(Container.Size.Width - Padding.Start - Padding.End - item.Margin.Start - item.Margin.End, item.Size.Height);
                 }
             }
+            return;
+        }
+
+        /// <summary>
+        /// Clear the current screen and all properties.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void Clear()
+        {
+            // Clean Up
+            if (requestLayoutTimer != null)
+            {
+                requestLayoutTimer.Dispose();
+            }
+            if (groups != null)
+            {
+                 /*
+                foreach (GroupInfo group in groups)
+                {
+                    //group.ItemPosition?.Clear();
+                    // if Disposable?
+                    //group.Dispose();
+                }
+                */
+                groups.Clear();
+            }
+            if (ItemPosition != null)
+            {
+                ItemPosition.Clear();
+            }
+            if (ItemSize != null)
+            {
+                ItemSize.Clear();
+            }
+            if (headerMargin != null)
+            {
+                headerMargin.Dispose();
+                headerMargin = null;
+            }
+            if (footerMargin != null)
+            {
+                footerMargin.Dispose();
+                footerMargin = null;
+            }
+            if (groupHeaderMargin != null)
+            {
+                groupHeaderMargin.Dispose();
+                groupHeaderMargin = null;
+            }
+            if (groupFooterMargin != null)
+            {
+                groupFooterMargin.Dispose();
+                groupFooterMargin = null;
+            }
+
+            base.Clear();
         }
 
+
         /// <inheritdoc/>
         [EditorBrowsable(EditorBrowsableState.Never)]
         public override void NotifyItemSizeChanged(RecyclerViewItem item)
@@ -453,6 +517,762 @@ namespace Tizen.NUI.Components
 
         /// <Inheritdoc/>
         [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemInserted(IItemSource source, int startIndex)
+        {
+            // Insert Single item.
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            // Will be null if not a group.
+            float currentSize = StepCandidate;
+            IGroupableItemSource gSource = source as IGroupableItemSource;
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+        
+            // 1. Handle MeasureAll
+            /*
+            if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+            {
+                //Need To Implement
+            }
+            */
+
+            //2. Handle Group Case.
+            if (isGrouped && gSource != null)
+            {
+                GroupInfo groupInfo = null;
+                object groupParent = gSource.GetGroupParent(startIndex);
+                int parentIndex = gSource.GetPosition(groupParent);
+                if (gSource.HasHeader) parentIndex--;
+
+                // Check item is group parent or not
+                // if group parent, add new gorupinfo
+                if (gSource.IsHeader(startIndex))
+                {
+                    // This is childless group.
+                    // create new groupInfo!
+                    groupInfo = new GroupInfo()
+                    {
+                        GroupParent = groupParent,
+                        StartIndex = startIndex,
+                        Count = 1,
+                        GroupSize = groupHeaderSize,
+                    };
+
+                    if (parentIndex >= groups.Count)
+                    {
+                        groupInfo.GroupPosition = ScrollContentSize;
+                        groups.Add(groupInfo);
+                    }
+                    else
+                    {
+                        groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
+                        groups.Insert(parentIndex, groupInfo);
+                    }
+
+                    currentSize = groupHeaderSize;
+                }
+                else
+                {
+                    // If not group parent, add item into the groupinfo.
+                    if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts.");
+                    groupInfo = groups[parentIndex];//GetGroupInfo(groupParent);
+                    if (groupInfo == null) throw new Exception("Cannot find group information!");
+                    groupInfo.Count++;
+
+                    if (gSource.IsGroupFooter(startIndex))
+                    {
+                        // It doesn't make sence to adding footer by notify...
+                        // if GroupFooterTemplate is added,
+                        // need to implement on here.
+                    }
+                    else
+                    {
+                        if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                        {
+                            float curPos = groupInfo.ItemPosition[startIndex - groupInfo.StartIndex];
+                            groupInfo.ItemPosition.Insert(startIndex - groupInfo.StartIndex, curPos);
+                            for (int i = startIndex - groupInfo.StartIndex; i < groupInfo.Count; i++)
+                            {
+                                groupInfo.ItemPosition[i] = curPos;
+                                curPos += GetItemStepSize(parentIndex + i);
+                            }
+                            groupInfo.GroupSize = curPos;
+                        }
+                        else
+                        {
+                            groupInfo.GroupSize += currentSize;
+                        }
+                    }
+                }
+
+                if (parentIndex + 1 < groups.Count)
+                {
+                    for(int i = parentIndex + 1; i < groups.Count; i++)
+                    {
+                        groups[i].GroupPosition += currentSize;
+                        groups[i].StartIndex++;
+                    }
+                }
+            }
+            else
+            {
+                if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                {
+                    // Need to Implements
+                }
+
+            }
+
+            // 3. Update Scroll Content Size
+            ScrollContentSize += currentSize;
+
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            // 4. Update Visible Items.
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (item.Index >= startIndex)
+                {
+                    item.Index++;
+                }
+            }
+
+
+            float scrollPosition = PrevScrollPosition;
+
+            /*
+            // Position Adjust
+            // Insertion above Top Visible!
+            if (startIndex <= topInScreenIndex)
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
+        {
+             // Insert Group
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            float currentSize = StepCandidate;
+            // Will be null if not a group.
+            IGroupableItemSource gSource = source as IGroupableItemSource;
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+
+            // 1. Handle MeasureAll
+            /*
+            if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+            {
+                //Need To Implement
+            }
+            */
+
+            // 2. Handle Group Case
+            // Adding ranged items should all same new groups.
+            if (isGrouped && gSource != null)
+            {
+                GroupInfo groupInfo = null;
+                object groupParent = gSource.GetGroupParent(startIndex);
+                int parentIndex = gSource.GetPosition(groupParent);
+                if (gSource.HasHeader) parentIndex--;
+                int groupStartIndex = 0;
+                if (gSource.IsGroupHeader(startIndex))
+                {
+                    groupStartIndex = startIndex;
+                }
+                else
+                {
+                    //exception case!
+                    throw new Exception("Inserted wrong groups!");
+                }
+
+                for (int current = startIndex; current - startIndex < count; current++)
+                {
+                    // Check item is group parent or not
+                    // if group parent, add new gorupinfo
+                    if (groupStartIndex == current)
+                    {
+                        //create new groupInfo!
+                        groupInfo = new GroupInfo()
+                        {
+                            GroupParent = groupParent,
+                            StartIndex = current,
+                            Count = 1,
+                            GroupSize = groupHeaderSize,
+                        };
+
+                    }
+                    else
+                    {
+                        //if not group parent, add item into the groupinfo.
+                        //groupInfo = GetGroupInfo(groupStartIndex);
+                        if (groupInfo == null) throw new Exception("Cannot find group information!");
+                        groupInfo.Count++;
+
+                        if (gSource.IsGroupFooter(current))
+                        {
+                                groupInfo.GroupSize += groupFooterSize;
+                        }
+                        else
+                        {
+                            if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                            {
+                               //Need To Implement
+                               /*
+                                float curPos = groupInfo.ItemPosition[current - groupStartIndex];
+                                groupInfo.ItemPosition.Insert(current - groupStartIndex, curPos);
+                                for (int i = current - groupStartIndex; i < groupInfo.Count; i++)
+                                {
+                                    groupInfo.ItemPosition[i] = curPos;
+                                    curPos += GetItemSize(parentIndex + i);
+                                }
+                                groupInfo.GroupSize = curPos;
+                                */
+                            }
+                            else
+                            {
+                                groupInfo.GroupSize += StepCandidate;
+                            }
+                        }
+                    }
+                }
+
+                if (parentIndex >= groups.Count)
+                {
+                    groupInfo.GroupPosition = ScrollContentSize;
+                    groups.Add(groupInfo);
+                }
+                else
+                {
+                    groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
+                    groups.Insert(parentIndex, groupInfo);
+                }
+
+                // Update other below group's position
+                if (parentIndex + 1 < groups.Count)
+                {
+                    for(int i = parentIndex + 1; i < groups.Count; i++)
+                    {
+                        groups[i].GroupPosition += groupInfo.GroupSize;
+                        groups[i].StartIndex += count;
+                    }
+                }
+
+                ScrollContentSize += groupInfo.GroupSize;
+            }
+            else
+            {
+                Tizen.Log.Error("NUI", "Not support insert ungrouped range items currently!");
+            }
+
+            // 3. Update Scroll Content Size
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            // 4. Update Visible Items.
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (item.Index >= startIndex)
+                {
+                    item.Index += count;
+                }
+            }
+
+            // Position Adjust
+            float scrollPosition = PrevScrollPosition;
+            /*
+            // Insertion above Top Visible!
+            if (startIndex + count <= topInScreenIndex)
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemRemoved(IItemSource source, int startIndex)
+        {
+            // Remove Single
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            // Will be null if not a group.
+            float currentSize = StepCandidate;
+            IGroupableItemSource gSource = source as IGroupableItemSource;
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+
+            // 1. Handle MeasureAll
+            /*
+            if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+            {
+                //Need To Implement
+            }
+            */
+
+            // 2. Handle Group Case
+            if (isGrouped && gSource != null)
+            {
+                int parentIndex = 0;
+                GroupInfo groupInfo = null;
+                foreach(GroupInfo cur in groups)
+                {
+                    if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex))
+                    {
+                        groupInfo = cur;
+                        break;
+                    }
+                    parentIndex++;
+                }
+                if (groupInfo == null) throw new Exception("Cannot find group information!");
+                // Check item is group parent or not
+                // if group parent, add new gorupinfo
+                if (groupInfo.StartIndex == startIndex)
+                {
+                    // This is empty group!
+                    // check group is empty.
+                    if (groupInfo.Count != 1)
+                    {
+                        throw new Exception("Cannot remove group parent");
+                    }
+                    currentSize = groupInfo.GroupSize;
+
+                    // Remove Group
+                    // groupInfo.Dispose();
+                    groups.Remove(groupInfo);
+                }
+                else
+                {
+                    groupInfo.Count--;
+
+                    if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                    {
+                        //Need to Implement this.
+                    }
+                    else
+                    {
+                        groupInfo.GroupSize -= currentSize;
+                    }
+                }
+
+                for (int i = parentIndex + 1; i < groups.Count; i++)
+                {
+                    groups[i].GroupPosition -= currentSize;
+                    groups[i].StartIndex--;
+                }
+            }
+            else
+            {
+                if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                {
+                    // Need to Implements
+                }
+                // else Nothing to Do
+            }
+
+            ScrollContentSize -= currentSize;
+
+            // 3. Update Scroll Content Size
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            // 4. Update Visible Items.
+            RecyclerViewItem targetItem = null;
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (item.Index == startIndex)
+                {
+                    targetItem = item;
+                    colView.UnrealizeItem(item);
+                }
+                else if (item.Index > startIndex)
+                {
+                    item.Index--;
+                }
+            }
+            VisibleItems.Remove(targetItem);
+
+            // Position Adjust
+            float scrollPosition = PrevScrollPosition;
+            /*
+            // Insertion above Top Visible!
+            if (startIndex <= topInScreenIndex)
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
+        {
+            // Remove Group
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            // Will be null if not a group.
+            float currentSize = StepCandidate;
+            IGroupableItemSource gSource = source as IGroupableItemSource;
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+
+            // 1. Handle MeasureAll
+            /*
+            if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+            {
+                //Need To Implement
+            }
+            */
+
+            // 2. Handle Group Case
+            if (isGrouped && gSource != null)
+            {
+                int parentIndex = 0;
+                GroupInfo groupInfo = null;
+                foreach(GroupInfo cur in groups)
+                {
+                    if ((cur.StartIndex == startIndex) && (cur.Count == count))
+                    {
+                        groupInfo = cur;
+                        break;
+                    }
+                    parentIndex++;
+                }
+                if (groupInfo == null) throw new Exception("Cannot find group information!");
+                // Check item is group parent or not
+                // if group parent, add new gorupinfo
+                currentSize = groupInfo.GroupSize;
+                if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                {
+                    // Update ItemSize and ItemPosition
+                }
+                // Remove Group
+                // groupInfo.Dispose();
+                groups.Remove(groupInfo);
+
+                for (int i = parentIndex; i < groups.Count; i++)
+                {
+                    groups[i].GroupPosition -= currentSize;
+                    groups[i].StartIndex -= count;
+                }
+            }
+            else
+            {
+                //It must group case! throw exception!
+                Tizen.Log.Error("NUI", "Not support remove ungrouped range items currently!");
+            }
+
+            ScrollContentSize -= currentSize;
+
+            // 3. Update Scroll Content Size
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            // 4. Update Visible Items.
+            List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if ((item.Index >= startIndex)
+                    && (item.Index < startIndex + count))
+                {
+                    unrealizedItems.Add(item);
+                    colView.UnrealizeItem(item);
+                }
+                else if (item.Index >= startIndex + count)
+                {
+                    item.Index -= count;
+                }
+            }
+            VisibleItems.RemoveAll(unrealizedItems.Contains);
+            unrealizedItems.Clear();
+
+            // Position Adjust
+            float scrollPosition = PrevScrollPosition;
+            /*
+            // Insertion above Top Visible!
+            if (startIndex <= topInScreenIndex)
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
+        {
+            // Reorder Single
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            // Will be null if not a group.
+            float currentSize = StepCandidate;
+            int diff = toPosition - fromPosition;
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+
+            // 1. Handle MeasureAll
+            /*
+            if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+            {
+                //Need To Implement
+            }
+            */
+            
+            // Move can only happen in it's own groups.
+            // so there will be no changes in position, startIndex in ohter groups.
+            // check visible item and update indexs.
+            int startIndex = ( diff > 0 ? fromPosition: toPosition);
+            int endIndex = (diff > 0 ? toPosition: fromPosition);
+
+            if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
+            {
+                foreach (RecyclerViewItem item in VisibleItems)
+                {
+                    if ((item.Index >= startIndex)
+                        && (item.Index <= endIndex))
+                    {
+                        if (item.Index == fromPosition) item.Index = toPosition;
+                        else
+                        {
+                            if (diff > 0) item.Index--;
+                            else item.Index++;
+                        }
+                    }
+                }
+            }
+
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            // Position Adjust
+            float scrollPosition = PrevScrollPosition;
+            /*
+            // Insertion above Top Visible!
+            if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
+                ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
+        {
+            // Reorder Groups
+            if (source == null) throw new ArgumentNullException(nameof(source));
+
+            // Will be null if not a group.
+            float currentSize = StepCandidate;
+            int diff = toPosition - fromPosition;
+
+            int startIndex = ( diff > 0 ? fromPosition: toPosition);
+            int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1);
+
+            // 2. Handle Group Case
+            if (isGrouped)
+            {
+                int fromParentIndex = 0;
+                int toParentIndex = 0;
+                bool findFrom = false;
+                bool findTo = false;
+                GroupInfo fromGroup = null;
+                GroupInfo toGroup = null;
+
+                foreach(GroupInfo cur in groups)
+                {
+                    if ((cur.StartIndex == fromPosition) && (cur.Count == count))
+                    {
+                        fromGroup = cur;
+                        findFrom = true;
+                        if (findFrom && findTo) break;
+                    }
+                    else if (cur.StartIndex == toPosition)
+                    {
+                        toGroup = cur;
+                        findTo = true;
+                        if (findFrom && findTo) break;
+                    }
+                    if (!findFrom) fromParentIndex++;
+                    if (!findTo) toParentIndex++;
+                }
+                if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!");
+
+                fromGroup.StartIndex = toGroup.StartIndex;
+                fromGroup.GroupPosition = toGroup.GroupPosition;
+
+                endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1);
+
+                groups.Remove(fromGroup);
+                groups.Insert(toParentIndex, fromGroup);
+
+                int startGroup = (diff > 0? fromParentIndex: toParentIndex);
+                int endGroup =  (diff > 0? toParentIndex: fromParentIndex);
+                
+                for (int i = startGroup; i <= endGroup; i++)
+                {
+                    if (i == toParentIndex) continue;
+                    float prevPos = groups[i].GroupPosition;
+                    int prevIdx = groups[i].StartIndex;
+                    groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize;
+                    groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count;
+                }
+            }
+            else
+            {
+                //It must group case! throw exception!
+                Tizen.Log.Error("NUI", "Not support move ungrouped range items currently!");
+            }
+
+            // Move can only happen in it's own groups.
+            // so there will be no changes in position, startIndex in ohter groups.
+            // check visible item and update indexs.
+            if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
+            {
+                foreach (RecyclerViewItem item in VisibleItems)
+                {
+                    if ((item.Index >= startIndex)
+                        && (item.Index <= endIndex))
+                    {
+                        if ((item.Index >= fromPosition) && (item.Index < fromPosition + count))
+                        {
+                            item.Index = fromPosition - item.Index + toPosition;
+                        }
+                        else
+                        {
+                            if (diff > 0) item.Index -= count;
+                            else item.Index += count;
+                        }
+                    }
+                }
+            }
+
+            // Position Adjust
+            float scrollPosition = PrevScrollPosition;
+            /*
+            // Insertion above Top Visible!
+            if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
+                ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
+            {
+                scrollPosition = GetItemPosition(topInScreenIndex);
+                scrollPosition -= offset;
+
+                colView.ScrollTo(scrollPosition);
+            }
+            */
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(scrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
+        {
+            // Reorder Group
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            IGroupableItemSource gSource = source as IGroupableItemSource;
+            if (gSource == null)throw new Exception("Source is not group!");
+
+            // Get the first Visible Position to adjust.
+            /*
+            int topInScreenIndex = 0;
+            float offset = 0F;
+            (topInScreenIndex, offset) = FindTopItemInScreen();
+            */
+
+
+            // Unrealize, initialized all items in the Range
+            // and receate all.
+
+            // Update Viewport in delay.
+            // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
+            // but currently we do not have any accessor to pre-calculation so instead of this,
+            // using Timer temporarily.
+            DelayedRequestLayout(PrevScrollPosition);
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public override float CalculateLayoutOrientationSize()
         {
             //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
@@ -721,6 +1541,44 @@ namespace Tizen.NUI.Components
             }            
         }
 
+        private void DelayedRequestLayout(float scrollPosition , bool force = true)
+        {
+            if (requestLayoutTimer != null)
+            {
+                requestLayoutTimer.Dispose();
+            }
+
+            requestLayoutTimer = new Timer(1);
+            requestLayoutTimer.Interval = 1;
+            requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) =>
+            {
+                RequestLayout(scrollPosition, force);
+                return false;
+            });
+        }
+
+        /*
+        private (int, float) FindTopItemInScreen()
+        {
+            int index = -1;
+            float offset = 0.0F, Pos, Size;
+
+            foreach(RecyclerViewItem item in VisibleItems)
+            {
+                Pos = IsHorizontal ? item.PositionX : item.PositionY;
+                Size = IsHorizontal ? item.SizeWidth : item.SizeHeight;
+                if (PrevScrollPosition >= Pos && PrevScrollPosition < Pos + Size)
+                {
+                    index = item.Index;
+                    offset = Pos - PrevScrollPosition;
+                    break;
+                }
+            }
+
+            return (index, offset);
+        }
+        */
+
         private float GetItemStepSize(int index)
         {
             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
index 93f63a1..e8199eb 100755 (executable)
@@ -88,7 +88,7 @@ namespace Tizen.NUI.Components
         /// Notify Dataset is Changed.
         /// </summary>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public void NotifyDataSetChanged()
+        public virtual void NotifyDataSetChanged()
         {
             //Need to update view.
             if (InternalItemsLayouter != null)
@@ -113,7 +113,7 @@ namespace Tizen.NUI.Components
         /// <param name="source">Dataset source.</param>
         /// <param name="startIndex">Changed item index.</param>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public void NotifyItemChanged(IItemSource source, int startIndex)
+        public virtual void NotifyItemChanged(IItemSource source, int startIndex)
         {
             if (InternalItemsLayouter != null)
             {
@@ -122,76 +122,77 @@ namespace Tizen.NUI.Components
         }
 
         /// <summary>
-        /// Notify observable item is inserted in dataset.
+        /// Notify range of observable items from start to end are changed.
         /// </summary>
         /// <param name="source">Dataset source.</param>
-        /// <param name="startIndex">Inserted item index.</param>
+        /// <param name="startRange">Start index of changed items range.</param>
+        /// <param name="endRange">End index of changed items range.</param>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public void NotifyItemInserted(IItemSource source, int startIndex)
+        public virtual void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
         {
             if (InternalItemsLayouter != null)
             {
-                InternalItemsLayouter.NotifyItemInserted(source, startIndex);
+                InternalItemsLayouter.NotifyItemRangeChanged(source, startRange, endRange);
             }
         }
 
         /// <summary>
-        /// Notify observable item is moved from fromPosition to ToPosition.
+        /// Notify observable item is inserted in dataset.
         /// </summary>
         /// <param name="source">Dataset source.</param>
-        /// <param name="fromPosition">Previous item position.</param>
-        /// <param name="toPosition">Moved item position.</param>
+        /// <param name="startIndex">Inserted item index.</param>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
+        public virtual void NotifyItemInserted(IItemSource source, int startIndex)
         {
             if (InternalItemsLayouter != null)
             {
-                InternalItemsLayouter.NotifyItemMoved(source, fromPosition, toPosition);
+                InternalItemsLayouter.NotifyItemInserted(source, startIndex);
             }
         }
 
         /// <summary>
-        /// Notify range of observable items from start to end are changed.
+        /// Notify count range of observable count items are inserted in startIndex.
         /// </summary>
         /// <param name="source">Dataset source.</param>
-        /// <param name="start">Start index of changed items range.</param>
-        /// <param name="end">End index of changed items range.</param>
+        /// <param name="startIndex">Start index of inserted items range.</param>
+        /// <param name="count">The number of inserted items.</param>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public void NotifyItemRangeChanged(IItemSource source, int start, int end)
+        public virtual void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
         {
             if (InternalItemsLayouter != null)
             {
-                InternalItemsLayouter.NotifyItemRangeChanged(source, start, end);
+                InternalItemsLayouter.NotifyItemRangeInserted(source, startIndex, count);
             }
         }
 
         /// <summary>
-        /// Notify count range of observable count items are inserted in startIndex.
+        /// Notify observable item is moved from fromPosition to ToPosition.
         /// </summary>
         /// <param name="source">Dataset source.</param>
-        /// <param name="startIndex">Start index of inserted items range.</param>
-        /// <param name="count">The number of inserted items.</param>
+        /// <param name="fromPosition">Previous item position.</param>
+        /// <param name="toPosition">Moved item position.</param>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
+        public virtual void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
         {
             if (InternalItemsLayouter != null)
             {
-                InternalItemsLayouter.NotifyItemRangeInserted(source, startIndex, count);
+                InternalItemsLayouter.NotifyItemMoved(source, fromPosition, toPosition);
             }
         }
 
         /// <summary>
-        /// Notify the count range of observable items from the startIndex are removed.
+        /// Notify the observable item is moved from fromPosition to ToPosition.
         /// </summary>
-        /// <param name="source">Dataset source.</param>
-        /// <param name="startIndex">Start index of removed items range.</param>
-        /// <param name="count">The number of removed items</param>
+        /// <param name="source"></param>
+        /// <param name="fromPosition"></param>
+        /// <param name="toPosition"></param>
+        /// <param name="count"></param>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
+        public virtual void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
         {
             if (InternalItemsLayouter != null)
             {
-                InternalItemsLayouter.NotifyItemRangeRemoved(source, startIndex, count);
+                InternalItemsLayouter.NotifyItemRangeMoved(source, fromPosition, toPosition, count);
             }
         }
 
@@ -201,7 +202,7 @@ namespace Tizen.NUI.Components
         /// <param name="source">Dataset source.</param>
         /// <param name="startIndex">Index of removed item.</param>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public void NotifyItemRemoved(IItemSource source, int startIndex)
+        public virtual void NotifyItemRemoved(IItemSource source, int startIndex)
         {
             if (InternalItemsLayouter != null)
             {
@@ -210,6 +211,21 @@ namespace Tizen.NUI.Components
         }
 
         /// <summary>
+        /// Notify the count range of observable items from the startIndex are removed.
+        /// </summary>
+        /// <param name="source">Dataset source.</param>
+        /// <param name="startIndex">Start index of removed items range.</param>
+        /// <param name="count">The number of removed items</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
+        {
+            if (InternalItemsLayouter != null)
+            {
+                InternalItemsLayouter.NotifyItemRangeRemoved(source, startIndex, count);
+            }
+        }
+
+        /// <summary>
         /// Realize indexed item.
         /// </summary>
         /// <param name="index"> Index position of realizing item </param>
@@ -256,13 +272,11 @@ namespace Tizen.NUI.Components
         {
             item.Index = -1;
             item.ParentItemsView = null;
-            // Remove BindingContext null set for performance improving.
-            //item.BindingContext = null; 
+            item.BindingContext = null; 
             item.IsPressed = false;
             item.IsSelected = false;
             item.IsEnabled = true;
-            // Remove Update Style on default for performance improving.
-            //item.UpdateState();
+            item.UpdateState();
             item.Relayout -= OnItemRelayout;
 
             if (!recycle || !PushRecycleCache(item))
index 36b2992..e9922b3 100755 (executable)
@@ -1,7 +1,9 @@
 ï»¿using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using Tizen.NUI.BaseComponents;
 using Tizen.NUI.Components;
 using Tizen.NUI.Binding;
+using System;
 
 namespace Tizen.NUI.Samples
 {
@@ -9,17 +11,25 @@ namespace Tizen.NUI.Samples
     {
         CollectionView colView;
         int itemCount = 500;
-        int selectedCount;
         ItemSelectionMode selMode;
+        ObservableCollection<Gallery> gallerySource;
+        Gallery insertMenu = new Gallery(999, "Insert item to 3rd");
+        Gallery deleteMenu = new Gallery(999, "Delete item at 3rd");
+        Gallery moveMenu = new Gallery(999, "Move last item to 3rd");
 
         public void Activate()
         {
             Window window = NUIApplication.GetDefaultWindow();
 
-            var myViewModelSource = new GalleryViewModel(itemCount);
+            var myViewModelSource = gallerySource = new GalleryViewModel(itemCount);
+            // Add test menu options.
+            gallerySource.Insert(0, moveMenu);
+            gallerySource.Insert(0, deleteMenu);
+            gallerySource.Insert(0, insertMenu);
+
             selMode = ItemSelectionMode.MultipleSelections;
             DefaultTitleItem myTitle = new DefaultTitleItem();
-            myTitle.Text = "Grid Sample Count["+itemCount+"] Selected["+selectedCount+"]";
+            myTitle.Text = "Grid Sample Count["+itemCount+"]";
             //Set Width Specification as MatchParent to fit the Item width with parent View.
             myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
 
@@ -76,7 +86,6 @@ namespace Tizen.NUI.Samples
                     {
                         galItem.Selected = false;
                         Tizen.Log.Debug("Unselected: {0}", galItem.ViewLabel);
-                        selectedCount--;
                     }
                 }
                 else continue;
@@ -90,15 +99,34 @@ namespace Tizen.NUI.Samples
                     {
                         galItem.Selected = true;
                         Tizen.Log.Debug("Selected: {0}", galItem.ViewLabel);
-                        selectedCount++;
+
+                        // Check test menu options.
+                        if (galItem == insertMenu)
+                        {
+                            // Insert new item to index 3.
+                            Random rand = new Random();
+                            int idx = rand.Next(1000);
+                            gallerySource.Insert(3, new Gallery(idx, "Inserted Item"));
+                        }
+                        else if (galItem == deleteMenu)
+                        {
+                            // Remove item in index 3.
+                            gallerySource.RemoveAt(3);
+                        }
+                        else if (galItem == moveMenu)
+                        {
+                            // Move last indexed item to index 3.
+                            gallerySource.Move(gallerySource.Count - 1, 3);                    
+                        }
                     }
+                    
                 }
                 else continue;
             }
             if (colView.Header != null && colView.Header is DefaultTitleItem)
             {
                 DefaultTitleItem title = (DefaultTitleItem)colView.Header;
-                title.Text = "Grid Sample Count["+itemCount+"] Selected["+selectedCount+"]";
+                title.Text = "Grid Sample Count["+gallerySource.Count+"] Selected["+newSel.Count+"]";
             }
         }
 
index e252842..a43939f 100755 (executable)
@@ -1,4 +1,5 @@
 ï»¿using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using Tizen.NUI.BaseComponents;
 using Tizen.NUI.Components;
 using Tizen.NUI.Binding;
@@ -13,12 +14,22 @@ namespace Tizen.NUI.Samples
         int itemCount = 500;
         string selectedItem;
         ItemSelectionMode selMode;
+        ObservableCollection<Gallery> gallerySource;
+        Gallery insertMenu = new Gallery(999, "Insert item to 3rd");
+        Gallery deleteMenu = new Gallery(999, "Delete item at 3rd");
+        Gallery moveMenu = new Gallery(999, "Move last item to 3rd");
+
 
         public void Activate()
         {
             Window window = NUIApplication.GetDefaultWindow();
 
-            var myViewModelSource = new GalleryViewModel(itemCount);
+            var myViewModelSource = gallerySource = new GalleryViewModel(itemCount);
+            // Add test menu options.
+            gallerySource.Insert(0, moveMenu);
+            gallerySource.Insert(0, deleteMenu);
+            gallerySource.Insert(0, insertMenu);
+
             selMode = ItemSelectionMode.SingleSelection;
             DefaultTitleItem myTitle = new DefaultTitleItem();
             myTitle.Text = "Linear Sample Count["+itemCount+"]";
@@ -95,16 +106,36 @@ namespace Tizen.NUI.Samples
             {
                 if (item == null) break;
                 Gallery selItem = (Gallery)item;
-                selItem.Selected = true;
+                //selItem.Selected = true;
                 selectedItem = selItem.Name;
+
+                // Check test menu options.
+                if (selItem == insertMenu)
+                {
+                    // Insert new item to index 3.
+                    Random rand = new Random();
+                    int idx = rand.Next(1000);
+                    gallerySource.Insert(3, new Gallery(idx, "Inserted Item"));
+                }
+                else if (selItem == deleteMenu)
+                {
+                    // Remove item in index 3.
+                    gallerySource.RemoveAt(3);
+                }
+                else if (selItem == moveMenu)
+                {
+                    // Move last indexed item to index 3.
+                    gallerySource.Move(gallerySource.Count - 1, 3);                    
+                }
                 //Tizen.Log.Debug("NUI", "LSH :: Selected: {0}", selItem.ViewLabel);
             }
             if (colView.Header != null && colView.Header is DefaultTitleItem)
             {
                 DefaultTitleItem title = (DefaultTitleItem)colView.Header;
-                title.Text = "Linear Sample Count[" + itemCount + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]");
+                title.Text = "Linear Sample Count[" + gallerySource.Count + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]");
             }
         }
+
         public void Deactivate()
         {
             if (colView != null)
index 4a08d75..c5a749d 100644 (file)
@@ -7,7 +7,6 @@ using Tizen.NUI.Components;
 using Tizen.NUI.Binding;
 
 
-
 class Gallery : INotifyPropertyChanged
 {
     string sourceDir = Tizen.NUI.Samples.CommonResource.GetDaliResourcePath()+"ItemViewDemo/gallery/gallery-medium-";
@@ -75,6 +74,7 @@ class Album : ObservableCollection<Gallery>
     private int index;
     private string name;
     private DateTime date;
+    private bool selected;
 
     public Album(int albumIndex, string albumName, DateTime albumDate)
     {
@@ -98,6 +98,16 @@ class Album : ObservableCollection<Gallery>
             return date.ToLongDateString();
         }
     }
+    public bool Selected {
+        get
+        {
+            return selected;
+        }
+        set
+        {
+            selected = value;
+        }
+    }
 }
 
 class GalleryViewModel : ObservableCollection<Gallery>
@@ -184,7 +194,6 @@ class AlbumViewModel : ObservableCollection<Album>
         ("Graduation", new DateTime(2017, 6, 30)),
     };
 
-
     public AlbumViewModel()
     {
         CreateData(this);
index 5aa0b9c..eb104e0 100644 (file)
@@ -11,24 +11,35 @@ namespace Tizen.NUI.Samples
     public class CollectionViewGridGroupSample : IExample
     {
         CollectionView colView;
-        int selectedCount;
         ItemSelectionMode selMode;
-        ObservableCollection<Album> groupSource;
+        ObservableCollection<Album> albumSource;
+        Album insertDeleteGroup = new Album(999, "Insert / Delete Groups", new DateTime(1999, 12, 31));
+        Gallery insertMenu = new Gallery(999, "Insert item to first of 3rd Group");
+        Gallery deleteMenu = new Gallery(999, "Delete first item in 3rd Group");
+        Album moveGroup = new Album(999, "move Groups", new DateTime(1999, 12, 31));
+        Gallery moveMenu = new Gallery(999, "Move last item to first in 3rd group");
 
         public void Activate()
         {
             Window window = NUIApplication.GetDefaultWindow();
 
-            groupSource = new AlbumViewModel();
+            albumSource = new AlbumViewModel();
+            // Add test menu options.
+            moveGroup.Add(moveMenu);
+            albumSource.Insert(0, moveGroup);
+            insertDeleteGroup.Add(insertMenu);
+            insertDeleteGroup.Add(deleteMenu);
+            albumSource.Insert(0, insertDeleteGroup);
+
             selMode = ItemSelectionMode.MultipleSelections;
             DefaultTitleItem myTitle = new DefaultTitleItem();
-            myTitle.Text = "Grid Sample Count["+ groupSource.Count+"] Selected["+selectedCount+"]";
+            myTitle.Text = "Grid Sample Count["+ albumSource.Count+"]";
             //Set Width Specification as MatchParent to fit the Item width with parent View.
             myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
 
             colView = new CollectionView()
             {
-                ItemsSource = groupSource,
+                ItemsSource = albumSource,
                 ItemsLayouter = new GridLayouter(),
                 ItemTemplate = new DataTemplate(() =>
                 {
@@ -90,11 +101,21 @@ namespace Tizen.NUI.Samples
                     if (!(newSel.Contains(item)))
                     {
                         galItem.Selected = false;
-                        Tizen.Log.Debug("Unselected: {0}", galItem.ViewLabel);
-                        selectedCount--;
+                        // Tizen.Log.Debug("Unselected: {0}", galItem.ViewLabel);
+                    }
+                }
+                else if (item is Album selAlbum)
+                {
+                    if (!(newSel.Contains(selAlbum)))
+                    {
+                        selAlbum.Selected = false;
+                        // Tizen.Log.Debug("Unselected Group: {0}", selAlbum.Title);
+                        if (selAlbum == insertDeleteGroup)
+                        {
+                            albumSource.RemoveAt(2);
+                        }
                     }
                 }
-                else continue;
             }
             foreach (object item in newSel)
             {
@@ -104,16 +125,58 @@ namespace Tizen.NUI.Samples
                     if (!(oldSel.Contains(item)))
                     {
                         galItem.Selected = true;
-                        Tizen.Log.Debug("Selected: {0}", galItem.ViewLabel);
-                        selectedCount++;
+                        // Tizen.Log.Debug("Selected: {0}", galItem.ViewLabel);
+                        
+                        if (galItem == insertMenu)
+                        {
+                            // Insert new item to index 3.
+                            Random rand = new Random();
+                            int idx = rand.Next(1000);
+                            albumSource[2].Insert(3, new Gallery(idx, "Inserted Item"));
+                        }
+                        else if (galItem == deleteMenu)
+                        {
+                            // Remove item in index 3.
+                            albumSource[2].RemoveAt(0);
+                        }
+                        else if (galItem == moveMenu)
+                        {
+                            // Move last indexed item to index 3.
+                            albumSource[2].Move(albumSource[2].Count - 1, 0);                   
+                        }
+                    }
+                }
+                else if (item is Album selAlbum)
+                {
+                    if (!(oldSel.Contains(selAlbum)))
+                    {
+                        selAlbum.Selected = true;
+                        // Tizen.Log.Debug("Selected Group: {0}", selAlbum.Title);
+                        if (selAlbum == insertDeleteGroup)
+                        {
+                            Random rand = new Random();
+                            int groupIdx = rand.Next(1000);
+                            Album insertAlbum = new Album(groupIdx, "Inserted group", new DateTime(1999, 12, 31));
+                            int idx = rand.Next(1000);
+                            insertAlbum.Add(new Gallery(idx, "Inserted Item 1"));
+                            idx = rand.Next(1000);
+                            insertAlbum.Add(new Gallery(idx, "Inserted Item 2"));
+                            idx = rand.Next(1000);
+                            insertAlbum.Add(new Gallery(idx, "Inserted Item 3"));
+                            albumSource.Insert(2, insertAlbum);
+                        }
+                        else if (selAlbum == moveGroup)
+                        {
+                            albumSource.Move(albumSource.Count - 1, 2);
+
+                        }
                     }
                 }
-                else continue;
             }
             if (colView.Header != null && colView.Header is DefaultTitleItem)
             {
                 DefaultTitleItem title = (DefaultTitleItem)colView.Header;
-                title.Text = "Grid Sample Count["+ groupSource.Count + "] Selected["+selectedCount+"]";
+                title.Text = "Grid Sample Count["+ albumSource.Count + "] Selected["+newSel.Count+"]";
             }
         }
 
index f8a759b..f9c63db 100644 (file)
@@ -13,32 +13,38 @@ namespace Tizen.NUI.Samples
         CollectionView colView;
         string selectedItem;
         ItemSelectionMode selMode;
-        ObservableCollection<Album> groupSource;
+        ObservableCollection<Album> albumSource;
+        Album insertDeleteGroup = new Album(999, "Insert / Delete Groups", new DateTime(1999, 12, 31));
+        Gallery insertMenu = new Gallery(999, "Insert item to first of 3rd Group");
+        Gallery deleteMenu = new Gallery(999, "Delete first item in 3rd Group");
+        Album moveGroup = new Album(999, "move Groups", new DateTime(1999, 12, 31));
+        Gallery moveMenu = new Gallery(999, "Move last item to first in 3rd group");
 
         public void Activate()
         {
             Window window = NUIApplication.GetDefaultWindow();
 
-            groupSource = new AlbumViewModel();
+            albumSource = new AlbumViewModel();
+            // Add test menu options.
+            moveGroup.Add(moveMenu);
+            albumSource.Insert(0, moveGroup);
+            insertDeleteGroup.Add(insertMenu);
+            insertDeleteGroup.Add(deleteMenu);
+            albumSource.Insert(0, insertDeleteGroup);
+
             selMode = ItemSelectionMode.SingleSelection;
             DefaultTitleItem myTitle = new DefaultTitleItem();
             //To Bind the Count property changes, need to create custom property for count.
-            myTitle.Text = "Linear Sample Group["+ groupSource.Count+"]";
+            myTitle.Text = "Linear Sample Group["+ albumSource.Count+"]";
             //Set Width Specification as MatchParent to fit the Item width with parent View.
             myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
 
             colView = new CollectionView()
             {
-                ItemsSource = groupSource,
+                ItemsSource = albumSource,
                 ItemsLayouter = new LinearLayouter(),
                 ItemTemplate = new DataTemplate(() =>
                 {
-                    var rand = new Random();
-                    RecyclerViewItem item = new RecyclerViewItem();
-                    item.WidthSpecification = LayoutParamPolicies.MatchParent;
-                    item.HeightSpecification = 100;
-                    item.BackgroundColor = new Color((float)rand.NextDouble(), (float)rand.NextDouble(), (float)rand.NextDouble(), 1);
-                    /*
                     DefaultLinearItem item = new DefaultLinearItem();
                     //Set Width Specification as MatchParent to fit the Item width with parent View.
                     item.WidthSpecification = LayoutParamPolicies.MatchParent;
@@ -59,25 +65,19 @@ namespace Tizen.NUI.Samples
                     //item.Extra.SetBinding(RadioButton.IsSelectedProperty, "Selected");
                     item.Extra.WidthSpecification = 80;
                     item.Extra.HeightSpecification = 80;
-                    */
+
                     return item;
                 }),
                 GroupHeaderTemplate = new DataTemplate(() =>
                 {
-                    var rand = new Random();
-                    RecyclerViewItem item = new RecyclerViewItem();
-                    item.WidthSpecification = LayoutParamPolicies.MatchParent;
-                    item.HeightSpecification = 50;
-                    item.BackgroundColor = new Color(0, 0, 0, 1);
-                    /*
                     DefaultTitleItem group = new DefaultTitleItem();
                     //Set Width Specification as MatchParent to fit the Item width with parent View.
                     group.WidthSpecification = LayoutParamPolicies.MatchParent;
 
                     group.Label.SetBinding(TextLabel.TextProperty, "Date");
                     group.Label.HorizontalAlignment = HorizontalAlignment.Begin;
-                    */
-                    return item;
+
+                    return group;
                 }),
                 Header = myTitle,
                 IsGrouped = true,
@@ -100,26 +100,82 @@ namespace Tizen.NUI.Samples
             foreach (object item in ev.PreviousSelection)
             {
                 if (item == null) break;
-                Gallery unselItem = (Gallery)item;
-
-                unselItem.Selected = false;
-                selectedItem = null;
-                //Tizen.Log.Debug("NUI", "LSH :: Unselected: {0}", unselItem.ViewLabel);
+                if (item is Gallery unselItem)
+                {
+                    unselItem.Selected = false;
+                    selectedItem = null;
+                    //Tizen.Log.Debug("NUI", "LSH :: Unselected: {0}", unselItem.ViewLabel);
+                }
+                else if (item is Album selAlbum)
+                {
+                    selectedItem = selAlbum.Title;
+                    selAlbum.Selected = false;
+                    if (selAlbum == insertDeleteGroup)
+                    {
+                        albumSource.RemoveAt(2);
+                    }
+                }
+                
             }
             foreach (object item in ev.CurrentSelection)
             {
                 if (item == null) break;
-                Gallery selItem = (Gallery)item;
-                selItem.Selected = true;
-                selectedItem = selItem.Name;
-                //Tizen.Log.Debug("NUI", "LSH :: Selected: {0}", selItem.ViewLabel);
+                if (item is Gallery selItem)
+                {
+                    selItem.Selected = true;
+                    selectedItem = selItem.Name;
+                    //Tizen.Log.Debug("NUI", "LSH :: Selected: {0}", selItem.ViewLabel);
+
+                    // Check test menu options.
+                    if (selItem == insertMenu)
+                    {
+                        // Insert new item to index 3.
+                        Random rand = new Random();
+                        int idx = rand.Next(1000);
+                        albumSource[2].Insert(0, new Gallery(idx, "Inserted Item"));
+                    }
+                    else if (selItem == deleteMenu)
+                    {
+                        // Remove item in index 3.
+                        albumSource[2].RemoveAt(0);
+                    }
+                    else if (selItem == moveMenu)
+                    {
+                        // Move last indexed item to index 3.
+                        albumSource[2].Move(albumSource[2].Count - 1, 0);                   
+                    }
+                }
+                else if (item is Album selAlbum)
+                {
+                    selectedItem = selAlbum.Title;
+                    selAlbum.Selected = true;
+                    if (selAlbum == insertDeleteGroup)
+                    {
+                        Random rand = new Random();
+                        int groupIdx = rand.Next(1000);
+                        Album insertAlbum = new Album(groupIdx, "Inserted group", new DateTime(1999, 12, 31));
+                        int idx = rand.Next(1000);
+                        insertAlbum.Add(new Gallery(idx, "Inserted Item 1"));
+                        idx = rand.Next(1000);
+                        insertAlbum.Add(new Gallery(idx, "Inserted Item 2"));
+                        idx = rand.Next(1000);
+                        insertAlbum.Add(new Gallery(idx, "Inserted Item 3"));
+                        albumSource.Insert(2, insertAlbum);
+                    }
+                    else if (selAlbum == moveGroup)
+                    {
+                        albumSource.Move(albumSource.Count - 1, 2);
+
+                    }
+                }
             }
             if (colView.Header != null && colView.Header is DefaultTitleItem)
             {
                 DefaultTitleItem title = (DefaultTitleItem)colView.Header;
-                title.Text = "Linear Sample Count[" + groupSource + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]");
+                title.Text = "Linear Sample Count[" + albumSource.Count + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]");
             }
         }
+
         public void Deactivate()
         {
             if (colView != null)