[NUI] Introduce CollectionView and related classes. (#2525)
authorSangHyeon Jade Lee <dltkdgus1764@gmail.com>
Tue, 9 Feb 2021 04:19:19 +0000 (13:19 +0900)
committerheeyongsong <35289263+heeyongsong@users.noreply.github.com>
Tue, 9 Feb 2021 05:36:55 +0000 (14:36 +0900)
* Introduce CollectionView and related classes.

View :
[ItemsView] Base class of model based view. create item by DataTemplate and
            layouting them with layouter using source data. inherited from ScrollableBase.
[CollectionView] groupable and selectable ItemsView.

Item :
[ViewItem] Base item for CollectionView.
[ViewItem.Internal] Internal partial class of ViewItem.
[ViewItemStyle] Style class of ViewItem.
[OneLineLinearItem] Linear type ViewItem of 1 line text Label and Icon, Extra View.
[OutLineGridItem] Grid type ViewItem of outline caption Label and ImageView Image and View Badge.

Layouter :
[ItemsLayouter] Base compositor class of item layouter on ItemsView.
[LinearLayouter] Layouter class of layouting item in linear list. restricted for CollectionView class only.
[GridLayouter] Layouter class of layouting item in grid list. restricted for CollectionView class only.

Item Source :
[ItemsSourceFactory] Factory of creating IItemSource from IEnumerable source.
[IItemSource] Interface for encapsulated data source from IEnumerable user source.
[IGroupedItemSource] Interface for grouped user data.
[EmptySource] Empty data source.
[ListSource] List data source.
[ObservableItemSource] Item source who provide observable notifications.
[ObservableGroupedSource] Grouped item source who provide observable notifications.
[UngroupedItemSource] Ungrouped item source.

Enum and Interface :
[ICollectionChangedNotifier] Interface of collection changed notifier.
[ItemSelectionMode] Mode enum value for Selection.
[ItemSizingStrategy] Sizing strategy for item measure.
[SelectionChangedEventArgs] Event for Selection changed notify.

Helper Class :
[MarshalingObservableCollection] Marshaling ObservableCollection for itemSource.
[SelectionList] Internal list for SelectionModel.

Other Changes :
Moving and change internal to public of DataTemplate related classes.

Known Issues :
- Empty items or pages shows when scroll fast, jump by ScrollTo().
- ScrollTo() is moving incorrectly.
- Scrolling event passing scrollPosition negative value.
- Application cannot initialize the cached ViewItem.
- Image trembling while scrolling.

Remaining Works :
- Implement groupable features.
- Implement observable changes(ICollectionChangedNotifier).
- Implement dynamic size and dynamic template selector.
- Implement Item / Layouter animations and support custom animations.
- Adding samples and test.
- Adding Unit Test.
- Documents and guides.

Design Documents :
https://code.sec.samsung.net/confluence/display/ENUIFWC/5.+Model-based++User+Interfaces

* [NUI] Update templates to EditorBrowsableState.Never

* [CollectionView] fix wrong initialize layouter and item count which makes missing 0 and last indexed item in Header/Footer exist

* [CollectionView] fix missing call of OnRelayout in base class.

* [CollectionView] Implement Groupable Features and Refactoring Items

1. Implement Groupable features in CollectionView, LinearLayouter,
    GridLayouter.
2. Refactoring items and adding styles.
   OneLineLinearItem => DefaultLinearItem : SubLabel added.
                                            Use RelativeLayout.
    Apply DefaultLinerItemStyle.
   OutLineGridItem => DefaultGridItem : Label changed Caption.
                                        Use RelativeLayout.
Support CaptionOrientation.
Apply DefaultGridItemStyle.
   DefaultTitleItem is added for Header / GroupHeader.
3. Sample is udpated.
4. Remove _ in the code.

* [CollectionView] Refactoring class directories

- Move all item source related classes in ItemSource
- Move all item class in Item
- Move all layouter class in Layouter

* [NUI] add copy-right license pre-comments

* Introduce CollectionView and related classes.

View :
[ItemsView] Base class of model based view. create item by DataTemplate and
            layouting them with layouter using source data. inherited from ScrollableBase.
[CollectionView] groupable and selectable ItemsView.

Item :
[ViewItem] Base item for CollectionView.
[ViewItem.Internal] Internal partial class of ViewItem.
[ViewItemStyle] Style class of ViewItem.
[OneLineLinearItem] Linear type ViewItem of 1 line text Label and Icon, Extra View.
[OutLineGridItem] Grid type ViewItem of outline caption Label and ImageView Image and View Badge.

Layouter :
[ItemsLayouter] Base compositor class of item layouter on ItemsView.
[LinearLayouter] Layouter class of layouting item in linear list. restricted for CollectionView class only.
[GridLayouter] Layouter class of layouting item in grid list. restricted for CollectionView class only.

Item Source :
[ItemsSourceFactory] Factory of creating IItemSource from IEnumerable source.
[IItemSource] Interface for encapsulated data source from IEnumerable user source.
[IGroupedItemSource] Interface for grouped user data.
[EmptySource] Empty data source.
[ListSource] List data source.
[ObservableItemSource] Item source who provide observable notifications.
[ObservableGroupedSource] Grouped item source who provide observable notifications.
[UngroupedItemSource] Ungrouped item source.

Enum and Interface :
[ICollectionChangedNotifier] Interface of collection changed notifier.
[ItemSelectionMode] Mode enum value for Selection.
[ItemSizingStrategy] Sizing strategy for item measure.
[SelectionChangedEventArgs] Event for Selection changed notify.

Helper Class :
[MarshalingObservableCollection] Marshaling ObservableCollection for itemSource.
[SelectionList] Internal list for SelectionModel.

Other Changes :
Moving and change internal to public of DataTemplate related classes.

Known Issues :
- Empty items or pages shows when scroll fast, jump by ScrollTo().
- ScrollTo() is moving incorrectly.
- Scrolling event passing scrollPosition negative value.
- Application cannot initialize the cached ViewItem.
- Image trembling while scrolling.

Remaining Works :
- Implement groupable features.
- Implement observable changes(ICollectionChangedNotifier).
- Implement dynamic size and dynamic template selector.
- Implement Item / Layouter animations and support custom animations.
- Adding samples and test.
- Adding Unit Test.
- Documents and guides.

Design Documents :
https://code.sec.samsung.net/confluence/display/ENUIFWC/5.+Model-based++User+Interfaces

* [NUI] Update templates to EditorBrowsableState.Never

* [CollectionView] fix wrong initialize layouter and item count which makes missing 0 and last indexed item in Header/Footer exist

* [CollectionView] fix missing call of OnRelayout in base class.

* [CollectionView] Implement Groupable Features and Refactoring Items

1. Implement Groupable features in CollectionView, LinearLayouter,
    GridLayouter.
2. Refactoring items and adding styles.
   OneLineLinearItem => DefaultLinearItem : SubLabel added.
                                            Use RelativeLayout.
    Apply DefaultLinerItemStyle.
   OutLineGridItem => DefaultGridItem : Label changed Caption.
                                        Use RelativeLayout.
Support CaptionOrientation.
Apply DefaultGridItemStyle.
   DefaultTitleItem is added for Header / GroupHeader.
3. Sample is udpated.
4. Remove _ in the code.

* [CollectionView] Refactoring class directories

- Move all item source related classes in ItemSource
- Move all item class in Item
- Move all layouter class in Layouter

* [NUI] add copy-right license pre-comments

* [NUI] Remove Old RecyclerView and Renamed ItemsView to RecyclerView

Old RecyclerView was never published or browsed, so get rid of this class and renamed ItemsView to RecyclerView.

RecyclerVIew/ -> Removed.
ItemsView -> RecyclerView
ViewItem -> RecyclerViewItem
ViewItemStyle -> RecyclerViewItemStyle

fix warnings.

* [NUI] fix build errors and comments.

FishEyeLayoutManager and WearableList no longer supported as recyclerView is removed.

* [NUI] Update Samples

* [NUI] apply cache in group header and footer

* [NUI] re-alive wearable list and move RecyclerView in wearble.

* Introduce CollectionView and related classes.

View :
[ItemsView] Base class of model based view. create item by DataTemplate and
            layouting them with layouter using source data. inherited from ScrollableBase.
[CollectionView] groupable and selectable ItemsView.

Item :
[ViewItem] Base item for CollectionView.
[ViewItem.Internal] Internal partial class of ViewItem.
[ViewItemStyle] Style class of ViewItem.
[OneLineLinearItem] Linear type ViewItem of 1 line text Label and Icon, Extra View.
[OutLineGridItem] Grid type ViewItem of outline caption Label and ImageView Image and View Badge.

Layouter :
[ItemsLayouter] Base compositor class of item layouter on ItemsView.
[LinearLayouter] Layouter class of layouting item in linear list. restricted for CollectionView class only.
[GridLayouter] Layouter class of layouting item in grid list. restricted for CollectionView class only.

Item Source :
[ItemsSourceFactory] Factory of creating IItemSource from IEnumerable source.
[IItemSource] Interface for encapsulated data source from IEnumerable user source.
[IGroupedItemSource] Interface for grouped user data.
[EmptySource] Empty data source.
[ListSource] List data source.
[ObservableItemSource] Item source who provide observable notifications.
[ObservableGroupedSource] Grouped item source who provide observable notifications.
[UngroupedItemSource] Ungrouped item source.

Enum and Interface :
[ICollectionChangedNotifier] Interface of collection changed notifier.
[ItemSelectionMode] Mode enum value for Selection.
[ItemSizingStrategy] Sizing strategy for item measure.
[SelectionChangedEventArgs] Event for Selection changed notify.

Helper Class :
[MarshalingObservableCollection] Marshaling ObservableCollection for itemSource.
[SelectionList] Internal list for SelectionModel.

Other Changes :
Moving and change internal to public of DataTemplate related classes.

Known Issues :
- Empty items or pages shows when scroll fast, jump by ScrollTo().
- ScrollTo() is moving incorrectly.
- Scrolling event passing scrollPosition negative value.
- Application cannot initialize the cached ViewItem.
- Image trembling while scrolling.

Remaining Works :
- Implement groupable features.
- Implement observable changes(ICollectionChangedNotifier).
- Implement dynamic size and dynamic template selector.
- Implement Item / Layouter animations and support custom animations.
- Adding samples and test.
- Adding Unit Test.
- Documents and guides.

Design Documents :
https://code.sec.samsung.net/confluence/display/ENUIFWC/5.+Model-based++User+Interfaces

* [NUI] Update templates to EditorBrowsableState.Never

* [CollectionView] fix wrong initialize layouter and item count which makes missing 0 and last indexed item in Header/Footer exist

* [CollectionView] fix missing call of OnRelayout in base class.

* [CollectionView] Implement Groupable Features and Refactoring Items

1. Implement Groupable features in CollectionView, LinearLayouter,
    GridLayouter.
2. Refactoring items and adding styles.
   OneLineLinearItem => DefaultLinearItem : SubLabel added.
                                            Use RelativeLayout.
    Apply DefaultLinerItemStyle.
   OutLineGridItem => DefaultGridItem : Label changed Caption.
                                        Use RelativeLayout.
Support CaptionOrientation.
Apply DefaultGridItemStyle.
   DefaultTitleItem is added for Header / GroupHeader.
3. Sample is udpated.
4. Remove _ in the code.

* [CollectionView] Refactoring class directories

- Move all item source related classes in ItemSource
- Move all item class in Item
- Move all layouter class in Layouter

* [NUI] add copy-right license pre-comments

* Introduce CollectionView and related classes.

View :
[ItemsView] Base class of model based view. create item by DataTemplate and
            layouting them with layouter using source data. inherited from ScrollableBase.
[CollectionView] groupable and selectable ItemsView.

Item :
[ViewItem] Base item for CollectionView.
[ViewItem.Internal] Internal partial class of ViewItem.
[ViewItemStyle] Style class of ViewItem.
[OneLineLinearItem] Linear type ViewItem of 1 line text Label and Icon, Extra View.
[OutLineGridItem] Grid type ViewItem of outline caption Label and ImageView Image and View Badge.

Layouter :
[ItemsLayouter] Base compositor class of item layouter on ItemsView.
[LinearLayouter] Layouter class of layouting item in linear list. restricted for CollectionView class only.
[GridLayouter] Layouter class of layouting item in grid list. restricted for CollectionView class only.

Item Source :
[ItemsSourceFactory] Factory of creating IItemSource from IEnumerable source.
[IItemSource] Interface for encapsulated data source from IEnumerable user source.
[IGroupedItemSource] Interface for grouped user data.
[EmptySource] Empty data source.
[ListSource] List data source.
[ObservableItemSource] Item source who provide observable notifications.
[ObservableGroupedSource] Grouped item source who provide observable notifications.
[UngroupedItemSource] Ungrouped item source.

Enum and Interface :
[ICollectionChangedNotifier] Interface of collection changed notifier.
[ItemSelectionMode] Mode enum value for Selection.
[ItemSizingStrategy] Sizing strategy for item measure.
[SelectionChangedEventArgs] Event for Selection changed notify.

Helper Class :
[MarshalingObservableCollection] Marshaling ObservableCollection for itemSource.
[SelectionList] Internal list for SelectionModel.

Other Changes :
Moving and change internal to public of DataTemplate related classes.

Known Issues :
- Empty items or pages shows when scroll fast, jump by ScrollTo().
- ScrollTo() is moving incorrectly.
- Scrolling event passing scrollPosition negative value.
- Application cannot initialize the cached ViewItem.
- Image trembling while scrolling.

Remaining Works :
- Implement groupable features.
- Implement observable changes(ICollectionChangedNotifier).
- Implement dynamic size and dynamic template selector.
- Implement Item / Layouter animations and support custom animations.
- Adding samples and test.
- Adding Unit Test.
- Documents and guides.

Design Documents :
https://code.sec.samsung.net/confluence/display/ENUIFWC/5.+Model-based++User+Interfaces

* [NUI] Update templates to EditorBrowsableState.Never

* [CollectionView] fix wrong initialize layouter and item count which makes missing 0 and last indexed item in Header/Footer exist

* [CollectionView] fix missing call of OnRelayout in base class.

* [CollectionView] Implement Groupable Features and Refactoring Items

1. Implement Groupable features in CollectionView, LinearLayouter,
    GridLayouter.
2. Refactoring items and adding styles.
   OneLineLinearItem => DefaultLinearItem : SubLabel added.
                                            Use RelativeLayout.
    Apply DefaultLinerItemStyle.
   OutLineGridItem => DefaultGridItem : Label changed Caption.
                                        Use RelativeLayout.
Support CaptionOrientation.
Apply DefaultGridItemStyle.
   DefaultTitleItem is added for Header / GroupHeader.
3. Sample is udpated.
4. Remove _ in the code.

* [CollectionView] Refactoring class directories

- Move all item source related classes in ItemSource
- Move all item class in Item
- Move all layouter class in Layouter

* [NUI] add copy-right license pre-comments

* [NUI] Remove Old RecyclerView and Renamed ItemsView to RecyclerView

Old RecyclerView was never published or browsed, so get rid of this class and renamed ItemsView to RecyclerView.

RecyclerVIew/ -> Removed.
ItemsView -> RecyclerView
ViewItem -> RecyclerViewItem
ViewItemStyle -> RecyclerViewItemStyle

fix warnings.

* [NUI] fix build errors and comments.

FishEyeLayoutManager and WearableList no longer supported as recyclerView is removed.

* [NUI] Update Samples

* [NUI] apply cache in group header and footer

* [NUI] re-alive wearable list and move RecyclerView in wearble.

Co-authored-by: dongsug-song <35130733+dongsug-song@users.noreply.github.com>
46 files changed:
src/Tizen.NUI.Components/Controls/RecyclerView/CollectionView.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ICollectionChangedNotifier.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/Item/DefaultGridItem.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/Item/DefaultLinearItem.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/Item/DefaultTitleItem.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/Item/RecyclerViewItem.Internal.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/Item/RecyclerViewItem.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSelectionMode.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSizingStrategy.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/EmptySource.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/IItemSource.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ItemsSourceFactory.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ListSource.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/MarshalingObservableCollection.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableGroupedSource.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableItemSource.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/UngroupedItemSource.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/ItemsLayouter.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs
src/Tizen.NUI.Components/Controls/RecyclerView/SelectionChangedEventArgs.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/SelectionList.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Style/DefaultGridItemStyle.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Style/DefaultLinearItemStyle.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Style/DefaultTitleItemStyle.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Style/RecyclerViewItemStyle.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Theme/DefaultTheme.cs
src/Tizen.NUI.Components/Theme/DefaultThemeMobile.cs
src/Tizen.NUI.Wearable/src/public/RecyclerView/GridRecycleLayoutManager.cs [moved from src/Tizen.NUI.Components/Controls/RecyclerView/GridRecycleLayoutManager.cs with 99% similarity]
src/Tizen.NUI.Wearable/src/public/RecyclerView/LinearRecycleLayoutManager.cs [moved from src/Tizen.NUI.Components/Controls/RecyclerView/LinearRecycleLayoutManager.cs with 99% similarity]
src/Tizen.NUI.Wearable/src/public/RecyclerView/RecycleAdapter.cs [moved from src/Tizen.NUI.Components/Controls/RecyclerView/RecycleAdapter.cs with 98% similarity]
src/Tizen.NUI.Wearable/src/public/RecyclerView/RecycleItem.cs [moved from src/Tizen.NUI.Components/Controls/RecyclerView/RecycleItem.cs with 96% similarity]
src/Tizen.NUI.Wearable/src/public/RecyclerView/RecycleLayoutManager.cs [moved from src/Tizen.NUI.Components/Controls/RecyclerView/RecycleLayoutManager.cs with 99% similarity]
src/Tizen.NUI.Wearable/src/public/RecyclerView/RecyclerView.cs [new file with mode: 0755]
src/Tizen.NUI/src/internal/XamlBinding/Internals/IDataTemplate.cs [deleted file]
src/Tizen.NUI/src/public/Template/DataTemplate.cs [moved from src/Tizen.NUI/src/internal/XamlBinding/DataTemplate.cs with 71% similarity]
src/Tizen.NUI/src/public/Template/DataTemplateExtensions.cs [moved from src/Tizen.NUI/src/internal/XamlBinding/DataTemplateExtensions.cs with 80% similarity]
src/Tizen.NUI/src/public/Template/DataTemplateSelector.cs [moved from src/Tizen.NUI/src/internal/XamlBinding/DataTemplateSelector.cs with 78% similarity]
src/Tizen.NUI/src/public/Template/ElementTemplate.cs [moved from src/Tizen.NUI/src/internal/XamlBinding/ElementTemplate.cs with 96% similarity]
src/Tizen.NUI/src/public/Template/IDataTemplate.cs [new file with mode: 0755]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewGridSample.cs [new file with mode: 0755]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewLinearSample.cs [new file with mode: 0755]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Gallery.cs [new file with mode: 0644]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewGridGroupSample.cs [new file with mode: 0644]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewLinearGroupSample.cs [new file with mode: 0644]

diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/CollectionView.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/CollectionView.cs
new file mode 100644 (file)
index 0000000..fc69968
--- /dev/null
@@ -0,0 +1,1068 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using System.Windows.Input;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// This class provides a View that can layouting items in list and grid with high performance.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class CollectionView : RecyclerView
+    {
+        /// <summary>
+        /// Binding Property of selected item in single selection.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty SelectedItemProperty =
+            BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(CollectionView), null,
+                propertyChanged: (bindable, oldValue, newValue)=>
+                {
+                    var colView = (CollectionView)bindable;
+                    oldValue = colView.selectedItem;
+                    colView.selectedItem = newValue;
+                    var args = new SelectionChangedEventArgs(oldValue, newValue);
+
+                    foreach (RecyclerViewItem item in colView.ContentContainer.Children.Where((item) => item is RecyclerViewItem))
+                    {
+                        if (item.BindingContext == null) continue;
+                        if (item.BindingContext == oldValue) item.IsSelected = false;
+                        else if (item.BindingContext == newValue) item.IsSelected = true;
+                    }
+
+                    SelectionPropertyChanged(colView, args);
+                },
+                defaultValueCreator: (bindable)=>
+                {
+                    var colView = (CollectionView)bindable;
+                    return colView.selectedItem;
+                });
+
+        /// <summary>
+        /// Binding Property of selected items list in multiple selection.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty SelectedItemsProperty =
+            BindableProperty.Create(nameof(SelectedItems), typeof(IList<object>), typeof(CollectionView), null,
+                propertyChanged: (bindable, oldValue, newValue)=>
+                {
+                    var colView = (CollectionView)bindable;
+                    var oldSelection = colView.selectedItems ?? selectEmpty;
+                    //FIXME : CoerceSelectedItems calls only isCreatedByXaml
+                    var newSelection = (SelectionList)CoerceSelectedItems(colView, newValue);
+                    colView.selectedItems = newSelection;
+                    colView.SelectedItemsPropertyChanged(oldSelection, newSelection);
+                },
+                defaultValueCreator: (bindable) =>
+                {
+                    var colView = (CollectionView)bindable;
+                    colView.selectedItems = colView.selectedItems ?? new SelectionList(colView);
+                    return colView.selectedItems;
+                });
+
+        /// <summary>
+        /// Binding Property of selected items list in multiple selection.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty SelectionModeProperty =
+            BindableProperty.Create(nameof(SelectionMode), typeof(ItemSelectionMode), typeof(CollectionView), ItemSelectionMode.None,
+                propertyChanged: (bindable, oldValue, newValue)=>
+                {
+                    var colView = (CollectionView)bindable;
+                    oldValue = colView.selectionMode;
+                    colView.selectionMode = (ItemSelectionMode)newValue;
+                    SelectionModePropertyChanged(colView, oldValue, newValue);
+                },
+                defaultValueCreator: (bindable) =>
+                {
+                    var colView = (CollectionView)bindable;
+                    return colView.selectionMode;
+                });
+
+
+        private static readonly IList<object> selectEmpty = new List<object>(0);
+        private DataTemplate itemTemplate = null;
+        private IEnumerable itemsSource = null;
+        private ItemsLayouter itemsLayouter = null;
+        private DataTemplate groupHeaderTemplate;
+        private DataTemplate groupFooterTemplate;
+        private bool isGrouped;
+        private bool wasRelayouted = false;
+        private bool needInitalizeLayouter = false;
+        private object selectedItem;
+        private SelectionList selectedItems;
+        private bool suppressSelectionChangeNotification;
+        private ItemSelectionMode selectionMode = ItemSelectionMode.None;
+        private RecyclerViewItem header;
+        private RecyclerViewItem footer;
+        private View focusedView;
+        private int prevFocusedDataIndex = 0;
+        private List<RecyclerViewItem> recycleGroupHeaderCache { get; } = new List<RecyclerViewItem>();
+        private List<RecyclerViewItem> recycleGroupFooterCache { get; } = new List<RecyclerViewItem>();
+
+        /// <summary>
+        /// Base constructor.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public CollectionView() : base()
+        {
+            FocusGroup = true;
+            SetKeyboardNavigationSupport(true);
+        }
+
+        /// <summary>
+        /// Base constructor with ItemsSource
+        /// </summary>
+        /// <param name="itemsSource">item's data source</param>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        public CollectionView(IEnumerable itemsSource) : this()
+        {
+            ItemsSource = itemsSource;
+        }
+
+        /// <summary>
+        /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
+        /// </summary>
+        /// <param name="itemsSource">item's data source</param>
+        /// <param name="layouter">item's layout manager</param>
+        /// <param name="template">item's view template with data bindings</param>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
+        {
+            ItemsSource = itemsSource;
+            ItemTemplate = template;
+            ItemsLayouter = layouter;
+        }
+
+        /// <summary>
+        /// Event of Selection changed.
+        /// old selection list and new selection will be provided.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
+
+        /// <summary>
+        /// Align item in the viewport when ScrollTo() calls.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        public enum ItemScrollTo
+        {
+            /// <summary>
+            /// Scroll to show item in nearest viewport on scroll direction.
+            /// item is above the scroll viewport, item will be came into front,
+            /// item is under the scroll viewport, item will be came into end,
+            /// item is in the scroll viewport, no scroll.
+            /// </summary>
+            Nearest,
+            /// <summary>
+            /// Scroll to show item in start of the viewport.
+            /// </summary>
+            Start,
+            /// <summary>
+            /// Scroll to show item in center of the viewport.
+            /// </summary>
+            Center,
+            /// <summary>
+            /// Scroll to show item in end of the viewport.
+            /// </summary>
+            End,
+        }
+
+        /// <summary>
+        /// Item's source data.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override IEnumerable ItemsSource
+        {
+            get
+            {
+                return itemsSource;
+            }
+            set
+            {
+
+                itemsSource = value;
+                if (value == null)
+                {
+                    if (InternalItemSource != null) InternalItemSource.Dispose();
+                    //layouter.Clear()
+                    return;
+                }
+                if (InternalItemSource != null) InternalItemSource.Dispose();
+                InternalItemSource = ItemsSourceFactory.Create(this);
+
+                if (itemsLayouter == null) return;
+
+                needInitalizeLayouter = true;
+                Init();
+            }
+        }
+
+        /// <summary>
+        /// DataTemplate for items.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override DataTemplate ItemTemplate
+        {
+            get
+            {
+                return itemTemplate;
+            }
+            set
+            {
+                itemTemplate = value;
+                if (value == null)
+                {
+                    //layouter.clear()
+                    return;
+                }
+
+                needInitalizeLayouter = true;
+                Init();
+            }
+        }
+
+        /// <summary>
+        /// Items Layouter.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual ItemsLayouter ItemsLayouter
+        {
+            get
+            {
+                return itemsLayouter;
+            }
+            set
+            {
+                itemsLayouter = value;
+                base.InternalItemsLayouter = ItemsLayouter;
+                if (value == null)
+                {
+                    needInitalizeLayouter = false;
+                    return;
+                }
+
+                needInitalizeLayouter = true;
+                Init();
+            }
+        }
+
+        /// <summary>
+        /// Scrolling direction to display items layout.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public new Direction ScrollingDirection
+        {
+            get
+            {
+                return base.ScrollingDirection;
+            }
+            set
+            {
+                base.ScrollingDirection = value;
+
+                if (ScrollingDirection == Direction.Horizontal)
+                {
+                    ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
+                }
+                else
+                {
+                    ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Selected item in single selection.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public object SelectedItem
+        {
+            get => GetValue(SelectedItemProperty);
+            set => SetValue(SelectedItemProperty, value);
+        }
+
+        /// <summary>
+        /// Selected items list in multiple selection.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public IList<object> SelectedItems
+        {
+            get => (IList<object>)GetValue(SelectedItemsProperty);
+            // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
+        }
+
+        /// <summary>
+        /// Selection mode to handle items selection. See ItemSelectionMode for details.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ItemSelectionMode SelectionMode
+        {
+            get => (ItemSelectionMode)GetValue(SelectionModeProperty);
+            set => SetValue(SelectionModeProperty, value);
+        }
+
+        /// <summary>
+        /// Command of selection changed.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ICommand SelectionChangedCommand { set; get; }
+
+        /// <summary>
+        /// Command parameter of selection changed.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public object SelectionChangedCommandParameter { set; get; }
+
+        /// <summary>
+        /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ItemSizingStrategy SizingStrategy { get; set; }
+
+        /// <summary>
+        /// Header item which placed in top-most position.
+        /// note : internal index and count will be increased.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecyclerViewItem Header
+        {
+            get => header;
+            set
+            {
+                if (header != null)
+                {
+                    //ContentContainer.Remove(header);
+                    Utility.Dispose(header);
+                }
+                if (value != null)
+                {
+                    value.Index = 0;
+                    value.ParentItemsView = this;
+                    value.IsHeader = true;
+                    ContentContainer.Add(value);
+                }
+                header = value;
+                needInitalizeLayouter = true;
+                Init();
+            }
+        }
+
+        /// <summary>
+        /// Footer item which placed in bottom-most position.
+        /// note : internal count will be increased.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecyclerViewItem Footer
+        {
+            get => footer;
+            set
+            {
+                if (footer != null)
+                {
+                    //ContentContainer.Remove(footer);
+                    Utility.Dispose(footer);
+                }
+                if (value != null)
+                {
+                    value.Index = InternalItemSource?.Count ?? 0;
+                    value.ParentItemsView = this;
+                    value.IsFooter = true;
+                    ContentContainer.Add(value);
+                }
+                footer = value;
+                needInitalizeLayouter = true;
+                Init();
+            }
+        }
+
+        /// <summary>
+        /// Boolean flag of group feature existence.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public bool IsGrouped 
+        { 
+            get => isGrouped;
+            set
+            {
+                isGrouped = value;
+                needInitalizeLayouter = true;
+                //Need to re-intialize Internal Item Source.
+                if (InternalItemSource != null)
+                {
+                    InternalItemSource.Dispose();
+                    InternalItemSource = null;
+                }
+                if (ItemsSource != null)
+                    InternalItemSource = ItemsSourceFactory.Create(this);
+                Init();
+            }
+        }
+
+        /// <summary>
+        ///  DataTemplate of group header. Group feature is not supported yet.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DataTemplate GroupHeaderTemplate
+        {
+            get
+            {
+                return groupHeaderTemplate;
+            }
+            set
+            {
+                groupHeaderTemplate = value;
+                needInitalizeLayouter = true;
+                Init();
+            }
+        }
+
+        /// <summary>
+        /// DataTemplate of group footer. Group feature is not supported yet.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DataTemplate GroupFooterTemplate
+        {
+            get
+            {
+                return groupFooterTemplate;
+            }
+            set
+            {
+                groupFooterTemplate = value;
+                needInitalizeLayouter = true;
+                Init();
+            }
+        }
+
+
+        /// <summary>
+        /// Internal encapsulated items data source.
+        /// </summary>
+        internal new IGroupableItemSource InternalItemSource
+        {
+            get
+            {
+                return (base.InternalItemSource as IGroupableItemSource);
+            }
+            set
+            {
+                base.InternalItemSource = value;
+            }
+        }
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void OnRelayout(Vector2 size, RelayoutContainer container)
+        {
+            base.OnRelayout(size, container);
+
+            wasRelayouted = true;
+            if (needInitalizeLayouter) Init();
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            View nextFocusedView = null;
+
+            if (focusedView == null)
+            {
+                // If focusedView is null, find child which has previous data index
+                if (ContentContainer.Children.Count > 0 && InternalItemSource.Count > 0)
+                {
+                    for (int i = 0; i < ContentContainer.Children.Count; i++)
+                    {
+                        RecyclerViewItem item = Children[i] as RecyclerViewItem;
+                        if (item?.Index == prevFocusedDataIndex)
+                        {
+                            nextFocusedView = item;
+                            break;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                // If this is not first focus, request next focus to Layouter
+                nextFocusedView = ItemsLayouter.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
+            }
+
+            if (nextFocusedView != null)
+            {
+                // Check next focused view is inside of visible area.
+                // If it is not, move scroll position to make it visible.
+                Position scrollPosition = ContentContainer.CurrentPosition;
+                float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
+
+                float left = nextFocusedView.Position.X;
+                float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
+                float top = nextFocusedView.Position.Y;
+                float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
+
+                float visibleRectangleLeft = -scrollPosition.X;
+                float visibleRectangleRight = -scrollPosition.X + Size.Width;
+                float visibleRectangleTop = -scrollPosition.Y;
+                float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
+
+                if (ScrollingDirection == Direction.Horizontal)
+                {
+                    if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
+                    {
+                        targetPosition = left;
+                    }
+                    else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
+                    {
+                        targetPosition = right - Size.Width;
+                    }
+                }
+                else
+                {
+                    if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
+                    {
+                        targetPosition = top;
+                    }
+                    else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
+                    {
+                        targetPosition = bottom - Size.Height;
+                    }
+                }
+
+                focusedView = nextFocusedView;
+                prevFocusedDataIndex = (nextFocusedView as RecyclerViewItem)?.Index ?? -1;
+
+                ScrollTo(targetPosition, true);
+            }
+            else
+            {
+                // If nextView is null, it means that we should move focus to outside of Control.
+                // Return FocusableView depending on direction.
+                switch (direction)
+                {
+                    case View.FocusDirection.Left:
+                    {
+                        nextFocusedView = LeftFocusableView;
+                        break;
+                    }
+                    case View.FocusDirection.Right:
+                    {
+                        nextFocusedView = RightFocusableView;
+                        break;
+                    }
+                    case View.FocusDirection.Up:
+                    {
+                        nextFocusedView = UpFocusableView;
+                        break;
+                    }
+                    case View.FocusDirection.Down:
+                    {
+                        nextFocusedView = DownFocusableView;
+                        break;
+                    }
+                }
+
+                if(nextFocusedView != null)
+                {
+                    focusedView = null;
+                }
+                else
+                {
+                    //If FocusableView doesn't exist, not move focus.
+                    nextFocusedView = focusedView;
+                }
+            }
+
+            return nextFocusedView;
+        }
+
+        /// <summary>
+        /// Update selected items list in multiple selection.
+        /// </summary>
+        /// <param name="newSelection">updated selection list by user</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void UpdateSelectedItems(IList<object> newSelection)
+        {
+            var oldSelection = new List<object>(SelectedItems);
+
+            suppressSelectionChangeNotification = true;
+
+            SelectedItems.Clear();
+
+            if (newSelection?.Count > 0)
+            {
+                for (int n = 0; n < newSelection.Count; n++)
+                {
+                    SelectedItems.Add(newSelection[n]);
+                }
+            }
+
+            suppressSelectionChangeNotification = false;
+
+            SelectedItemsPropertyChanged(oldSelection, newSelection);
+        }
+
+        /// <summary>
+        /// Scroll to specific position with or without animation.
+        /// </summary>
+        /// <param name="position">Destination.</param>
+        /// <param name="animate">Scroll with or without animation</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public new void ScrollTo(float position, bool animate) => base.ScrollTo(position, animate);
+
+        /// <summary>
+        /// Scroll to specific item's aligned position with or without animation.
+        /// </summary>
+        /// <param name="item">Target item of dataset.</param>
+        /// <param name="animate">Boolean flag of animation.</param>
+        /// <param name="align">Align state of item. see details in ItemScrollTo.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void ScrollTo(object item, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
+        {
+            if (item == null) throw new ArgumentNullException(nameof(item));
+            if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
+
+            if (InternalItemSource.GetPosition(item) == -1)
+            {
+                throw new Exception("ScrollTo parameter item is not a member of ItemsSource");
+            }
+
+            float scrollPos, curPos, curSize, curItemSize;
+            (float x, float y) = ItemsLayouter.GetItemPosition(item);
+            (float width, float height) = ItemsLayouter.GetItemSize(item);
+            if (ScrollingDirection == Direction.Horizontal)
+            {
+                scrollPos = x;
+                curPos = ScrollPosition.X;
+                curSize = Size.Width;
+                curItemSize = width;
+            }
+            else
+            {
+                scrollPos = y;
+                curPos = ScrollPosition.Y;
+                curSize = Size.Height;
+                curItemSize = height;
+            }
+
+            //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalItemSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
+            switch (align)
+            {
+                case ItemScrollTo.Start:
+                    //nothing necessary.
+                break;
+                case ItemScrollTo.Center:
+                    scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
+                break;
+                case ItemScrollTo.End:
+                    scrollPos = scrollPos - curSize + curItemSize;
+                break;
+                case ItemScrollTo.Nearest:
+                    if (scrollPos < curPos - curItemSize)
+                    {
+                        // item is placed before the current screen. scrollTo.Top
+                    }
+                    else if (scrollPos >= curPos + curSize + curItemSize)
+                    {
+                        // item is placed after the current screen. scrollTo.End
+                        scrollPos = scrollPos - curSize + curItemSize;
+                    }
+                    else
+                    {
+                        // item is in the scroller. ScrollTo() is ignored.
+                        return;
+                    }
+                break;
+            }
+
+            //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
+            base.ScrollTo(scrollPos, animate);
+        }
+
+        // Realize and Decorate the item.
+        internal override RecyclerViewItem RealizeItem(int index)
+        {
+            if (index == 0 && Header != null)
+            {
+                Header.Show();
+                return Header;
+            }
+
+            if (index == InternalItemSource.Count - 1 && Footer != null)
+            {
+                Footer.Show();
+                return Footer;
+            }
+
+            if (isGrouped)
+            {
+                var context = InternalItemSource.GetItem(index);
+                if (InternalItemSource.IsGroupHeader(index))
+                {
+                    DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
+
+                    RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
+                    if (groupHeader == null)
+                    {
+                        groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
+
+                        groupHeader.ParentItemsView = this;
+                        groupHeader.Template = templ;
+                        groupHeader.isGroupHeader = true;
+                        groupHeader.isGroupFooter = false;
+                        ContentContainer.Add(groupHeader);
+                    }
+                    groupHeader.Index = index;
+                    groupHeader.ParentGroup = context;
+                    groupHeader.BindingContext = context;
+                    //group selection?
+                    return groupHeader;
+                }
+                else if (InternalItemSource.IsGroupFooter(index))
+                {
+                    DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
+
+                    RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
+                    if (groupFooter == null)
+                    {
+                        groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this);
+
+                        groupFooter.ParentItemsView = this;
+                        groupFooter.Template = templ;
+                        groupFooter.isGroupHeader = false;
+                        groupFooter.isGroupFooter = true;
+                        ContentContainer.Add(groupFooter);
+                    }
+                    groupFooter.Index = index;
+                    groupFooter.ParentGroup = context;
+                    groupFooter.BindingContext = context;
+
+                    //group selection?
+                    return groupFooter;
+                }
+            }
+
+            RecyclerViewItem item = base.RealizeItem(index);
+            if (isGrouped) item.ParentGroup = InternalItemSource.GetGroupParent(index);
+
+            switch (SelectionMode)
+            {
+                case ItemSelectionMode.SingleSelection:
+                    if (item.BindingContext == SelectedItem) item.IsSelected = true;
+                    break;
+
+                case ItemSelectionMode.MultipleSelections:
+                    if (SelectedItems?.Contains(item.BindingContext) ?? false) item.IsSelected = true;
+                    break;
+                case ItemSelectionMode.None:
+                    item.IsSelectable = false;
+                    break;
+            }
+
+            return item;
+        }
+
+        // Unrealize and caching the item.
+        internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
+        {
+            if (item == Header)
+            {
+                item.Hide();
+                return;
+            }
+            if (item == Footer)
+            {
+                item.Hide();
+                return;
+            }
+            if (item.isGroupHeader || item.isGroupFooter)
+            {
+                if (!recycle || !PushRecycleGroupCache(item))
+                    Utility.Dispose(item);
+                return;
+            }
+
+            item.IsSelected = false;
+            base.UnrealizeItem(item, recycle);
+        }
+
+        internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
+        {
+            if (suppressSelectionChangeNotification)
+            {
+                return;
+            }
+
+            foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
+            {
+                if (item.BindingContext == null) continue;
+                if (newSelection.Contains(item.BindingContext))
+                {
+                    if (!item.IsSelected) item.IsSelected = true;
+                }
+                else
+                {
+                    if (item.IsSelected) item.IsSelected = false;
+                }
+            }
+            SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
+
+            OnPropertyChanged(SelectedItemsProperty.PropertyName);
+        }
+
+        /// <summary>
+        /// Internal selection callback.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
+        {
+            //Selection Callback
+        }
+
+        /// <summary>
+        /// Adjust scrolling position by own scrolling rules.
+        /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
+        /// </summary>
+        /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
+        /// <returns>Adjusted scroll destination</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override float AdjustTargetPositionOfScrollAnimation(float position)
+        {
+            // Destination is depending on implementation of layout manager.
+            // Get destination from layout manager.
+            return ItemsLayouter.CalculateCandidateScrollPosition(position);
+        }
+
+        /// <summary>
+        /// OnScroll event callback.
+        /// </summary>
+        /// <param name="source">Scroll source object</param>
+        /// <param name="args">Scroll event argument</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void OnScrolling(object source, ScrollEventArgs args)
+        {
+            if (disposed) return;
+
+            if (needInitalizeLayouter)
+            {
+                ItemsLayouter.Initialize(this);
+                needInitalizeLayouter = false;
+            }
+            base.OnScrolling(source, args);
+        }
+
+        /// <summary>
+        /// Dispose ItemsView and all children on it.
+        /// </summary>
+        /// <param name="type">Dispose type.</param>
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+            if (type == DisposeTypes.Explicit)
+            {
+                disposed = true;
+                if (InternalItemSource != null)
+                {
+                    InternalItemSource.Dispose();
+                    InternalItemSource = null;
+                }
+                if (Header != null)
+                {
+                    Utility.Dispose(Header);
+                    Header = null;
+                }
+                if (Footer != null)
+                {
+                    Utility.Dispose(Footer);
+                    Footer = null;
+                }
+                groupHeaderTemplate = null;
+                groupFooterTemplate = null;
+                //
+            }
+
+            base.Dispose(type);
+        }
+
+        private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
+        {
+            var command = colView.SelectionChangedCommand;
+
+            if (command != null)
+            {
+                var commandParameter = colView.SelectionChangedCommandParameter;
+
+                if (command.CanExecute(commandParameter))
+                {
+                    command.Execute(commandParameter);
+                }
+            }
+            colView.SelectionChanged?.Invoke(colView, args);
+            colView.OnSelectionChanged(args);
+        }
+
+        private static object CoerceSelectedItems(BindableObject bindable, object value)
+        {
+            if (value == null)
+            {
+                return new SelectionList((CollectionView)bindable);
+            }
+
+            if (value is SelectionList)
+            {
+                return value;
+            }
+
+            return new SelectionList((CollectionView)bindable, value as IList<object>);
+        }
+
+        private static void SelectionModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+        {
+            var colView = (CollectionView)bindable;
+
+            var oldMode = (ItemSelectionMode)oldValue;
+            var newMode = (ItemSelectionMode)newValue;
+
+            IList<object> previousSelection = new List<object>();
+            IList<object> newSelection = new List<object>();
+
+            switch (oldMode)
+            {
+                case ItemSelectionMode.None:
+                    break;
+                case ItemSelectionMode.SingleSelection:
+                    if (colView.SelectedItem != null)
+                    {
+                        previousSelection.Add(colView.SelectedItem);
+                    }
+                    break;
+                case ItemSelectionMode.MultipleSelections:
+                    previousSelection = colView.SelectedItems;
+                    break;
+            }
+
+            switch (newMode)
+            {
+                case ItemSelectionMode.None:
+                    break;
+                case ItemSelectionMode.SingleSelection:
+                    if (colView.SelectedItem != null)
+                    {
+                        newSelection.Add(colView.SelectedItem);
+                    }
+                    break;
+                case ItemSelectionMode.MultipleSelections:
+                    newSelection = colView.SelectedItems;
+                    break;
+            }
+
+            if (previousSelection.Count == newSelection.Count)
+            {
+                if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
+                {
+                    // Both selections are empty or have the same single item; no reason to signal a change
+                    return;
+                }
+            }
+
+            var args = new SelectionChangedEventArgs(previousSelection, newSelection);
+            SelectionPropertyChanged(colView, args);
+        }
+
+        private void Init()
+        {
+            if (ItemsSource == null) return;
+            if (ItemsLayouter == null) return;
+            if (ItemTemplate == null) return;
+
+            if (disposed) return;
+            if (needInitalizeLayouter)
+            {
+                if (InternalItemSource == null) return;
+
+                InternalItemSource.HasHeader = (header != null);
+                InternalItemSource.HasFooter = (footer != null);
+            }
+
+            if (!wasRelayouted) return;
+
+            if (needInitalizeLayouter)
+            {
+                ItemsLayouter.Initialize(this);
+                needInitalizeLayouter = false;
+            }
+            ItemsLayouter.RequestLayout(0.0f, true);
+
+            if (ScrollingDirection == Direction.Horizontal)
+            {
+                ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
+            }
+            else
+            {
+                ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
+            }
+        }
+
+        private bool PushRecycleGroupCache(RecyclerViewItem item)
+        {
+            if (item == null) throw new ArgumentNullException(nameof(item));
+            if (RecycleCache.Count >= 20) return false;
+            if (item.Template == null) return false;
+            if (item.isGroupHeader)
+            {
+                recycleGroupHeaderCache.Add(item);
+            }
+            else if (item.isGroupFooter)
+            {
+                recycleGroupFooterCache.Add(item);
+            }
+            else return false;
+            item.Hide();
+            item.Index = -1;
+            return true;
+        }
+
+        private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
+        {
+            RecyclerViewItem viewItem = null;
+
+            var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
+            for (int i = 0; i < Cache.Count; i++)
+            {
+                viewItem = Cache[i];
+                if (Template == viewItem.Template) break;
+            }
+
+            if (viewItem != null)
+            {
+                Cache.Remove(viewItem);
+                viewItem.Show();
+            }
+            return viewItem;
+        }
+
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ICollectionChangedNotifier.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ICollectionChangedNotifier.cs
new file mode 100644 (file)
index 0000000..d00695d
--- /dev/null
@@ -0,0 +1,94 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// Notify observers about dataset changes of observable items.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public interface ICollectionChangedNotifier
+    {
+
+        /// <summary>
+        /// Notify the dataset is Changed.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        void NotifyDataSetChanged();
+
+        /// <summary>
+        /// Notify the observable item in startIndex is changed.
+        /// </summary>
+        /// <param name="source">dataset source</param>
+        /// <param name="startIndex">changed item index</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        void NotifyItemChanged(IItemSource source, int startIndex);
+
+        /// <summary>
+        /// Notify the observable item is inserted in dataset.
+        /// </summary>
+        /// <param name="source">dataset source</param>
+        /// <param name="startIndex">Inserted item index</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        void NotifyItemInserted(IItemSource source, int startIndex);
+
+        /// <summary>
+        /// Notify the observable item is moved from fromPosition to ToPosition.
+        /// </summary>
+        /// <param name="source"></param>
+        /// <param name="fromPosition"></param>
+        /// <param name="toPosition"></param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition);
+
+        /// <summary>
+        /// Notify the range of observable items from start to end are changed.
+        /// </summary>
+        /// <param name="source"></param>
+        /// <param name="startIndex"></param>
+        /// <param name="endIndex"></param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        void NotifyItemRangeChanged(IItemSource source, int startIndex, int endIndex);
+
+        /// <summary>
+        /// Notify the count range of observable items are inserted in startIndex.
+        /// </summary>
+        /// <param name="source"></param>
+        /// <param name="startIndex"></param>
+        /// <param name="count"></param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        void NotifyItemRangeInserted(IItemSource source, int startIndex, int count);
+
+        /// <summary>
+        /// Notify the count range of observable items from the startIndex are removed.
+        /// </summary>
+        /// <param name="source"></param>
+        /// <param name="startIndex"></param>
+        /// <param name="count"></param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count);
+
+        /// <summary>
+        /// Notify the observable item in startIndex is removed.
+        /// </summary>
+        /// <param name="source"></param>
+        /// <param name="startIndex"></param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        void NotifyItemRemoved(IItemSource source, int startIndex);
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Item/DefaultGridItem.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Item/DefaultGridItem.cs
new file mode 100644 (file)
index 0000000..b618bc7
--- /dev/null
@@ -0,0 +1,517 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// DefaultGridItem is one kind of common component, a DefaultGridItem clearly describes what action will occur when the user selects it.
+    /// DefaultGridItem may contain text or an icon.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class DefaultGridItem : RecyclerViewItem
+    {
+        private TextLabel itemCaption;
+        private ImageView itemImage;
+        private View itemBadge;
+        private CaptionOrientation captionOrientation;
+        private bool layoutChanged;
+
+        private DefaultGridItemStyle ItemStyle => ViewStyle as DefaultGridItemStyle;
+
+        /// <summary>
+        /// Return a copied Style instance of DefaultLinearItem
+        /// </summary>
+        /// <remarks>
+        /// It returns copied Style instance and changing it does not effect to the DefaultLinearItem.
+        /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public new DefaultGridItemStyle Style
+        {
+            get
+            {
+                var result = new DefaultGridItemStyle(ItemStyle);
+                result.CopyPropertiesFromView(this);
+                if (itemCaption) result.Caption.CopyPropertiesFromView(itemCaption);
+                if (itemImage) result.Image.CopyPropertiesFromView(itemImage);
+                if (itemBadge) result.Badge.CopyPropertiesFromView(itemBadge);
+
+                return result;
+            }
+        }
+
+        static DefaultGridItem() {}
+
+        /// <summary>
+        /// Creates a new instance of DefaultGridItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultGridItem() : base()
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Creates a new instance of DefaultGridItem with style
+        /// </summary>
+        /// <param name="style=">Create DefaultGridItem by special style defined in UX.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultGridItem(string style) : base(style)
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Creates a new instance of DefaultGridItem with style
+        /// </summary>
+        /// <param name="itemStyle=">Create DefaultGridItem by style customized by user.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultGridItem(DefaultGridItemStyle itemStyle) : base(itemStyle)
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Caption orientation.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public enum CaptionOrientation
+        {
+            /// <summary>
+            /// Outside of image bottom edge.
+            /// </summary>
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            OutsideBottom,
+            /// <summary>
+            /// Outside of image top edge.
+            /// </summary>
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            OutsideTop,
+            /// <summary>
+            /// inside of image bottom edge.
+            /// </summary>
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            InsideBottom,
+            /// <summary>
+            /// inside of image top edge.
+            /// </summary>
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            InsideTop,
+        }
+
+        /// <summary>
+        /// DefaultGridItem's icon part.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ImageView Image
+        {
+            get
+            {
+                if ( itemImage == null)
+                {
+                    itemImage = CreateImage(ItemStyle.Image);
+                    if (itemImage != null)
+                    {
+                        Add(itemImage);
+                        itemImage.Relayout += OnImageRelayout;
+                        layoutChanged = true;
+                    }
+                }
+                return itemImage;
+            }
+            internal set
+            {
+                itemImage = value;
+                layoutChanged = true;
+            }
+        }
+
+
+        /// <summary>
+        /// DefaultGridItem's badge object. will be placed in right-top edge.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public View Badge
+        {
+            get
+            {
+                return itemBadge;
+
+            }
+            set
+            {
+                if (value == null)
+                {
+                    Remove(itemBadge);
+                }
+                itemBadge = value;
+                if (itemBadge != null)
+                {
+                    itemBadge.ApplyStyle(ItemStyle.Badge);
+                    Add(itemBadge);
+                }
+                layoutChanged = true;
+            }
+        }
+
+/* open when ImageView using Uri not string
+        /// <summary>
+        /// Image image's resource url in DefaultGridItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string ImageUrl
+        {
+            get
+            {
+                return Image.ResourceUrl;
+            }
+            set
+            {
+                Image.ResourceUrl = value;
+            }
+        }
+*/
+
+        /// <summary>
+        /// DefaultGridItem's text part.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TextLabel Caption
+        {
+            get
+            {
+                if (itemCaption == null)
+                {
+                    itemCaption = CreateLabel(ItemStyle.Caption);
+                    if (itemCaption != null)
+                    {
+                        Add(itemCaption);
+                        layoutChanged = true;
+                    }
+                }
+                return itemCaption;
+            }
+            internal set
+            {
+                itemCaption = value;
+                layoutChanged = true;
+                AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Label, itemCaption.Text);
+            }
+        }
+
+        /// <summary>
+        /// The text of DefaultGridItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string Text
+        {
+            get
+            {
+                return Caption.Text;
+            }
+            set
+            {
+                Caption.Text = value;
+            }
+        }
+
+        /// <summary>
+        /// Caption relative orientation with image in DefaultGridItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public CaptionOrientation CaptionRelativeOrientation
+        {
+            get
+            {
+                return captionOrientation;
+            }
+            set
+            {
+                captionOrientation = value;
+                layoutChanged = true;
+            }
+        }
+
+        /// <summary>
+        /// Apply style to DefaultLinearItemStyle.
+        /// </summary>
+        /// <param name="viewStyle">The style to apply.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void ApplyStyle(ViewStyle viewStyle)
+        {
+
+            base.ApplyStyle(viewStyle);
+            if (viewStyle != null && viewStyle is DefaultGridItemStyle defaultStyle)
+            {
+                if (itemCaption != null)
+                    itemCaption.ApplyStyle(defaultStyle.Caption);
+                if (itemImage != null)
+                    itemImage.ApplyStyle(defaultStyle.Image);
+                if (itemBadge != null)
+                    itemBadge.ApplyStyle(defaultStyle.Badge);
+            }
+        }
+
+        /// <summary>
+        /// Creates Item's text part.
+        /// </summary>
+        /// <return>The created Item's text part.</return>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual TextLabel CreateLabel(TextLabelStyle textStyle)
+        {
+            return new TextLabel(textStyle)
+            {
+                HorizontalAlignment = HorizontalAlignment.Center,
+                VerticalAlignment = VerticalAlignment.Center
+            };
+        }
+
+        /// <summary>
+        /// Creates Item's icon part.
+        /// </summary>
+        /// <return>The created Item's icon part.</return>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual ImageView CreateImage(ImageViewStyle imageStyle)
+        {
+            return new ImageView(imageStyle);
+        }
+
+         /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void MeasureChild()
+        {
+            //nothing to do.
+            if (itemCaption)
+            {
+                var pad = Padding;
+                var margin = itemCaption.Margin;
+                itemCaption.SizeWidth = SizeWidth - pad.Start - pad.End - margin.Start - margin.End;
+            }
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void LayoutChild()
+        {
+            if (!layoutChanged) return;
+            if (itemImage == null) return;
+
+            layoutChanged = false;
+
+            RelativeLayout.SetLeftTarget(itemImage, this);
+            RelativeLayout.SetLeftRelativeOffset(itemImage, 0.0F);
+            RelativeLayout.SetRightTarget(itemImage, this);
+            RelativeLayout.SetRightRelativeOffset(itemImage, 1.0F);
+            RelativeLayout.SetHorizontalAlignment(itemImage, RelativeLayout.Alignment.Center);
+
+            if (itemCaption != null)
+            {
+                itemCaption.RaiseAbove(itemImage);
+                RelativeLayout.SetLeftTarget(itemCaption, itemImage);
+                RelativeLayout.SetLeftRelativeOffset(itemCaption, 0.0F);
+                RelativeLayout.SetRightTarget(itemCaption, itemImage);
+                RelativeLayout.SetRightRelativeOffset(itemCaption, 1.0F);
+                RelativeLayout.SetHorizontalAlignment(itemCaption, RelativeLayout.Alignment.Center);
+                RelativeLayout.SetFillHorizontal(itemCaption, true);
+            }
+            else
+            {
+                RelativeLayout.SetTopTarget(itemImage, this);
+                RelativeLayout.SetTopRelativeOffset(itemImage, 0.0F);
+                RelativeLayout.SetBottomTarget(itemImage, this);
+                RelativeLayout.SetBottomRelativeOffset(itemImage, 1.0F);
+                RelativeLayout.SetVerticalAlignment(itemImage, RelativeLayout.Alignment.Center);
+            }
+
+            if (itemBadge)
+            {
+                RelativeLayout.SetLeftTarget(itemBadge, itemImage);
+                RelativeLayout.SetLeftRelativeOffset(itemBadge, 1.0F);
+                RelativeLayout.SetRightTarget(itemBadge, itemImage);
+                RelativeLayout.SetRightRelativeOffset(itemBadge, 1.0F);
+                RelativeLayout.SetHorizontalAlignment(itemBadge, RelativeLayout.Alignment.End);
+            }
+
+            switch (captionOrientation)
+            {
+                case CaptionOrientation.OutsideBottom:
+                    if (itemCaption != null)
+                    {
+                        RelativeLayout.SetTopTarget(itemCaption, this);
+                        RelativeLayout.SetTopRelativeOffset(itemCaption, 1.0F);
+                        RelativeLayout.SetBottomTarget(itemCaption, this);
+                        RelativeLayout.SetBottomRelativeOffset(itemCaption, 1.0F);
+                        RelativeLayout.SetVerticalAlignment(itemCaption, RelativeLayout.Alignment.End);
+
+                        RelativeLayout.SetTopTarget(itemImage, this);
+                        RelativeLayout.SetTopRelativeOffset(itemImage, 0.0F);
+                        RelativeLayout.SetBottomTarget(itemImage, itemCaption);
+                        RelativeLayout.SetBottomRelativeOffset(itemImage, 0.0F);
+                        RelativeLayout.SetVerticalAlignment(itemImage, RelativeLayout.Alignment.Center);
+                    }
+
+                    if (itemBadge)
+                    {
+                        RelativeLayout.SetTopTarget(itemBadge, itemImage);
+                        RelativeLayout.SetTopRelativeOffset(itemBadge, 0.0F);
+                        RelativeLayout.SetBottomTarget(itemBadge, itemImage);
+                        RelativeLayout.SetBottomRelativeOffset(itemBadge, 0.0F);
+                        RelativeLayout.SetVerticalAlignment(itemBadge, RelativeLayout.Alignment.Start);
+                    }
+                    break;
+
+                case CaptionOrientation.OutsideTop:
+                    if (itemCaption != null)
+                    {
+                        RelativeLayout.SetTopTarget(itemCaption, this);
+                        RelativeLayout.SetTopRelativeOffset(itemCaption, 0.0F);
+                        RelativeLayout.SetBottomTarget(itemCaption, this);
+                        RelativeLayout.SetBottomRelativeOffset(itemCaption, 0.0F);
+                        RelativeLayout.SetVerticalAlignment(itemCaption, RelativeLayout.Alignment.Start);
+
+                        RelativeLayout.SetTopTarget(itemImage, itemCaption);
+                        RelativeLayout.SetTopRelativeOffset(itemImage, 1.0F);
+                        RelativeLayout.SetBottomTarget(itemImage, this);
+                        RelativeLayout.SetBottomRelativeOffset(itemImage, 1.0F);
+                        RelativeLayout.SetVerticalAlignment(itemImage, RelativeLayout.Alignment.Center);
+                    }
+
+                    if (itemBadge)
+                    {
+                        RelativeLayout.SetTopTarget(itemBadge, itemImage);
+                        RelativeLayout.SetTopRelativeOffset(itemBadge, 1.0F);
+                        RelativeLayout.SetBottomTarget(itemBadge, itemImage);
+                        RelativeLayout.SetBottomRelativeOffset(itemBadge, 1.0F);
+                        RelativeLayout.SetVerticalAlignment(itemBadge, RelativeLayout.Alignment.End);
+                    }
+                    break;
+
+                case CaptionOrientation.InsideBottom:
+                    if (itemCaption != null)
+                    {
+                        RelativeLayout.SetTopTarget(itemCaption, this);
+                        RelativeLayout.SetTopRelativeOffset(itemCaption, 1.0F);
+                        RelativeLayout.SetBottomTarget(itemCaption, this);
+                        RelativeLayout.SetBottomRelativeOffset(itemCaption, 1.0F);
+                        RelativeLayout.SetVerticalAlignment(itemCaption, RelativeLayout.Alignment.End);
+
+                        RelativeLayout.SetTopTarget(itemImage, this);
+                        RelativeLayout.SetTopRelativeOffset(itemImage, 0.0F);
+                        RelativeLayout.SetBottomTarget(itemImage, this);
+                        RelativeLayout.SetBottomRelativeOffset(itemImage, 1.0F);
+                        RelativeLayout.SetVerticalAlignment(itemImage, RelativeLayout.Alignment.Center);
+                    }
+
+                    if (itemBadge)
+                    {
+                        RelativeLayout.SetTopTarget(itemBadge, itemImage);
+                        RelativeLayout.SetTopRelativeOffset(itemBadge, 0.0F);
+                        RelativeLayout.SetBottomTarget(itemBadge, itemImage);
+                        RelativeLayout.SetBottomRelativeOffset(itemBadge, 0.0F);
+                        RelativeLayout.SetVerticalAlignment(itemBadge, RelativeLayout.Alignment.Start);
+                    }
+                    break;
+
+                case CaptionOrientation.InsideTop:
+                    if (itemCaption != null)
+                    {
+                        RelativeLayout.SetTopTarget(itemCaption, this);
+                        RelativeLayout.SetTopRelativeOffset(itemCaption, 0.0F);
+                        RelativeLayout.SetBottomTarget(itemCaption, this);
+                        RelativeLayout.SetBottomRelativeOffset(itemCaption, 0.0F);
+                        RelativeLayout.SetVerticalAlignment(itemCaption, RelativeLayout.Alignment.Start);
+
+                        RelativeLayout.SetTopTarget(itemImage, this);
+                        RelativeLayout.SetTopRelativeOffset(itemImage, 0.0F);
+                        RelativeLayout.SetBottomTarget(itemImage, this);
+                        RelativeLayout.SetBottomRelativeOffset(itemImage, 1.0F);
+                        RelativeLayout.SetVerticalAlignment(itemImage, RelativeLayout.Alignment.Center);
+                    }
+
+                    if (itemBadge)
+                    {
+                        RelativeLayout.SetTopTarget(itemBadge, itemImage);
+                        RelativeLayout.SetTopRelativeOffset(itemBadge, 1.0F);
+                        RelativeLayout.SetBottomTarget(itemBadge, itemImage);
+                        RelativeLayout.SetBottomRelativeOffset(itemBadge, 1.0F);
+                        RelativeLayout.SetVerticalAlignment(itemBadge, RelativeLayout.Alignment.End);
+                    }
+                    break;
+            }
+
+
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        private void Initialize()
+        {
+            Layout = new RelativeLayout();
+            layoutChanged = true;
+            LayoutDirectionChanged += OnLayoutDirectionChanged;
+        }
+
+        /// <summary>
+        /// Dispose Item and all children on it.
+        /// </summary>
+        /// <param name="type">Dispose type.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+            if (type == DisposeTypes.Explicit)
+            {
+                //Extension : Extension?.OnDispose(this);
+
+                if (itemImage != null)
+                {
+                    Utility.Dispose(itemImage);
+                }
+                if (itemCaption != null)
+                {
+                    Utility.Dispose(itemCaption);
+                }
+                if (itemBadge != null)
+                {
+                    Utility.Dispose(itemBadge);
+                }
+            }
+
+            base.Dispose(type);
+        }
+
+        private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e)
+        {
+            MeasureChild();
+            LayoutChild();
+        }
+        private void OnImageRelayout(object sender, EventArgs e)
+        {
+            MeasureChild();
+            LayoutChild();
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Item/DefaultLinearItem.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Item/DefaultLinearItem.cs
new file mode 100644 (file)
index 0000000..0e9972f
--- /dev/null
@@ -0,0 +1,565 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// DefaultLinearItem is one kind of common component, a DefaultLinearItem clearly describes what action will occur when the user selects it.
+    /// DefaultLinearItem may contain text or an icon.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class DefaultLinearItem : RecyclerViewItem
+    {
+        private View itemIcon;
+        private TextLabel itemLabel;
+        private TextLabel itemSubLabel;
+        private View itemExtra;
+        private View itemSeperator;
+        private bool layoutChanged;
+        private Size prevSize;
+        private DefaultLinearItemStyle ItemStyle => ViewStyle as DefaultLinearItemStyle;
+
+        /// <summary>
+        /// Return a copied Style instance of DefaultLinearItem
+        /// </summary>
+        /// <remarks>
+        /// It returns copied Style instance and changing it does not effect to the DefaultLinearItem.
+        /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public new DefaultLinearItemStyle Style
+        {
+            get
+            {
+                var result = new DefaultLinearItemStyle(ItemStyle);
+                result.CopyPropertiesFromView(this);
+                if (itemLabel) result.Label.CopyPropertiesFromView(itemLabel);
+                if (itemSubLabel) result.SubLabel.CopyPropertiesFromView(itemSubLabel);
+                if (itemIcon) result.Icon.CopyPropertiesFromView(itemIcon);
+                if (itemExtra) result.Extra.CopyPropertiesFromView(itemExtra);
+                if (itemSeperator) result.Seperator.CopyPropertiesFromView(itemSeperator);
+
+                return result;
+            }
+        }
+
+        static DefaultLinearItem() {}
+
+        /// <summary>
+        /// Creates a new instance of DefaultLinearItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultLinearItem() : base()
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Creates a new instance of a DefaultLinearItem with style.
+        /// </summary>
+        /// <param name="style">Create DefaultLinearItem by style defined in UX.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultLinearItem(string style) : base(style)
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Creates a new instance of a DefaultLinearItem with style.
+        /// </summary>
+        /// <param name="itemStyle">Create DefaultLinearItem by style customized by user.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultLinearItem(DefaultLinearItemStyle itemStyle) : base(itemStyle)
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Icon part of DefaultLinearItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public View Icon
+        {
+            get
+            {
+                if (itemIcon == null)
+                {
+                    itemIcon = CreateIcon(ItemStyle?.Icon);
+                    if (itemIcon != null)
+                    {
+                        layoutChanged = true;
+                        Add(itemIcon);
+                        itemIcon.Relayout += OnIconRelayout;
+                    }
+                }
+                return itemIcon;
+            }
+            set
+            {
+                itemIcon = value;
+            }
+        }
+
+        /* open when imageView using Uri not string.
+        /// <summary>
+        /// Icon image's resource url. Only activatable for icon as ImageView.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string IconUrl
+        {
+            get
+            {
+                return (Icon as ImageView)?.ResourceUrl;
+            }
+            set
+            {
+                if (itemIcon != null && !(itemIcon is ImageView))
+                {
+                    // Tizen.Log.Error("IconUrl only can set Icon is ImageView");
+                    return;
+                }
+                (Icon as ImageView).ResourceUrl = value; 
+            }
+        }
+        */
+
+        /// <summary>
+        /// DefaultLinearItem's text part of DefaultLinearItem
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TextLabel Label
+        {
+            get
+            {
+                if (itemLabel == null)
+                {
+                    itemLabel = CreateLabel(ItemStyle?.Label);
+                    if (itemLabel != null)
+                    {
+                        layoutChanged = true;
+                        Add(itemLabel);
+                    }
+                }
+                return itemLabel;
+            }
+            internal set
+            {
+                itemLabel = value;
+                AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Label, itemLabel.Text);
+            }
+        }
+
+        /// <summary>
+        /// The text of DefaultLinearItem.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        public string Text
+        {
+            get
+            {
+                return Label.Text;
+            }
+            set
+            {
+                Label.Text = value;
+            }
+        }
+
+        /// <summary>
+        /// DefaultLinearItem's secondary text part of DefaultLinearItem
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TextLabel SubLabel
+        {
+            get
+            {
+                if (itemSubLabel == null)
+                {
+                    itemSubLabel = CreateLabel(ItemStyle?.SubLabel);
+                    if (itemLabel != null)
+                    {
+                        layoutChanged = true;
+                        Add(itemSubLabel);
+                    }
+                }
+                return itemSubLabel;
+            }
+            internal set
+            {
+                itemSubLabel = value;
+                AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Label, itemSubLabel.Text);
+            }
+        }
+
+        /// <summary>
+        /// The text of DefaultLinearItem.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        public string SubText
+        {
+            get
+            {
+                return SubLabel.Text;
+            }
+            set
+            {
+                SubLabel.Text = value;
+            }
+        }
+
+        /// <summary>
+        /// Extra icon part of DefaultLinearItem. it will place next of label.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public View Extra
+        {
+            get
+            {
+                if (itemExtra == null)
+                {
+                    itemExtra = CreateIcon(ItemStyle?.Extra);
+                    if (itemExtra != null)
+                    {
+                        layoutChanged = true;
+                        Add(itemExtra);
+                        itemExtra.Relayout += OnExtraRelayout;
+                    }
+                }
+                return itemExtra;
+            }
+            set
+            {
+                if (itemExtra != null) Remove(itemExtra);
+                itemExtra = value;
+                Add(itemExtra);
+            }
+        }
+
+        /// <summary>
+        /// Seperator devider of DefaultLinearItem. it will place at the end of item.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public View Seperator
+        {
+            get
+            {
+                if (itemSeperator == null)
+                {
+                    itemSeperator = new View(ItemStyle?.Seperator)
+                    {
+                        //need to consider horizontal/vertical!
+                        WidthSpecification = LayoutParamPolicies.MatchParent,
+                        HeightSpecification = 2,
+                        ExcludeLayouting = true
+                    };
+                    layoutChanged = true;
+                    Add(itemSeperator);
+                }
+                return itemSeperator;
+            }
+        }
+
+        /// <summary>
+        /// Apply style to DefaultLinearItemStyle.
+        /// </summary>
+        /// <param name="viewStyle">The style to apply.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void ApplyStyle(ViewStyle viewStyle)
+        {
+
+            base.ApplyStyle(viewStyle);
+            if (viewStyle != null && viewStyle is DefaultLinearItemStyle defaultStyle)
+            {
+                if (itemLabel != null)
+                    itemLabel.ApplyStyle(defaultStyle.Label);
+                if (itemSubLabel != null)
+                    itemSubLabel.ApplyStyle(defaultStyle.SubLabel);
+                if (itemIcon != null)
+                    itemIcon.ApplyStyle(defaultStyle.Icon);
+                if (itemExtra != null)
+                    itemExtra.ApplyStyle(defaultStyle.Extra);
+                if (itemSeperator != null)
+                    itemSeperator.ApplyStyle(defaultStyle.Seperator);
+            }
+        }
+
+        /// <inheritdoc />
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void OnRelayout(Vector2 size, RelayoutContainer container)
+        {
+            base.OnRelayout(size, container);
+
+            if (prevSize != Size)
+            {
+                prevSize = Size;
+                if (itemSeperator)
+                {
+                    var margin = itemSeperator.Margin;
+                    itemSeperator.SizeWidth = SizeWidth - margin.Start - margin.End;
+                    itemSeperator.SizeHeight = itemSeperator.HeightSpecification;
+                    itemSeperator.Position = new Position(margin.Start, SizeHeight - margin.Bottom -itemSeperator.SizeHeight);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Creates Item's text part.
+        /// </summary>
+        /// <return>The created Item's text part.</return>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual TextLabel CreateLabel(TextLabelStyle style)
+        {
+            return new TextLabel(style)
+            {
+                HorizontalAlignment = HorizontalAlignment.Begin,
+                VerticalAlignment = VerticalAlignment.Center
+            };
+        }
+
+        /// <summary>
+        /// Creates Item's icon part.
+        /// </summary>
+        /// <return>The created Item's icon part.</return>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual ImageView CreateIcon(ViewStyle style)
+        {
+            return new ImageView(style);
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void MeasureChild()
+        {
+            var pad = Padding;
+            if (itemLabel)
+            {
+                var margin = itemLabel.Margin;
+                itemLabel.SizeWidth = SizeWidth - pad.Start - pad.End - margin.Start - margin.End;
+            }
+
+            if (itemSubLabel)
+            {
+                var margin = itemSubLabel.Margin;
+                itemSubLabel.SizeWidth = SizeWidth - pad.Start - pad.End - margin.Start - margin.End;
+            }
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void LayoutChild()
+        {
+            if (!layoutChanged) return;
+            if (itemLabel == null) return;
+
+            layoutChanged = false;
+
+            if (itemIcon != null)
+            {
+                RelativeLayout.SetLeftTarget(itemIcon, this);
+                RelativeLayout.SetLeftRelativeOffset(itemIcon, 0.0F);
+                RelativeLayout.SetRightTarget(itemIcon, this);
+                RelativeLayout.SetRightRelativeOffset(itemIcon, 0.0F);
+                RelativeLayout.SetTopTarget(itemIcon, this);
+                RelativeLayout.SetTopRelativeOffset(itemIcon, 0.0F);
+                RelativeLayout.SetBottomTarget(itemIcon, this);
+                RelativeLayout.SetBottomRelativeOffset(itemIcon, 1.0F);
+                RelativeLayout.SetVerticalAlignment(itemIcon, RelativeLayout.Alignment.Center);
+                RelativeLayout.SetHorizontalAlignment(itemIcon, RelativeLayout.Alignment.Start);
+            }
+
+            if (itemExtra != null)
+            {
+                RelativeLayout.SetLeftTarget(itemExtra, this);
+                RelativeLayout.SetLeftRelativeOffset(itemExtra, 1.0F);
+                RelativeLayout.SetRightTarget(itemExtra, this);
+                RelativeLayout.SetRightRelativeOffset(itemIcon, 1.0F);
+                RelativeLayout.SetTopTarget(itemExtra, this);
+                RelativeLayout.SetTopRelativeOffset(itemExtra, 0.0F);
+                RelativeLayout.SetBottomTarget(itemExtra, this);
+                RelativeLayout.SetBottomRelativeOffset(itemExtra, 1.0F);
+                RelativeLayout.SetVerticalAlignment(itemExtra, RelativeLayout.Alignment.Center);
+                RelativeLayout.SetHorizontalAlignment(itemExtra, RelativeLayout.Alignment.End);
+            }
+
+            if (itemSubLabel != null)
+            {
+                if (itemIcon)
+                {
+                    RelativeLayout.SetLeftTarget(itemSubLabel, itemIcon);
+                    RelativeLayout.SetLeftRelativeOffset(itemSubLabel, 1.0F);
+                }
+                else
+                {
+                    RelativeLayout.SetLeftTarget(itemSubLabel, this);
+                    RelativeLayout.SetLeftRelativeOffset(itemSubLabel, 0.0F);
+                }
+                if (itemExtra)
+                {
+                    RelativeLayout.SetRightTarget(itemSubLabel, itemExtra);
+                    RelativeLayout.SetRightRelativeOffset(itemSubLabel, 0.0F);
+                }
+                else
+                {
+                    RelativeLayout.SetRightTarget(itemSubLabel, this);
+                    RelativeLayout.SetRightRelativeOffset(itemSubLabel, 1.0F);
+                }
+
+                RelativeLayout.SetTopTarget(itemSubLabel, this);
+                RelativeLayout.SetTopRelativeOffset(itemSubLabel, 1.0F);
+                RelativeLayout.SetBottomTarget(itemSubLabel, this);
+                RelativeLayout.SetBottomRelativeOffset(itemSubLabel, 1.0F);
+                RelativeLayout.SetVerticalAlignment(itemSubLabel, RelativeLayout.Alignment.End);
+
+                RelativeLayout.SetHorizontalAlignment(itemSubLabel, RelativeLayout.Alignment.Center);
+                RelativeLayout.SetFillHorizontal(itemSubLabel, true);
+            }
+
+            if (itemIcon)
+            {
+                RelativeLayout.SetLeftTarget(itemLabel, itemIcon);
+                RelativeLayout.SetLeftRelativeOffset(itemLabel, 1.0F);
+            }
+            else
+            {
+                RelativeLayout.SetLeftTarget(itemLabel, this);
+                RelativeLayout.SetLeftRelativeOffset(itemLabel, 0.0F);
+            }
+            if (itemExtra)
+            {
+                RelativeLayout.SetRightTarget(itemLabel, itemExtra);
+                RelativeLayout.SetRightRelativeOffset(itemLabel, 0.0F);
+            }
+            else
+            {
+                RelativeLayout.SetRightTarget(itemLabel, this);
+                RelativeLayout.SetRightRelativeOffset(itemLabel, 1.0F);
+            }
+
+            RelativeLayout.SetTopTarget(itemLabel, this);
+            RelativeLayout.SetTopRelativeOffset(itemLabel, 0.0F);
+
+            if (itemSubLabel)
+            {
+                RelativeLayout.SetBottomTarget(itemLabel, itemSubLabel);
+                RelativeLayout.SetBottomRelativeOffset(itemLabel, 0.0F);
+            }
+            else
+            {
+                RelativeLayout.SetBottomTarget(itemLabel, this);
+                RelativeLayout.SetBottomRelativeOffset(itemLabel, 1.0F);
+            }
+            RelativeLayout.SetVerticalAlignment(itemLabel, RelativeLayout.Alignment.Center);
+            RelativeLayout.SetHorizontalAlignment(itemLabel, RelativeLayout.Alignment.Center);
+            RelativeLayout.SetFillHorizontal(itemLabel, true);
+
+            if (prevSize != Size)
+            {
+                prevSize = Size;
+                if (itemSeperator)
+                {
+                    var margin = itemSeperator.Margin;
+                    itemSeperator.SizeWidth = SizeWidth - margin.Start - margin.End;
+                    itemSeperator.SizeHeight = itemSeperator.HeightSpecification;
+                    itemSeperator.Position = new Position(margin.Start, SizeHeight - margin.Bottom -itemSeperator.SizeHeight);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Dispose Item and all children on it.
+        /// </summary>
+        /// <param name="type">Dispose type.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+            if (type == DisposeTypes.Explicit)
+            {
+                //Extension : Extension?.OnDispose(this);
+
+                if (itemIcon != null)
+                {
+                    Utility.Dispose(itemIcon);
+                }
+                if (itemExtra != null)
+                {
+                    Utility.Dispose(itemExtra);
+                }
+                if (itemLabel != null)
+                {
+                    Utility.Dispose(itemLabel);
+                }
+                if (itemSubLabel != null)
+                {
+                    Utility.Dispose(itemSubLabel);
+                }
+                if (itemSeperator != null)
+                {
+                    Utility.Dispose(itemSeperator);
+                }
+            }
+
+            base.Dispose(type);
+        }
+
+        /// <summary>
+        /// Get DefaultLinearItem style.
+        /// </summary>
+        /// <returns>The default DefaultLinearItem style.</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override ViewStyle CreateViewStyle()
+        {
+            return new DefaultLinearItemStyle();
+        }
+
+        private void Initialize()
+        {
+            base.OnInitialize();
+            Layout = new RelativeLayout();
+            var seperator = Seperator;
+            layoutChanged = true;
+            LayoutDirectionChanged += OnLayoutDirectionChanged;
+        }
+
+        private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e)
+        {
+            MeasureChild();
+            LayoutChild();
+        }
+
+        private void OnIconRelayout(object sender, EventArgs e)
+        {
+            MeasureChild();
+            LayoutChild();
+        }
+
+        private void OnExtraRelayout(object sender, EventArgs e)
+        {
+            MeasureChild();
+            LayoutChild();
+        }        
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Item/DefaultTitleItem.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Item/DefaultTitleItem.cs
new file mode 100644 (file)
index 0000000..432f61b
--- /dev/null
@@ -0,0 +1,409 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// DefaultTitleItem is one kind of common component, a DefaultTitleItem clearly describes what action will occur when the user selects it.
+    /// DefaultTitleItem may contain text or an icon.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class DefaultTitleItem : RecyclerViewItem
+    {
+        private TextLabel itemLabel;
+        private View itemIcon;
+        private View itemSeperator;
+        private bool layoutChanged;
+        private Size prevSize;
+        private DefaultTitleItemStyle ItemStyle => ViewStyle as DefaultTitleItemStyle;
+
+        /// <summary>
+        /// Return a copied Style instance of DefaultTitleItem
+        /// </summary>
+        /// <remarks>
+        /// It returns copied Style instance and changing it does not effect to the DefaultTitleItem.
+        /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public new DefaultTitleItemStyle Style
+        {
+            get
+            {
+                var result = new DefaultTitleItemStyle(ItemStyle);
+                result.CopyPropertiesFromView(this);
+                if (itemLabel) result.Label.CopyPropertiesFromView(itemLabel);
+                if (itemIcon) result.Icon.CopyPropertiesFromView(itemIcon);
+                if (itemSeperator) result.Seperator.CopyPropertiesFromView(itemSeperator);
+
+                return result;
+            }
+        }
+
+        static DefaultTitleItem() {}
+
+        /// <summary>
+        /// Creates a new instance of DefaultTitleItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultTitleItem() : base()
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Creates a new instance of a DefaultTitleItem with style.
+        /// </summary>
+        /// <param name="style">Create DefaultTitleItem by style defined in UX.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultTitleItem(string style) : base(style)
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Creates a new instance of a DefaultTitleItem with style.
+        /// </summary>
+        /// <param name="itemStyle">Create DefaultTitleItem by style customized by user.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultTitleItem(DefaultTitleItemStyle itemStyle) : base(itemStyle)
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Icon part of DefaultTitleItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public View Icon
+        {
+            get
+            {
+                if (itemIcon == null)
+                {
+                    itemIcon = CreateIcon(ItemStyle?.Icon);
+                    if (itemIcon != null)
+                    {
+                        layoutChanged = true;
+                        Add(itemIcon);
+                        itemIcon.Relayout += OnIconRelayout;
+                    }
+                }
+                return itemIcon;
+            }
+            set
+            {
+                itemIcon = value;
+            }
+        }
+
+        /* open when imageView using Uri not string.
+        /// <summary>
+        /// Icon image's resource url. Only activatable for icon as ImageView.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string IconUrl
+        {
+            get
+            {
+                return (Icon as ImageView)?.ResourceUrl;
+            }
+            set
+            {
+                if (itemIcon != null && !(itemIcon is ImageView))
+                {
+                    // Tizen.Log.Error("IconUrl only can set Icon is ImageView");
+                    return;
+                }
+                (Icon as ImageView).ResourceUrl = value; 
+            }
+        }
+        */
+
+        /// <summary>
+        /// DefaultTitleItem's text part of DefaultTitleItem
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TextLabel Label
+        {
+            get
+            {
+                if (itemLabel == null)
+                {
+                    itemLabel = CreateLabel(ItemStyle?.Label);
+                    if (itemLabel != null)
+                    {
+                        layoutChanged = true;
+                        Add(itemLabel);
+                    }
+                }
+                return itemLabel;
+            }
+            internal set
+            {
+                itemLabel = value;
+                AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Label, itemLabel.Text);
+            }
+        }
+
+        /// <summary>
+        /// The text of DefaultTitleItem.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        public string Text
+        {
+            get
+            {
+                return Label.Text;
+            }
+            set
+            {
+                Label.Text = value;
+            }
+        }
+
+        /// <summary>
+        /// Seperator devider of DefaultTitleItem. it will place at the end of item.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public View Seperator
+        {
+            get
+            {
+                if (itemSeperator == null)
+                {
+                    itemSeperator = new View(ItemStyle?.Seperator)
+                    {
+                        //need to consider horizontal/vertical!
+                        WidthSpecification = LayoutParamPolicies.MatchParent,
+                        HeightSpecification = 2,
+                        ExcludeLayouting = true
+                    };
+                    layoutChanged = true;
+                    Add(itemSeperator);
+                }
+                return itemSeperator;
+            }
+        }
+
+        /// <summary>
+        /// Apply style to DefaultTitleItemStyle.
+        /// </summary>
+        /// <param name="viewStyle">The style to apply.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void ApplyStyle(ViewStyle viewStyle)
+        {
+
+            base.ApplyStyle(viewStyle);
+            if (viewStyle != null && viewStyle is DefaultTitleItemStyle defaultStyle)
+            {
+                if (itemLabel != null)
+                    itemLabel.ApplyStyle(defaultStyle.Label);
+                if (itemIcon != null)
+                    itemIcon.ApplyStyle(defaultStyle.Icon);
+                if (itemSeperator != null)
+                    itemSeperator.ApplyStyle(defaultStyle.Seperator);
+            }
+        }
+
+        /// <inheritdoc />
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void OnRelayout(Vector2 size, RelayoutContainer container)
+        {
+            base.OnRelayout(size, container);
+
+            if (prevSize != Size)
+            {
+                prevSize = Size;
+                if (itemSeperator)
+                {
+                    var margin = itemSeperator.Margin;
+                    itemSeperator.SizeWidth = SizeWidth - margin.Start - margin.End;
+                    itemSeperator.SizeHeight = itemSeperator.HeightSpecification;
+                    itemSeperator.Position = new Position(margin.Start, SizeHeight - margin.Bottom -itemSeperator.SizeHeight);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Creates Item's text part.
+        /// </summary>
+        /// <return>The created Item's text part.</return>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual TextLabel CreateLabel(TextLabelStyle style)
+        {
+            return new TextLabel(style)
+            {
+                HorizontalAlignment = HorizontalAlignment.Begin,
+                VerticalAlignment = VerticalAlignment.Center
+            };
+        }
+
+        /// <summary>
+        /// Creates Item's icon part.
+        /// </summary>
+        /// <return>The created Item's icon part.</return>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual ImageView CreateIcon(ViewStyle style)
+        {
+            return new ImageView(style);
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void MeasureChild()
+        {
+            if (itemLabel)
+            {
+                var pad = Padding;
+                var margin = itemLabel.Margin;
+                itemLabel.SizeWidth = SizeWidth - pad.Start - pad.End - margin.Start - margin.End;
+            }
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void LayoutChild()
+        {
+            if (!layoutChanged) return;
+            if (itemLabel == null) return;
+
+            layoutChanged = false;
+
+            if (itemIcon != null)
+            {
+                RelativeLayout.SetLeftTarget(itemIcon, this);
+                RelativeLayout.SetLeftRelativeOffset(itemIcon, 1.0F);
+                RelativeLayout.SetRightTarget(itemIcon, this);
+                RelativeLayout.SetRightRelativeOffset(itemIcon, 1.0F);
+                RelativeLayout.SetTopTarget(itemIcon, this);
+                RelativeLayout.SetTopRelativeOffset(itemIcon, 0.0F);
+                RelativeLayout.SetBottomTarget(itemIcon, this);
+                RelativeLayout.SetBottomRelativeOffset(itemIcon, 1.0F);
+                RelativeLayout.SetVerticalAlignment(itemIcon, RelativeLayout.Alignment.Center);
+                RelativeLayout.SetHorizontalAlignment(itemIcon, RelativeLayout.Alignment.End);
+            }
+
+            RelativeLayout.SetLeftTarget(itemLabel, this);
+            RelativeLayout.SetLeftRelativeOffset(itemLabel, 0.0F);
+            if (itemIcon)
+            {
+                RelativeLayout.SetRightTarget(itemLabel, itemIcon);
+                RelativeLayout.SetRightRelativeOffset(itemLabel, 0.0F);
+            }
+            else
+            {
+                RelativeLayout.SetRightTarget(itemLabel, this);
+                RelativeLayout.SetRightRelativeOffset(itemLabel, 1.0F);
+            }
+
+            RelativeLayout.SetTopTarget(itemLabel, this);
+            RelativeLayout.SetTopRelativeOffset(itemLabel, 0.0F);
+            RelativeLayout.SetBottomTarget(itemLabel, this);
+            RelativeLayout.SetBottomRelativeOffset(itemLabel, 1.0F);
+            RelativeLayout.SetVerticalAlignment(itemLabel, RelativeLayout.Alignment.Center);
+            RelativeLayout.SetHorizontalAlignment(itemLabel, RelativeLayout.Alignment.Center);
+            RelativeLayout.SetFillHorizontal(itemLabel, true);
+
+            if (prevSize != Size)
+            {
+                prevSize = Size;
+                if (itemSeperator)
+                {
+                    var margin = itemSeperator.Margin;
+                    itemSeperator.SizeWidth = SizeWidth - margin.Start - margin.End;
+                    itemSeperator.SizeHeight = itemSeperator.HeightSpecification;
+                    itemSeperator.Position = new Position(margin.Start, SizeHeight - margin.Bottom -itemSeperator.SizeHeight);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Dispose Item and all children on it.
+        /// </summary>
+        /// <param name="type">Dispose type.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+            if (type == DisposeTypes.Explicit)
+            {
+                //Extension : Extension?.OnDispose(this);
+
+                if (itemIcon != null)
+                {
+                    Utility.Dispose(itemIcon);
+                }
+                if (itemLabel != null)
+                {
+                    Utility.Dispose(itemLabel);
+                }
+                if (itemSeperator != null)
+                {
+                    Utility.Dispose(itemSeperator);
+                }
+            }
+
+            base.Dispose(type);
+        }
+
+        /// <summary>
+        /// Get DefaultTitleItem style.
+        /// </summary>
+        /// <returns>The default DefaultTitleItem style.</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override ViewStyle CreateViewStyle()
+        {
+            return new DefaultTitleItemStyle();
+        }
+
+        private void Initialize()
+        {
+            base.OnInitialize();
+            Layout = new RelativeLayout();
+            var seperator = Seperator;
+            layoutChanged = true;
+            LayoutDirectionChanged += OnLayoutDirectionChanged;
+        }
+
+        private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e)
+        {
+            MeasureChild();
+            LayoutChild();
+        }
+
+        private void OnIconRelayout(object sender, EventArgs e)
+        {
+            MeasureChild();
+            LayoutChild();
+        }
+
+        private void OnExtraRelayout(object sender, EventArgs e)
+        {
+            MeasureChild();
+            LayoutChild();
+        }        
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Item/RecyclerViewItem.Internal.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Item/RecyclerViewItem.Internal.cs
new file mode 100644 (file)
index 0000000..980dca2
--- /dev/null
@@ -0,0 +1,280 @@
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility; // To use AccessibilityManager
+
+namespace Tizen.NUI.Components
+{
+    public partial class RecyclerViewItem
+    {
+        internal RecyclerView ParentItemsView = null;
+        internal object ParentGroup = null;
+        internal bool isGroupHeader;
+        internal bool isGroupFooter;
+        private bool styleApplied = false;
+
+        /// <summary>
+        /// Update ViewItem State.
+        /// </summary>
+        internal void UpdateState()
+        {
+            if (!styleApplied) return;
+
+            ControlState sourceState = ControlState;
+            ControlState targetState;
+
+            // Normal, Disabled
+            targetState = IsEnabled ? ControlState.Normal : ControlState.Disabled;
+
+            // Selected, DisabledSelected
+            if (IsSelected) targetState += ControlState.Selected;
+
+            // Pressed, PressedSelected
+            if (IsPressed) targetState += ControlState.Pressed;
+
+            // Focused, FocusedPressed, FocusedPressedSelected, DisabledFocused, DisabledSelectedFocused
+            if (IsFocused) targetState += ControlState.Focused;
+
+            if (sourceState != targetState)
+            {
+                ControlState = targetState;
+                OnUpdate();
+            }
+        }
+
+        internal override bool OnAccessibilityActivated()
+        {
+            if (!IsEnabled)
+            {
+                return false;
+            }
+
+            // Touch Down
+            IsPressed = true;
+            UpdateState();
+
+            // Touch Up
+            bool clicked = IsPressed && IsEnabled;
+            IsPressed = false;
+
+            if (IsSelectable)
+            {
+                //IsSelected = !IsSelected;
+            }
+            else
+            {
+                UpdateState();
+            }
+
+            if (clicked)
+            {
+                ClickedEventArgs eventArgs = new ClickedEventArgs();
+                OnClickedInternal(eventArgs);
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Called when the ViewItem is Clicked by a user
+        /// </summary>
+        /// <param name="eventArgs">The click information.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual void OnClicked(ClickedEventArgs eventArgs)
+        {
+            //Console.WriteLine("On Clicked Called {0}", this.Index);
+        }
+
+        /// <summary>
+        /// Called when the ViewItem need to be updated
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void OnUpdate()
+        {
+            base.OnUpdate();
+            UpdateContent();
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override bool HandleControlStateOnTouch(Touch touch)
+        {
+            if (!IsEnabled || null == touch)
+            {
+                return false;
+            }
+
+            PointStateType state = touch.GetState(0);
+
+            switch (state)
+            {
+                case PointStateType.Down:
+                    IsPressed = true;
+                    UpdateState();
+                    return true;
+                case PointStateType.Interrupted:
+                    IsPressed = false;
+                    UpdateState();
+                    return true;
+                case PointStateType.Up:
+                    {
+                        bool clicked = IsPressed && IsEnabled;
+                        IsPressed = false;
+
+                        if (!clicked) return true;
+
+                        if (IsSelectable)
+                        {
+                            if (ParentItemsView as CollectionView)
+                            {
+                                CollectionView colView = ParentItemsView as CollectionView;
+                                switch (colView.SelectionMode)
+                                {
+                                    case ItemSelectionMode.SingleSelection :
+                                        colView.SelectedItem = IsSelected ? null : BindingContext;
+                                        break;
+                                    case ItemSelectionMode.MultipleSelections :
+                                        var selectedItems = colView.SelectedItems;
+                                        if (selectedItems.Contains(BindingContext)) selectedItems.Remove(BindingContext);
+                                        else selectedItems.Add(BindingContext);
+                                        break;
+                                    case ItemSelectionMode.None :
+                                        break;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            // Extension : Extension?.SetTouchInfo(touch);
+                            UpdateState();
+                        }
+
+                        if (clicked)
+                        {
+                            ClickedEventArgs eventArgs = new ClickedEventArgs();
+                            OnClickedInternal(eventArgs);
+                        }
+
+                        return true;
+                    }
+                default:
+                    break;
+            }
+            return base.HandleControlStateOnTouch(touch);
+        }
+
+
+        /// <summary>
+        /// Measure child, it can be override.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual void MeasureChild()
+        {
+        }
+
+        /// <summary>
+        /// Layout child, it can be override.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual void LayoutChild()
+        {
+        }
+
+        /// <summary>
+        /// Dispose Item and all children on it.
+        /// </summary>
+        /// <param name="type">Dispose type.</param>
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+            if (type == DisposeTypes.Explicit)
+            {
+                //
+            }
+
+            base.Dispose(type);
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void OnControlStateChanged(ControlStateChangedEventArgs controlStateChangedInfo)
+        {
+            if (controlStateChangedInfo == null) throw new ArgumentNullException(nameof(controlStateChangedInfo));
+            base.OnControlStateChanged(controlStateChangedInfo);
+
+            var stateEnabled = !controlStateChangedInfo.CurrentState.Contains(ControlState.Disabled);
+
+            if (IsEnabled != stateEnabled)
+            {
+                IsEnabled = stateEnabled;
+            }
+
+            var statePressed = controlStateChangedInfo.CurrentState.Contains(ControlState.Pressed);
+
+            if (IsPressed != statePressed)
+            {
+                IsPressed = statePressed;
+            }
+        }
+
+        /// <summary>
+        /// Get ViewItem style.
+        /// </summary>
+        /// <returns>The default ViewItem style.</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override ViewStyle CreateViewStyle()
+        {
+            return new RecyclerViewItemStyle();
+        }
+
+        /// <summary>
+        /// It is hijack by using protected, style copy problem when class inherited from Button.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        private void Initialize()
+        {
+            //FIXME!
+            IsCreateByXaml = true;
+            Layout = new AbsoluteLayout();
+            UpdateState();
+
+            AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "ViewItem");
+        }
+
+        /// <summary>
+        /// Update the Content. it can be override.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual void UpdateContent()
+        {
+            MeasureChild();
+            LayoutChild();
+
+            Sensitive = IsEnabled;
+        }
+
+
+        /// FIXME!! This has to be done in Element or View class.
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void OnBindingContextChanged()
+        {
+            foreach (View child in Children)
+            {
+                SetChildInheritedBindingContext(child, BindingContext);
+            }
+        }
+
+        private void OnClickedInternal(ClickedEventArgs eventArgs)
+        {
+            Command?.Execute(CommandParameter);
+            OnClicked(eventArgs);
+
+            Clicked?.Invoke(this, eventArgs);
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Item/RecyclerViewItem.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Item/RecyclerViewItem.cs
new file mode 100644 (file)
index 0000000..5f3e882
--- /dev/null
@@ -0,0 +1,328 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// This class provides a basic item for CollectionView.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public partial class RecyclerViewItem : Control
+    {
+        /// <summary>
+        /// Property of boolean Enable flag.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(nameof(IsEnabled), typeof(bool), typeof(RecyclerViewItem), true, propertyChanged: (bindable, oldValue, newValue) =>
+        {
+            var instance = (RecyclerViewItem)bindable;
+            if (newValue != null)
+            {
+                bool newEnabled = (bool)newValue;
+                if (instance.isEnabled != newEnabled)
+                {
+                    instance.isEnabled = newEnabled;
+                    if (instance.ItemStyle != null)
+                    {
+                        instance.ItemStyle.IsEnabled = newEnabled;
+                    }
+                    instance.UpdateState();
+                }
+            }
+        },
+        defaultValueCreator: (bindable) => ((RecyclerViewItem)bindable).isEnabled);
+
+        /// <summary>
+        /// Property of boolean Selected flag.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty IsSelectedProperty = BindableProperty.Create(nameof(IsSelected), typeof(bool), typeof(RecyclerViewItem), true, propertyChanged: (bindable, oldValue, newValue) =>
+        {
+            var instance = (RecyclerViewItem)bindable;
+            if (newValue != null)
+            {
+                bool newSelected = (bool)newValue;
+                if (instance.isSelected != newSelected)
+                {
+                    instance.isSelected = newSelected;
+
+                    if (instance.ItemStyle != null)
+                    {
+                        instance.ItemStyle.IsSelected = newSelected;
+                    }
+
+                    if (instance.isSelectable)
+                    {
+                        instance.UpdateState();
+                    }
+                }
+            }
+        },
+        defaultValueCreator: (bindable) =>
+        {
+            var instance = (RecyclerViewItem)bindable;
+            return instance.isSelectable && instance.isSelected;
+        });
+
+        /// <summary>
+        /// Property of boolean Selectable flag.
+        /// </summary>      
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty IsSelectableProperty = BindableProperty.Create(nameof(IsSelectable), typeof(bool), typeof(RecyclerViewItem), true, propertyChanged: (bindable, oldValue, newValue) =>
+        {
+            var instance = (RecyclerViewItem)bindable;
+            if (newValue != null)
+            {
+                bool newSelectable = (bool)newValue;
+                if (instance.isSelectable != newSelectable)
+                {
+                    instance.isSelectable = newSelectable;
+
+                    if (instance.ItemStyle != null)
+                    {
+                        instance.ItemStyle.IsSelectable = newSelectable;
+                    }
+
+                    instance.UpdateState();
+                }
+            }
+        },
+        defaultValueCreator: (bindable) => ((RecyclerViewItem)bindable).isSelectable);
+
+        private bool isSelected = false;
+        private bool isSelectable = true;
+        private bool isEnabled = true;
+        private RecyclerViewItemStyle ItemStyle => ViewStyle as RecyclerViewItemStyle;
+
+        /// <summary>
+        /// Return a copied Style instance of Toast
+        /// </summary>
+        /// <remarks>
+        /// It returns copied Style instance and changing it does not effect to the Toast.
+        /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public new RecyclerViewItemStyle Style
+        {
+            get
+            {
+                var result = new RecyclerViewItemStyle(ItemStyle);
+                result.CopyPropertiesFromView(this);
+                return result;
+            }
+        }
+
+        static RecyclerViewItem() {}
+
+        /// <summary>
+        /// Creates a new instance of RecyclerViewItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecyclerViewItem() : base()
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Creates a new instance of RecyclerViewItem with style.
+        /// </summary>
+        /// <param name="style">Create RecyclerViewItem by special style defined in UX.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecyclerViewItem(string style) : base(style)
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Creates a new instance of a RecyclerViewItem with style.
+        /// </summary>
+        /// <param name="itemStyle">Create RecyclerViewItem by style customized by user.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecyclerViewItem(RecyclerViewItemStyle itemStyle) : base(itemStyle)
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// An event for the RecyclerViewItem clicked signal which can be used to subscribe or unsubscribe the event handler provided by the user.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<ClickedEventArgs> Clicked;
+
+        /// <summary>
+        /// Flag to decide RecyclerViewItem can be selected or not.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public bool IsSelectable
+        {
+            get => (bool)GetValue(IsSelectableProperty);
+            set => SetValue(IsSelectableProperty, value);
+        }
+
+        /// <summary>
+        /// Flag to decide selected state in RecyclerViewItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public bool IsSelected
+        {
+            get => (bool)GetValue(IsSelectedProperty);
+            set => SetValue(IsSelectedProperty, value);
+        }
+
+        /// <summary>
+        /// Flag to decide enable or disable in RecyclerViewItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public bool IsEnabled
+        {
+            get =>  (bool)GetValue(IsEnabledProperty);
+            set =>  SetValue(IsEnabledProperty, value);
+        }
+
+        /// <summary>
+        /// Data index which is binded to item.
+        /// Can access to data using this index.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public int Index { get; internal set; } = 0;
+
+        /// <summary>
+        /// DataTemplate of this view object
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DataTemplate Template { get; internal set; }
+
+        /// <summary>
+        /// State of Realization
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public bool IsRealized { get; internal set; }
+        internal bool IsHeader { get; set; }
+        internal bool IsFooter { get; set; }
+        internal bool IsPressed  { get; set; } = false;
+
+        /// <summary>
+        /// Called after a key event is received by the view that has had its focus set.
+        /// </summary>
+        /// <param name="key">The key event.</param>
+        /// <returns>True if the key event should be consumed.</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override bool OnKey(Key key)
+        {
+            if (!IsEnabled || null == key)
+            {
+                return false;
+            }
+
+            if (key.State == Key.StateType.Down)
+            {
+                if (key.KeyPressedName == "Return")
+                {
+                    IsPressed = true;
+                    UpdateState();
+                }
+            }
+            else if (key.State == Key.StateType.Up)
+            {
+                if (key.KeyPressedName == "Return")
+                {
+                    bool clicked = IsPressed && IsEnabled;
+
+                    IsPressed = false;
+
+                    if (IsSelectable)
+                    {
+                        // Extension : Extension?.SetTouchInfo(touch);
+                        if (ParentItemsView as CollectionView)
+                        {
+                            CollectionView colView = ParentItemsView as CollectionView;
+                            switch (colView.SelectionMode)
+                            {
+                                case ItemSelectionMode.SingleSelection :
+                                    colView.SelectedItem = IsSelected ? null : BindingContext;
+                                    break;
+                                case ItemSelectionMode.MultipleSelections :
+                                    var selectedItems = colView.SelectedItems;
+                                    if (selectedItems.Contains(BindingContext)) selectedItems.Remove(BindingContext);
+                                    else selectedItems.Add(BindingContext);
+                                    break;
+                                case ItemSelectionMode.None :
+                                    break;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        UpdateState();
+                    }
+
+                    if (clicked)
+                    {
+                        ClickedEventArgs eventArgs = new ClickedEventArgs();
+                        OnClickedInternal(eventArgs);
+                    }
+                }
+            }
+            return base.OnKey(key);
+        }
+
+        /// <summary>
+        /// Called when the control gain key input focus. Should be overridden by derived classes if they need to customize what happens when the focus is gained.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void OnFocusGained()
+        {
+            base.OnFocusGained();
+            UpdateState();
+        }
+
+        /// <summary>
+        /// Called when the control loses key input focus. 
+        /// Should be overridden by derived classes if they need to customize
+        /// what happens when the focus is lost.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void OnFocusLost()
+        {
+            base.OnFocusLost();
+            UpdateState();
+        }
+
+        /// <summary>
+        /// Apply style to RecyclerViewItem.
+        /// </summary>
+        /// <param name="viewStyle">The style to apply.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void ApplyStyle(ViewStyle viewStyle)
+        {
+            styleApplied = false;
+
+            base.ApplyStyle(viewStyle);
+            if (viewStyle != null)
+            {
+                //Extension = RecyclerViewItemStyle.CreateExtension();
+            }
+
+            styleApplied = true;
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSelectionMode.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSelectionMode.cs
new file mode 100644 (file)
index 0000000..3156bf2
--- /dev/null
@@ -0,0 +1,43 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// Selection mode of CollecitonView.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public enum ItemSelectionMode
+    {
+        /// <summary>
+        /// None of item can be selected.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        None,
+        /// <summary>
+        /// Single selection. select item exculsively so previous selected item will be unselected.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        SingleSelection,
+        /// <summary>
+        /// Multiple selections. select multiple items and previous selected item still remains selected.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        MultipleSelections
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSizingStrategy.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSizingStrategy.cs
new file mode 100644 (file)
index 0000000..006da23
--- /dev/null
@@ -0,0 +1,42 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// Size calculation strategy for CollectionView.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public enum ItemSizingStrategy
+    {
+
+        /// <summary>
+        /// Measure all items in advanced.
+        /// Estimate first item size for all, and when scroll reached position,
+        /// measure strictly. Note : This will make scroll bar trembling.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        MeasureAll,
+        /// <summary>
+        /// Measure first item and deligate size for all items.
+        /// if template is selector, the size of first item from each template will be deligated.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        MeasureFirst,
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/EmptySource.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/EmptySource.cs
new file mode 100644 (file)
index 0000000..d348b00
--- /dev/null
@@ -0,0 +1,63 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+
+namespace Tizen.NUI.Components
+{
+    internal sealed class EmptySource : IItemSource
+    {
+        public int Count => 0;
+
+        public bool HasHeader { get; set; }
+        public bool HasFooter { get; set; }
+
+        public void Dispose()
+        {
+
+        }
+
+        public bool IsHeader(int index)
+        {
+            return HasHeader && index == 0;
+        }
+
+        public bool IsFooter(int index)
+        {
+            if (!HasFooter)
+            {
+                return false;
+            }
+
+            if (HasHeader)
+            {
+                return index == 1;
+            }
+
+            return index == 0;
+        }
+
+        public int GetPosition(object item)
+        {
+            return -1;
+        }
+
+        public object GetItem(int position)
+        {
+            throw new IndexOutOfRangeException("IItemSource is empty");
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/IItemSource.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/IItemSource.cs
new file mode 100644 (file)
index 0000000..4a4d86b
--- /dev/null
@@ -0,0 +1,105 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// Base interface for encapsulated data source in RecyclerView.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public interface IItemSource : IDisposable
+    {
+        /// <summary>
+        /// Count of data source.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        int Count { get; }
+
+        /// <summary>
+        /// Position integer value of data object.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        int GetPosition(object item);
+
+        /// <summary>
+        /// Item object in position.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        object GetItem(int position);
+
+        /// <summary>
+        /// Flag of header existence.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        bool HasHeader { get; set; }
+
+        /// <summary>
+        /// Flag of Footer existence.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        bool HasFooter { get; set; }
+
+        /// <summary>
+        /// Boolean checker for position is header or not.
+        /// 0 index will be header if header exist.
+        /// warning: if header exist, all item index will be increased.
+        /// </summary>
+        /// <param name="position">The position for checking header.</param>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        bool IsHeader(int position);
+
+        /// <summary>
+        /// Boolean checker for position is footer or not.
+        /// last index will be footer if footer exist.
+        /// warning: footer will be place original data count or data count + 1.
+        /// </summary>
+        /// <param name="position">The position for checking footer.</param>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        bool IsFooter(int position);
+    }
+
+    /// <summary>
+    /// Base interface for encapsulated data source with group structure in CollectionView.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public interface IGroupableItemSource : IItemSource
+    {
+        /// <summary>
+        /// Boolean checker for position is group header or not
+        /// </summary>
+        /// <param name="position">The position for checking group header.</param>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        bool IsGroupHeader(int position);
+
+        /// <summary>
+        /// Boolean checker for position is group footer or not
+        /// </summary>
+        /// <param name="position">The position for checking group footer.</param>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        bool IsGroupFooter(int position);
+
+        /// <summary>
+        /// Boolean checker for position is group footer or not
+        /// </summary>
+        /// <param name="position">The position for checking group footer.</param>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        object GetGroupParent(int position);
+
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ItemsSourceFactory.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ItemsSourceFactory.cs
new file mode 100644 (file)
index 0000000..f3df899
--- /dev/null
@@ -0,0 +1,62 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+//using AndroidX.RecyclerView.Widget; ??? need to find whot it needs? adapter?
+
+namespace Tizen.NUI.Components
+{
+    internal static class ItemsSourceFactory
+    {
+        public static IItemSource Create(IEnumerable itemsSource, ICollectionChangedNotifier notifier)
+        {
+            if (itemsSource == null)
+            {
+                return new EmptySource();
+            }
+
+            switch (itemsSource)
+            {
+                case IList list when itemsSource is INotifyCollectionChanged:
+                    return new ObservableItemSource(new MarshalingObservableCollection(list), notifier);
+                case IEnumerable _ when itemsSource is INotifyCollectionChanged:
+                    return new ObservableItemSource(itemsSource, notifier);
+                case IEnumerable<object> generic:
+                    return new ListSource(generic);
+            }
+
+            return new ListSource(itemsSource);
+        }
+
+        public static IItemSource Create(RecyclerView recyclerView)
+        {
+            return Create(recyclerView.ItemsSource, recyclerView);
+        }
+
+        public static IGroupableItemSource Create(CollectionView colView)
+        {
+            var source = colView.ItemsSource;
+
+            if (colView.IsGrouped && source != null)
+                return new ObservableGroupedSource(colView, colView);
+
+            else
+                return new UngroupedItemSource(Create(colView.ItemsSource, colView));
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ListSource.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ListSource.cs
new file mode 100644 (file)
index 0000000..41835ad
--- /dev/null
@@ -0,0 +1,154 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.Components
+{
+    sealed class ListSource : IItemSource, IList
+    {
+        IList _itemsSource;
+
+        public ListSource()
+        {
+        }
+
+        public ListSource(IEnumerable<object> enumerable)
+        {
+            _itemsSource = new List<object>(enumerable);
+        }
+
+        public ListSource(IEnumerable enumerable)
+        {
+            _itemsSource = new List<object>();
+
+            if (enumerable == null)
+                return;
+
+            foreach (object item in enumerable)
+            {
+                _itemsSource.Add(item);
+            }
+        }
+
+        public int Count => _itemsSource.Count + (HasHeader ? 1 : 0) + (HasFooter ? 1 : 0);
+
+        public bool HasHeader { get; set; }
+        public bool HasFooter { get; set; }
+
+        public bool IsReadOnly => _itemsSource.IsReadOnly;
+
+        public bool IsFixedSize => _itemsSource.IsFixedSize;
+
+        public object SyncRoot => _itemsSource.SyncRoot;
+
+        public bool IsSynchronized => _itemsSource.IsSynchronized;
+
+        object IList.this[int index] { get => _itemsSource[index]; set => _itemsSource[index] = value; }
+
+        public void Dispose()
+        {
+
+        }
+
+        public bool IsFooter(int index)
+        {
+            return HasFooter && index == Count - 1;
+        }
+
+        public bool IsHeader(int index)
+        {
+            return HasHeader && index == 0;
+        }
+
+        public int GetPosition(object item)
+        {
+            for (int n = 0; n < _itemsSource.Count; n++)
+            {
+                var elementByIndex = _itemsSource[n];
+                var isEqual = elementByIndex == item || (elementByIndex != null && item != null && elementByIndex.Equals(item));
+
+                if (isEqual)
+                {
+                    return AdjustPosition(n);
+                }
+            }
+
+            return -1;
+        }
+
+        public object GetItem(int position)
+        {
+            return _itemsSource[AdjustIndexRequest(position)];
+        }
+
+        int AdjustIndexRequest(int index)
+        {
+            return index - (HasHeader ? 1 : 0);
+        }
+
+        int AdjustPosition(int index)
+        {
+            return index + (HasHeader ? 1 : 0);
+        }
+        public int Add(object value)
+        {
+            return _itemsSource.Add(value);
+        }
+
+        public bool Contains(object value)
+        {
+            return _itemsSource.Contains(value);
+        }
+
+        public void Clear()
+        {
+            _itemsSource.Clear();
+        }
+
+        public int IndexOf(object value)
+        {
+            return _itemsSource.IndexOf(value);
+        }
+
+        public void Insert(int index, object value)
+        {
+            _itemsSource.Insert(index, value);
+        }
+
+        public void Remove(object value)
+        {
+            _itemsSource.Remove(value);
+        }
+
+        public void RemoveAt(int index)
+        {
+            _itemsSource.RemoveAt(index);
+        }
+
+        public void CopyTo(Array array, int index)
+        {
+            _itemsSource.CopyTo(array, index);
+        }
+
+        public IEnumerator GetEnumerator()
+        {
+            return _itemsSource.GetEnumerator();
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/MarshalingObservableCollection.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/MarshalingObservableCollection.cs
new file mode 100644 (file)
index 0000000..92fac69
--- /dev/null
@@ -0,0 +1,176 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace Tizen.NUI.Components
+{
+    // Wraps a List which implements INotifyCollectionChanged (usually an ObservableCollection)
+    // and marshals all of the list modifications to the main thread. Modifications to the underlying 
+    // collection which are made off of the main thread remain invisible to consumers on the main thread
+    // until they have been processed by the main thread.
+
+    internal class MarshalingObservableCollection : List<object>, INotifyCollectionChanged
+    {
+        readonly IList internalCollection;
+
+        public MarshalingObservableCollection(IList list)
+        {
+            if (!(list is INotifyCollectionChanged incc))
+            {
+                throw new ArgumentException($"{nameof(list)} must implement {nameof(INotifyCollectionChanged)}");
+            }
+
+            internalCollection = list;
+            incc.CollectionChanged += InternalCollectionChanged;
+
+            foreach (var item in internalCollection)
+            {
+                Add(item);
+            }
+        }
+
+        class ResetNotifyCollectionChangedEventArgs : NotifyCollectionChangedEventArgs
+        {
+            public IList Items { get; }
+            public ResetNotifyCollectionChangedEventArgs(IList items)
+                : base(NotifyCollectionChangedAction.Reset) => Items = items;
+        }
+
+        public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+        void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
+        {
+            CollectionChanged?.Invoke(this, args);
+        }
+
+        void InternalCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+        {
+            if (args.Action == NotifyCollectionChangedAction.Reset)
+            {
+                var items = new List<object>();
+                for (int n = 0; n < internalCollection.Count; n++)
+                {
+                    items.Add(internalCollection[n]);
+                }
+
+                args = new ResetNotifyCollectionChangedEventArgs(items);
+            }
+/*
+            if (Device.IsInvokeRequired)
+            {
+                Device.BeginInvokeOnMainThread(() => HandleCollectionChange(args));
+            }
+            else
+            {
+                HandleCollectionChange(args);
+            }
+*/
+
+            HandleCollectionChange(args);
+        }
+
+        void HandleCollectionChange(NotifyCollectionChangedEventArgs args)
+        {
+            switch (args.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    Add(args);
+                    break;
+                case NotifyCollectionChangedAction.Move:
+                    Move(args);
+                    break;
+                case NotifyCollectionChangedAction.Remove:
+                    Remove(args);
+                    break;
+                case NotifyCollectionChangedAction.Replace:
+                    Replace(args);
+                    break;
+                case NotifyCollectionChangedAction.Reset:
+                    Reset(args);
+                    break;
+            }
+        }
+
+        void Move(NotifyCollectionChangedEventArgs args)
+        {
+            var count = args.OldItems.Count;
+
+            for (int n = 0; n < count; n++)
+            {
+                var toMove = this[args.OldStartingIndex];
+                RemoveAt(args.OldStartingIndex);
+                Insert(args.NewStartingIndex, toMove);
+            }
+
+            OnCollectionChanged(args);
+        }
+
+        void Remove(NotifyCollectionChangedEventArgs args)
+        {
+            var startIndex = args.OldStartingIndex + args.OldItems.Count - 1;
+            for (int n = startIndex; n >= args.OldStartingIndex; n--)
+            {
+                RemoveAt(n);
+            }
+
+            OnCollectionChanged(args);
+        }
+
+        void Replace(NotifyCollectionChangedEventArgs args)
+        {
+            var startIndex = args.NewStartingIndex;
+            foreach (var item in args.NewItems)
+            {
+                this[startIndex] = item;
+                startIndex += 1;
+            }
+
+            OnCollectionChanged(args);
+        }
+
+        void Add(NotifyCollectionChangedEventArgs args)
+        {
+            var startIndex = args.NewStartingIndex;
+            foreach (var item in args.NewItems)
+            {
+                Insert(startIndex, item);
+                startIndex += 1;
+            }
+
+            OnCollectionChanged(args);
+        }
+
+        void Reset(NotifyCollectionChangedEventArgs args)
+        {
+            if (!(args is ResetNotifyCollectionChangedEventArgs resetArgs))
+            {
+                throw new InvalidOperationException($"Cannot guarantee collection accuracy for Resets which do not use {nameof(ResetNotifyCollectionChangedEventArgs)}");
+            }
+
+            Clear();
+            foreach (var item in resetArgs.Items)
+            {
+                Add(item);
+            }
+
+            OnCollectionChanged(args);
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableGroupedSource.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableGroupedSource.cs
new file mode 100644 (file)
index 0000000..0602f87
--- /dev/null
@@ -0,0 +1,466 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace Tizen.NUI.Components
+{
+    internal class ObservableGroupedSource : IGroupableItemSource, ICollectionChangedNotifier
+    {
+        readonly ICollectionChangedNotifier notifier;
+        readonly IList groupSource;
+        readonly List<IItemSource> groups = new List<IItemSource>();
+        readonly bool hasGroupHeaders;
+        readonly bool hasGroupFooters;
+        bool disposed;
+
+        public int Count
+        {
+            get
+            {
+                var groupContents = 0;
+
+                for (int n = 0; n < groups.Count; n++)
+                {
+                    groupContents += groups[n].Count;
+                }
+
+                return (HasHeader ? 1 : 0)
+                     + (HasFooter ? 1 : 0)
+                     + groupContents;
+            }
+        }
+
+        public bool HasHeader { get; set; }
+        public bool HasFooter { get; set; }
+
+        public ObservableGroupedSource(CollectionView colView, ICollectionChangedNotifier changedNotifier)
+        {
+            var source = colView.ItemsSource;
+
+            notifier = changedNotifier;
+            groupSource = source as IList ?? new ListSource(source);
+
+            hasGroupFooters = colView.GroupFooterTemplate != null;
+            hasGroupHeaders = colView.GroupHeaderTemplate != null;
+            HasHeader = colView.Header != null;
+            HasFooter = colView.Footer != null;
+
+            if (groupSource is INotifyCollectionChanged incc)
+            {
+                incc.CollectionChanged += CollectionChanged;
+            }
+
+            UpdateGroupTracking();
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+        }
+
+        public bool IsFooter(int position)
+        {
+            if (!HasFooter)
+            {
+                return false;
+            }
+
+            return position == Count - 1;
+        }
+
+        public bool IsHeader(int position)
+        {
+            return HasHeader && position == 0;
+        }
+
+        public bool IsGroupHeader(int position)
+        {
+            if (IsFooter(position) || IsHeader(position))
+            {
+                return false;
+            }
+
+            var (group, inGroup) = GetGroupAndIndex(position);
+
+            return groups[group].IsHeader(inGroup);
+        }
+
+        public bool IsGroupFooter(int position)
+        {
+            if (IsFooter(position) || IsHeader(position))
+            {
+                return false;
+            }
+
+            var (group, inGroup) = GetGroupAndIndex(position);
+
+            return groups[group].IsFooter(inGroup);
+        }
+
+        public int GetPosition(object item)
+        {
+            int previousGroupsOffset = 0;
+
+            for (int groupIndex = 0; groupIndex < groupSource.Count; groupIndex++)
+            {
+                if (groupSource[groupIndex].Equals(item))
+                {
+                    return AdjustPositionForHeader(groupIndex);
+                }
+
+                var group = groups[groupIndex];
+                var inGroup = group.GetPosition(item);
+
+                if (inGroup > -1)
+                {
+                    return AdjustPositionForHeader(previousGroupsOffset + inGroup);
+                }
+
+                previousGroupsOffset += group.Count;
+            }
+
+            return -1;
+        }
+
+        public object GetItem(int position)
+        {
+            var (group, inGroup) = GetGroupAndIndex(position);
+
+            if (IsGroupFooter(position) || IsGroupHeader(position))
+            {
+                // This is looping to find the group/index twice, need to make it less inefficient
+                return groupSource[group];
+            }
+
+            return groups[group].GetItem(inGroup);
+        }
+
+        public object GetGroupParent(int position)
+        {
+            var (group, inGroup) = GetGroupAndIndex(position);
+            return groupSource[group];
+        }
+
+        // The ICollectionChangedNotifier methods are called by child observable items sources (i.e., the groups)
+        // This class can then translate their local changes into global positions for upstream notification 
+        // (e.g., to the actual RecyclerView.Adapter, so that it can notify the RecyclerView and handle animating
+        // the changes)
+        public void NotifyDataSetChanged()
+        {
+            Reload();
+        }
+
+        public void NotifyItemChanged(IItemSource group, int localIndex)
+        {
+            localIndex = GetAbsolutePosition(group, localIndex);
+            notifier.NotifyItemChanged(this, localIndex);
+        }
+
+        public void NotifyItemInserted(IItemSource group, int localIndex)
+        {
+            localIndex = GetAbsolutePosition(group, localIndex);
+            notifier.NotifyItemInserted(this, localIndex);
+        }
+
+        public void NotifyItemMoved(IItemSource group, int localFromIndex, int localToIndex)
+        {
+            localFromIndex = GetAbsolutePosition(group, localFromIndex);
+            localToIndex = GetAbsolutePosition(group, localToIndex);
+            notifier.NotifyItemMoved(this, localFromIndex, localToIndex);
+        }
+
+        public void NotifyItemRangeChanged(IItemSource group, int localStartIndex, int localEndIndex)
+        {
+            localStartIndex = GetAbsolutePosition(group, localStartIndex);
+            localEndIndex = GetAbsolutePosition(group, localEndIndex);
+            notifier.NotifyItemRangeChanged(this, localStartIndex, localEndIndex);
+        }
+
+        public void NotifyItemRangeInserted(IItemSource group, int localIndex, int count)
+        {
+            localIndex = GetAbsolutePosition(group, localIndex);
+            notifier.NotifyItemRangeInserted(this, localIndex, count);
+        }
+
+        public void NotifyItemRangeRemoved(IItemSource group, int localIndex, int count)
+        {
+            localIndex = GetAbsolutePosition(group, localIndex);
+            notifier.NotifyItemRangeRemoved(this, localIndex, count);
+        }
+
+        public void NotifyItemRemoved(IItemSource group, int localIndex)
+        {
+            localIndex = GetAbsolutePosition(group, localIndex);
+            notifier.NotifyItemRemoved(this, localIndex);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+           disposed = true;
+
+            if (disposing)
+            {
+                ClearGroupTracking();
+
+                if (groupSource is INotifyCollectionChanged notifyCollectionChanged)
+                {
+                    notifyCollectionChanged.CollectionChanged -= CollectionChanged;
+                }
+                if (groupSource is IDisposable dispoableSource) dispoableSource.Dispose();
+            }
+        }
+
+        void UpdateGroupTracking()
+        {
+            ClearGroupTracking();
+
+            for (int n = 0; n < groupSource.Count; n++)
+            {
+                var source = ItemsSourceFactory.Create(groupSource[n] as IEnumerable, this);
+                source.HasFooter = hasGroupFooters;
+                source.HasHeader = hasGroupHeaders;
+                groups.Add(source);
+            }
+        }
+
+        void ClearGroupTracking()
+        {
+            for (int n = groups.Count - 1; n >= 0; n--)
+            {
+                groups[n].Dispose();
+                groups.RemoveAt(n);
+            }
+        }
+
+        void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+        {/*
+            if (Device.IsInvokeRequired)
+            {
+                Device.BeginInvokeOnMainThread(() => CollectionChanged(args));
+            }
+            else
+            {
+                */
+                CollectionChanged(args);
+            //}
+        }
+
+        void CollectionChanged(NotifyCollectionChangedEventArgs args)
+        {
+            switch (args.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    Add(args);
+                    break;
+                case NotifyCollectionChangedAction.Remove:
+                    Remove(args);
+                    break;
+                case NotifyCollectionChangedAction.Replace:
+                    Replace(args);
+                    break;
+                case NotifyCollectionChangedAction.Move:
+                    Move(args);
+                    break;
+                case NotifyCollectionChangedAction.Reset:
+                    Reload();
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(args));
+            }
+        }
+
+        void Reload()
+        {
+            UpdateGroupTracking();
+            notifier.NotifyDataSetChanged();
+        }
+
+        void Add(NotifyCollectionChangedEventArgs args)
+        {
+            var groupIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : groupSource.IndexOf(args.NewItems[0]);
+            var groupCount = args.NewItems.Count;
+
+            UpdateGroupTracking();
+
+            // Determine the absolute starting position and the number of items in the groups being added
+            var absolutePosition = GetAbsolutePosition(groups[groupIndex], 0);
+            var itemCount = CountItemsInGroups(groupIndex, groupCount);
+
+            if (itemCount == 1)
+            {
+                notifier.NotifyItemInserted(this, absolutePosition);
+                return;
+            }
+
+            notifier.NotifyItemRangeInserted(this, absolutePosition, itemCount);
+        }
+
+        void Remove(NotifyCollectionChangedEventArgs args)
+        {
+            var groupIndex = args.OldStartingIndex;
+
+            if (groupIndex < 0)
+            {
+                // INCC implementation isn't giving us enough information to know where the removed groups was in the
+                // collection. So the best we can do is a full reload.
+                Reload();
+                return;
+            }
+
+            // If we have a start index, we can be more clever about removing the group(s) (and get the nifty animations)
+            var groupCount = args.OldItems.Count;
+
+            var absolutePosition = GetAbsolutePosition(groups[groupIndex], 0);
+
+            // Figure out how many items are in the groups we're removing
+            var itemCount = CountItemsInGroups(groupIndex, groupCount);
+
+            if (itemCount == 1)
+            {
+                notifier.NotifyItemRemoved(this, absolutePosition);
+
+                UpdateGroupTracking();
+
+                return;
+            }
+
+            notifier.NotifyItemRangeRemoved(this, absolutePosition, itemCount);
+
+            UpdateGroupTracking();
+        }
+
+        void Replace(NotifyCollectionChangedEventArgs args)
+        {
+            var groupCount = args.NewItems.Count;
+
+            if (groupCount != args.OldItems.Count)
+            {
+                // The original and replacement sets are of unequal size; this means that most everything currently in 
+                // view will have to be updated. So just reload the whole thing.
+                Reload();
+                return;
+            }
+
+            var newStartIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : groupSource.IndexOf(args.NewItems[0]);
+            var oldStartIndex = args.OldStartingIndex > -1 ? args.OldStartingIndex : groupSource.IndexOf(args.OldItems[0]);
+
+            var newItemCount = CountItemsInGroups(newStartIndex, groupCount);
+            var oldItemCount = CountItemsInGroups(oldStartIndex, groupCount);
+
+            if (newItemCount != oldItemCount)
+            {
+                // The original and replacement sets are of unequal size; this means that most everything currently in 
+                // view will have to be updated. So just reload the whole thing.
+                Reload();
+                return;
+            }
+
+            // We are replacing one set of items with a set of equal size; we can do a simple item or range notification 
+            var firstGroupIndex = Math.Min(newStartIndex, oldStartIndex);
+            var absolutePosition = GetAbsolutePosition(groups[firstGroupIndex], 0);
+
+            if (newItemCount == 1)
+            {
+                notifier.NotifyItemChanged(this, absolutePosition);
+                UpdateGroupTracking();
+            }
+            else
+            {
+                notifier.NotifyItemRangeChanged(this, absolutePosition, newItemCount * 2);
+                UpdateGroupTracking();
+            }
+        }
+
+        void Move(NotifyCollectionChangedEventArgs args)
+        {
+            var start = Math.Min(args.OldStartingIndex, args.NewStartingIndex);
+            var end = Math.Max(args.OldStartingIndex, args.NewStartingIndex) + args.NewItems.Count;
+
+            var itemCount = CountItemsInGroups(start, end - start);
+            var absolutePosition = GetAbsolutePosition(groups[start], 0);
+
+            notifier.NotifyItemRangeChanged(this, absolutePosition, itemCount);
+
+            UpdateGroupTracking();
+        }
+
+        int GetAbsolutePosition(IItemSource group, int indexInGroup)
+        {
+            var groupIndex = groups.IndexOf(group);
+
+            var runningIndex = 0;
+
+            for (int n = 0; n < groupIndex; n++)
+            {
+                runningIndex += groups[n].Count;
+            }
+
+            return AdjustPositionForHeader(runningIndex + indexInGroup);
+        }
+
+        (int, int) GetGroupAndIndex(int absolutePosition)
+        {
+            absolutePosition = AdjustIndexForHeader(absolutePosition);
+
+            var group = 0;
+            var localIndex = 0;
+
+            while (absolutePosition > 0)
+            {
+                localIndex += 1;
+
+                if (localIndex == groups[group].Count)
+                {
+                    group += 1;
+                    localIndex = 0;
+                }
+
+                absolutePosition -= 1;
+            }
+
+            return (group, localIndex);
+        }
+
+        int AdjustIndexForHeader(int index)
+        {
+            return index - (HasHeader ? 1 : 0);
+        }
+
+        int AdjustPositionForHeader(int position)
+        {
+            return position + (HasHeader ? 1 : 0);
+        }
+
+        int CountItemsInGroups(int groupStartIndex, int groupCount)
+        {
+            var itemCount = 0;
+            for (int n = 0; n < groupCount; n++)
+            {
+                itemCount += groups[groupStartIndex + n].Count;
+            }
+            return itemCount;
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableItemSource.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableItemSource.cs
new file mode 100644 (file)
index 0000000..f5581a4
--- /dev/null
@@ -0,0 +1,271 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+
+namespace Tizen.NUI.Components
+{
+    internal class ObservableItemSource : IItemSource
+    {
+        readonly IEnumerable itemsSource;
+        readonly ICollectionChangedNotifier notifier;
+        bool disposed;
+
+        public ObservableItemSource(IEnumerable itemSource, ICollectionChangedNotifier changedNotifier)
+        {
+            itemsSource = itemSource as IList ?? itemSource as IEnumerable;
+            notifier = changedNotifier;
+
+            ((INotifyCollectionChanged)itemSource).CollectionChanged += CollectionChanged;
+        }
+
+
+        internal event NotifyCollectionChangedEventHandler CollectionItemsSourceChanged;
+
+        public int Count => ItemsCount() + (HasHeader ? 1 : 0) + (HasFooter ? 1 : 0);
+
+        public bool HasHeader { get; set; }
+        public bool HasFooter { get; set; }
+
+        public void Dispose()
+        {
+            Dispose(true);
+        }
+
+        public bool IsFooter(int index)
+        {
+            return HasFooter && index == Count - 1;
+        }
+
+        public bool IsHeader(int index)
+        {
+            return HasHeader && index == 0;
+        }
+
+        public int GetPosition(object item)
+        {
+            for (int n = 0; n < ItemsCount(); n++)
+            {
+                var elementByIndex = ElementAt(n);
+                var isEqual = elementByIndex == item || (elementByIndex != null && item != null && elementByIndex.Equals(item));
+
+                if (isEqual)
+                {
+                    return AdjustPositionForHeader(n);
+                }
+            }
+
+            return -1;
+        }
+
+        public object GetItem(int position)
+        {
+            return ElementAt(AdjustIndexForHeader(position));
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+            disposed = true;
+
+            if (disposing)
+            {
+                ((INotifyCollectionChanged)itemsSource).CollectionChanged -= CollectionChanged;
+            }
+        }
+
+        int AdjustIndexForHeader(int index)
+        {
+            return index - (HasHeader ? 1 : 0);
+        }
+
+        int AdjustPositionForHeader(int position)
+        {
+            return position + (HasHeader ? 1 : 0);
+        }
+
+        void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+        {/*
+            if (Device.IsInvokeRequired)
+            {
+                Device.BeginInvokeOnMainThread(() => CollectionChanged(args));
+            }
+            else
+            {*/
+                CollectionChanged(args);
+            //}
+
+        }
+
+        void CollectionChanged(NotifyCollectionChangedEventArgs args)
+        {
+            switch (args.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    Add(args);
+                    break;
+                case NotifyCollectionChangedAction.Remove:
+                    Remove(args);
+                    break;
+                case NotifyCollectionChangedAction.Replace:
+                    Replace(args);
+                    break;
+                case NotifyCollectionChangedAction.Move:
+                    Move(args);
+                    break;
+                case NotifyCollectionChangedAction.Reset:
+                    notifier.NotifyDataSetChanged();
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(args));
+            }
+            CollectionItemsSourceChanged?.Invoke(this, args);
+        }
+
+        void Move(NotifyCollectionChangedEventArgs args)
+        {
+            var count = args.NewItems.Count;
+
+            if (count == 1)
+            {
+                // For a single item, we can use NotifyItemMoved and get the animation
+                notifier.NotifyItemMoved(this, AdjustPositionForHeader(args.OldStartingIndex), AdjustPositionForHeader(args.NewStartingIndex));
+                return;
+            }
+
+            var start = AdjustPositionForHeader(Math.Min(args.OldStartingIndex, args.NewStartingIndex));
+            var end = AdjustPositionForHeader(Math.Max(args.OldStartingIndex, args.NewStartingIndex) + count);
+            notifier.NotifyItemRangeChanged(this, start, end);
+        }
+
+        void Add(NotifyCollectionChangedEventArgs args)
+        {
+            var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : IndexOf(args.NewItems[0]);
+            startIndex = AdjustPositionForHeader(startIndex);
+            var count = args.NewItems.Count;
+
+            if (count == 1)
+            {
+                notifier.NotifyItemInserted(this, startIndex);
+                return;
+            }
+
+            notifier.NotifyItemRangeInserted(this, startIndex, count);
+        }
+
+        void Remove(NotifyCollectionChangedEventArgs args)
+        {
+            var startIndex = args.OldStartingIndex;
+
+            if (startIndex < 0)
+            {
+                // INCC implementation isn't giving us enough information to know where the removed items were in the
+                // collection. So the best we can do is a NotifyDataSetChanged()
+                notifier.NotifyDataSetChanged();
+                return;
+            }
+
+            startIndex = AdjustPositionForHeader(startIndex);
+
+            // If we have a start index, we can be more clever about removing the item(s) (and get the nifty animations)
+            var count = args.OldItems.Count;
+
+            if (count == 1)
+            {
+                notifier.NotifyItemRemoved(this, startIndex);
+                return;
+            }
+
+            notifier.NotifyItemRangeRemoved(this, startIndex, count);
+        }
+
+        void Replace(NotifyCollectionChangedEventArgs args)
+        {
+            var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : IndexOf(args.NewItems[0]);
+            startIndex = AdjustPositionForHeader(startIndex);
+            var newCount = args.NewItems.Count;
+
+            if (newCount == args.OldItems.Count)
+            {
+                // We are replacing one set of items with a set of equal size; we can do a simple item or range 
+                // notification to the adapter
+                if (newCount == 1)
+                {
+                    notifier.NotifyItemChanged(this, startIndex);
+                }
+                else
+                {
+                    notifier.NotifyItemRangeChanged(this, startIndex, newCount);
+                }
+
+                return;
+            }
+
+            // The original and replacement sets are of unequal size; this means that everything currently in view will 
+            // have to be updated. So we just have to use NotifyDataSetChanged and let the RecyclerView update everything
+            notifier.NotifyDataSetChanged();
+        }
+
+        internal int ItemsCount()
+        {
+            if (itemsSource is IList list)
+                return list.Count;
+
+            int count = 0;
+            foreach (var item in itemsSource)
+                count++;
+            return count;
+        }
+
+        internal object ElementAt(int index)
+        {
+            if (itemsSource is IList list)
+                return list[index];
+
+            int count = 0;
+            foreach (var item in itemsSource)
+            {
+                if (count == index)
+                    return item;
+                count++;
+            }
+
+            return -1;
+        }
+
+        internal int IndexOf(object item)
+        {
+            if (itemsSource is IList list)
+                return list.IndexOf(item);
+
+            int count = 0;
+            foreach (var i in itemsSource)
+            {
+                if (i == item)
+                    return count;
+                count++;
+            }
+
+            return -1;
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/UngroupedItemSource.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/UngroupedItemSource.cs
new file mode 100644 (file)
index 0000000..32f0340
--- /dev/null
@@ -0,0 +1,73 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+namespace Tizen.NUI.Components
+{
+    internal class UngroupedItemSource : IGroupableItemSource
+    {
+        readonly IItemSource source;
+
+        public UngroupedItemSource(IItemSource itemSource)
+        {
+            source = itemSource;
+        }
+
+        public int Count => source.Count;
+
+        public bool HasHeader { get => source.HasHeader; set => source.HasHeader = value; }
+        public bool HasFooter { get => source.HasFooter; set => source.HasFooter = value; }
+
+        public void Dispose()
+        {
+            source.Dispose();
+        }
+
+        public object GetItem(int position)
+        {
+            return source.GetItem(position);
+        }
+
+        public int GetPosition(object item)
+        {
+            return source.GetPosition(item);
+        }
+
+        public bool IsFooter(int position)
+        {
+            return source.IsFooter(position);
+        }
+
+        public bool IsGroupFooter(int position)
+        {
+            return false;
+        }
+
+        public bool IsGroupHeader(int position)
+        {
+            return false;
+        }
+
+        public bool IsHeader(int position)
+        {
+            return source.IsHeader(position);
+        }
+
+        public object GetGroupParent(int position)
+        {
+            return null;
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs
new file mode 100644 (file)
index 0000000..7e98751
--- /dev/null
@@ -0,0 +1,724 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// This class implements a grid box layout.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class GridLayouter : ItemsLayouter
+    {
+        private CollectionView colView;
+        private Size2D sizeCandidate;
+        private int spanSize = 1;
+        private float align = 0.5f;
+        private bool hasHeader;
+        private float headerSize;
+        private bool hasFooter;
+        private float footerSize;
+        private bool isGrouped;
+        private readonly List<GroupInfo> groups = new List<GroupInfo>();
+        private float groupHeaderSize;
+        private float groupFooterSize;
+        private GroupInfo Visited;
+
+        /// <summary>
+        /// Clean up ItemsLayouter.
+        /// </summary>
+        /// <param name="view"> ItemsView of layouter. </param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void Initialize(RecyclerView view)
+        {
+            colView = view as CollectionView;
+            if (colView == null)
+            {
+                throw new ArgumentException("GridLayouter only can be applied CollectionView.", nameof(view));
+            }
+
+            // 1. Clean Up
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                colView.UnrealizeItem(item, false);
+            }
+            VisibleItems.Clear();
+            groups.Clear();
+
+            FirstVisible = 0;
+            LastVisible = 0;
+
+            IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
+
+            RecyclerViewItem header = colView?.Header;
+            RecyclerViewItem footer = colView?.Footer;
+            float width, height;
+            int count = colView.InternalItemSource.Count;
+            int pureCount = count - (header ? 1 : 0) - (footer ? 1 : 0);
+
+            // 2. Get the header / footer and size deligated item and measure the size.
+            if (header != null)
+            {
+                MeasureChild(colView, header);
+
+                width = header.Layout != null ? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
+                height = header.Layout != null ? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
+
+                headerSize = IsHorizontal ? width : height;
+                hasHeader = true;
+
+                colView.UnrealizeItem(header);
+            }
+
+            if (footer != null)
+            {
+                MeasureChild(colView, footer);
+
+                width = footer.Layout != null ? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
+                height = footer.Layout != null ? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
+
+                footerSize = IsHorizontal ? width : height;
+                footer.Index = count - 1;
+                hasFooter = true;
+
+                colView.UnrealizeItem(footer);
+            }
+
+            int firstIndex = header ? 1 : 0;
+
+            if (colView.IsGrouped)
+            {
+                isGrouped = true;
+
+                if (colView.GroupHeaderTemplate != null)
+                {
+                    while (!colView.InternalItemSource.IsGroupHeader(firstIndex)) firstIndex++;
+                    //must be always true
+                    if (colView.InternalItemSource.IsGroupHeader(firstIndex))
+                    {
+                        RecyclerViewItem groupHeader = colView.RealizeItem(firstIndex);
+                        firstIndex++;
+
+                        if (groupHeader == null) throw new Exception("[" + firstIndex + "] Group Header failed to realize!");
+
+                        // Need to Set proper hieght or width on scroll direciton.
+                        if (groupHeader.Layout == null)
+                        {
+                            width = groupHeader.WidthSpecification;
+                            height = groupHeader.HeightSpecification;
+                        }
+                        else
+                        {
+                            MeasureChild(colView, groupHeader);
+
+                            width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
+                            height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
+                        }
+                        //Console.WriteLine("[NUI] GroupHeader Size {0} :{0}", width, height);
+                        // pick the StepCandidate.
+                        groupHeaderSize = IsHorizontal ? width : height;
+                        colView.UnrealizeItem(groupHeader);
+                    }
+                }
+                else
+                {
+                    groupHeaderSize = 0F;
+                }
+
+                if (colView.GroupFooterTemplate != null)
+                {
+                    int firstFooter = firstIndex;
+                    while (!colView.InternalItemSource.IsGroupFooter(firstFooter)) firstFooter++;
+                    //must be always true
+                    if (colView.InternalItemSource.IsGroupFooter(firstFooter))
+                    {
+                        RecyclerViewItem groupFooter = colView.RealizeItem(firstFooter);
+
+                        if (groupFooter == null) throw new Exception("[" + firstFooter + "] Group Footer failed to realize!");
+                        // Need to Set proper hieght or width on scroll direciton.
+                        if (groupFooter.Layout == null)
+                        {
+                            width = groupFooter.WidthSpecification;
+                            height = groupFooter.HeightSpecification;
+                        }
+                        else
+                        {
+                            MeasureChild(colView, groupFooter);
+
+                            width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
+                            height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
+                        }
+                        // pick the StepCandidate.
+                        groupFooterSize = IsHorizontal ? width : height;
+
+                        colView.UnrealizeItem(groupFooter);
+                    }
+                }
+                else
+                {
+                    groupFooterSize = 0F;
+                }
+            }
+            else isGrouped = false;
+
+            bool failed = false;
+            //Final Check of FirstIndex
+            while (colView.InternalItemSource.IsHeader(firstIndex) ||
+                    colView.InternalItemSource.IsGroupHeader(firstIndex) ||
+                    colView.InternalItemSource.IsGroupFooter(firstIndex))
+            {
+                if (colView.InternalItemSource.IsFooter(firstIndex))
+                {
+                    StepCandidate = 0F;
+                    failed = true;
+                    break;
+                }
+                firstIndex++;
+            }
+
+            sizeCandidate = new Size2D(0, 0);
+            if (!failed)
+            {
+                // Get Size Deligate. FIXME if group exist index must be changed.
+                RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
+                if (sizeDeligate == null)
+                {
+                    throw new Exception("Cannot create content from DatTemplate.");
+                }
+                sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
+
+                // Need to Set proper hieght or width on scroll direciton.
+                if (sizeDeligate.Layout == null)
+                {
+                    width = sizeDeligate.WidthSpecification;
+                    height = sizeDeligate.HeightSpecification;
+                }
+                else
+                {
+                    MeasureChild(colView, sizeDeligate);
+
+                    width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
+                    height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
+                }
+                //Console.WriteLine("[NUI] item Size {0} :{1}", width, height);
+
+                // pick the StepCandidate.
+                StepCandidate = IsHorizontal ? width : height;
+                spanSize = IsHorizontal ? Convert.ToInt32(Math.Truncate((double)(colView.Size.Height / height))) :
+                                        Convert.ToInt32(Math.Truncate((double)(colView.Size.Width / width)));
+
+                sizeCandidate = new Size2D(Convert.ToInt32(width), Convert.ToInt32(height));
+
+                colView.UnrealizeItem(sizeDeligate);
+            }
+
+            if (StepCandidate < 1) StepCandidate = 1;
+            if (spanSize < 1) spanSize = 1;
+
+            if (isGrouped)
+            {
+                float Current = 0.0F;
+                IGroupableItemSource source = colView.InternalItemSource;
+                GroupInfo currentGroup = null;
+
+                for (int i = 0; i < count; i++)
+                {
+                    if (i == 0 && hasHeader)
+                    {
+                        Current += headerSize;
+                    }
+                    else if (i == count - 1 && hasFooter)
+                    {
+                        Current += footerSize;
+                    }
+                    else
+                    {
+                        //GroupHeader must always exist in group usage.
+                        if (source.IsGroupHeader(i))
+                        {
+                            currentGroup = new GroupInfo()
+                            {
+                                GroupParent = source.GetGroupParent(i),
+                                StartIndex = i,
+                                Count = 1,
+                                GroupSize = groupHeaderSize,
+                                GroupPosition = Current
+                            };
+                            groups.Add(currentGroup);
+                            Current += groupHeaderSize;
+                        }
+                        //optional
+                        else if (source.IsGroupFooter(i))
+                        {
+                            //currentGroup.hasFooter = true;
+                            currentGroup.Count++;
+                            currentGroup.GroupSize += groupFooterSize;
+                            Current += groupFooterSize;
+                        }
+                        else
+                        {
+                            currentGroup.Count++;
+                            int index = i - currentGroup.StartIndex - 1; // groupHeader must always exist.
+                            if ((index % spanSize) == 0)
+                            {
+                                currentGroup.GroupSize += StepCandidate;
+                                Current += StepCandidate;
+                            }
+                        }
+                    }
+                }
+                ScrollContentSize = Current;
+            }
+            else
+            {
+                // 3. Measure the scroller content size.
+                ScrollContentSize = StepCandidate * Convert.ToInt32(Math.Ceiling((double)pureCount / (double)spanSize));
+                if (hasHeader) ScrollContentSize += headerSize;
+                if (hasFooter) ScrollContentSize += footerSize;
+            }
+
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+            base.Initialize(colView);
+            //Console.WriteLine("Init Done, StepCnadidate{0}, spanSize{1}, Scroll{2}", StepCandidate, spanSize, ScrollContentSize);
+        }
+
+        /// <summary>
+        /// This is called to find out where items are lain out according to current scroll position.
+        /// </summary>
+        /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+        /// <param name="force">boolean force flag to layouting forcely.</param>
+        public override void RequestLayout(float scrollPosition, bool force = false)
+        {
+            // Layouting is only possible after once it intialized.
+            if (!IsInitialized) return;
+            int LastIndex = colView.InternalItemSource.Count;
+
+            if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
+            PrevScrollPosition = Math.Abs(scrollPosition);
+
+            int prevFirstVisible = FirstVisible;
+            int prevLastVisible = LastVisible;
+            bool IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
+
+            (float X, float Y) visibleArea = (PrevScrollPosition,
+                PrevScrollPosition + (IsHorizontal ? colView.Size.Width : colView.Size.Height)
+            );
+
+            //Console.WriteLine("[NUI] itemsView [{0},{1}] [{2},{3}]", colView.Size.Width, colView.Size.Height, colView.ContentContainer.Size.Width, colView.ContentContainer.Size.Height);
+
+            // 1. Set First/Last Visible Item Index. 
+            (int start, int end) = FindVisibleItems(visibleArea);
+            FirstVisible = start;
+            LastVisible = end;
+
+            //Console.WriteLine("[NUI] {0} :visibleArea before [{1},{2}] after [{3},{4}]", scrollPosition, prevFirstVisible, prevLastVisible, FirstVisible, LastVisible);
+
+            // 2. Unrealize invisible items.
+            List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (item.Index < FirstVisible || item.Index > LastVisible)
+                {
+                    //Console.WriteLine("[NUI] Unrealize{0}!", item.Index);
+                    unrealizedItems.Add(item);
+                    colView.UnrealizeItem(item);
+                }
+            }
+            VisibleItems.RemoveAll(unrealizedItems.Contains);
+
+            //Console.WriteLine("Realize Begin [{0} to {1}]", FirstVisible, LastVisible);
+            // 3. Realize and placing visible items.
+            for (int i = FirstVisible; i <= LastVisible; i++)
+            {
+                //Console.WriteLine("[NUI] Realize!");
+                RecyclerViewItem item = null;
+                // 4. Get item if visible or realize new.
+                if (i >= prevFirstVisible && i <= prevLastVisible)
+                {
+                    item = GetVisibleItem(i);
+                    if (item) continue;
+                }
+                if (item == null) item = colView.RealizeItem(i);
+                VisibleItems.Add(item);
+
+                (float x, float y) = GetItemPosition(i);
+                // 5. Placing item.
+                item.Position = new Position(x, y);
+                //Console.WriteLine("[NUI] ["+item.Index+"] ["+item.Position.X+", "+item.Position.Y+" ==== \n");
+            }
+            //Console.WriteLine("Realize Done");
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override (float X, float Y) GetItemPosition(object item)
+        {
+            if (item == null) throw new ArgumentNullException(nameof(item));
+            if (colView == null) return (0, 0);
+
+            return GetItemPosition(colView.InternalItemSource.GetPosition(item));
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override (float X, float Y) GetItemSize(object item)
+        {
+            if (item == null) throw new ArgumentNullException(nameof(item));
+            if (sizeCandidate == null) return (0, 0);
+
+            if (isGrouped)
+            {
+                int index = colView.InternalItemSource.GetPosition(item);
+                float view = (IsHorizontal ? colView.Size.Height : colView.Size.Width);
+
+                if (colView.InternalItemSource.IsGroupHeader(index))
+                {
+                    return (IsHorizontal ? (groupHeaderSize, view) : (view, groupHeaderSize));
+                }
+                else if (colView.InternalItemSource.IsGroupFooter(index))
+                {
+                    return (IsHorizontal ? (groupFooterSize, view) : (view, groupFooterSize));
+                }
+            }
+
+            return (sizeCandidate.Width, sizeCandidate.Height);
+        }
+
+        /// <inheritdoc/>
+        public override void NotifyItemSizeChanged(RecyclerViewItem item)
+        {
+            // All Item size need to be same in grid!
+            // if you want to change item size, change dataTemplate to re-initing.
+            return;
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override float CalculateLayoutOrientationSize()
+        {
+            //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
+            return ScrollContentSize;
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override float CalculateCandidateScrollPosition(float scrollPosition)
+        {
+            //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
+            return scrollPosition;
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            if (currentFocusedView == null)
+                throw new ArgumentNullException(nameof(currentFocusedView));
+
+            View nextFocusedView = null;
+            int targetSibling = -1;
+            bool IsHorizontal = colView.ScrollingDirection == ScrollableBase.Direction.Horizontal;
+
+            switch (direction)
+            {
+                case View.FocusDirection.Left:
+                    {
+                        targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder - 1 : targetSibling;
+                        break;
+                    }
+                case View.FocusDirection.Right:
+                    {
+                        targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder + 1 : targetSibling;
+                        break;
+                    }
+                case View.FocusDirection.Up:
+                    {
+                        targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder - 1;
+                        break;
+                    }
+                case View.FocusDirection.Down:
+                    {
+                        targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder + 1;
+                        break;
+                    }
+            }
+
+            if (targetSibling > -1 && targetSibling < Container.Children.Count)
+            {
+                RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
+                if (candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
+                {
+                    nextFocusedView = candidate;
+                }
+            }
+            return nextFocusedView;
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
+        {
+            int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter ? 1 : 0);
+            int adds = spanSize * 2;
+            int skipGroup = -1;
+            (int start, int end) found = (0, 0);
+
+            // Header is Showing
+            if (hasHeader && visibleArea.X < headerSize)
+            {
+                found.start = 0;
+            }
+            else
+            {
+                if (isGrouped)
+                {
+                    bool failed = true;
+                    foreach (GroupInfo gInfo in groups)
+                    {
+                        skipGroup++;
+                        // in the Group
+                        if (gInfo.GroupPosition <= visibleArea.X &&
+                            gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
+                        {
+                            if (gInfo.GroupPosition + groupHeaderSize >= visibleArea.X)
+                            {
+                                found.start = gInfo.StartIndex - adds;
+                                failed = false;
+                            }
+                            //can be step in spanSize...
+                            for (int i = 1; i < gInfo.Count; i++)
+                            {
+                                if (!failed) break;
+                                // Reach last index of group.
+                                if (i == (gInfo.Count - 1))
+                                {
+                                    found.start = gInfo.StartIndex + i - adds;
+                                    failed = false;
+                                    break;
+
+                                }
+                                else if ((((i - 1) / spanSize) * StepCandidate) + StepCandidate >= visibleArea.X - gInfo.GroupPosition - groupHeaderSize)
+                                {
+                                    found.start = gInfo.StartIndex + i - adds;
+                                    failed = false;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    //footer only shows?
+                    if (failed)
+                    {
+                        found.start = MaxIndex;
+                    }
+                }
+                else
+                {
+                    float visibleAreaX = visibleArea.X - (hasHeader ? headerSize : 0);
+                    found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / StepCandidate)) - 1) * spanSize;
+                    if (hasHeader) found.start += 1;
+                }
+                if (found.start < 0) found.start = 0;
+            }
+
+            if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
+            {
+                found.end = MaxIndex + 1;
+            }
+            else
+            {
+                if (isGrouped)
+                {
+                    bool failed = true;
+                    // can it be start from founded group...?
+                    //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
+                    foreach (GroupInfo gInfo in groups)
+                    {
+                        // in the Group
+                        if (gInfo.GroupPosition <= visibleArea.Y &&
+                            gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
+                        {
+                            if (gInfo.GroupPosition + groupHeaderSize >= visibleArea.Y)
+                            {
+                                found.end = gInfo.StartIndex + adds;
+                                failed = false;
+                            }
+                            //can be step in spanSize...
+                            for (int i = 1; i < gInfo.Count; i++)
+                            {
+                                if (!failed) break;
+                                // Reach last index of group.
+                                if (i == (gInfo.Count - 1))
+                                {
+                                    found.end = gInfo.StartIndex + i + adds;
+                                    failed = false;
+                                    break;
+                                }
+                                else if ((((i - 1) / spanSize) * StepCandidate) + StepCandidate >= visibleArea.Y - gInfo.GroupPosition - groupHeaderSize)
+                                {
+                                    found.end = gInfo.StartIndex + i + adds;
+                                    failed = false;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    //footer only shows?
+                    if (failed)
+                    {
+                        found.start = MaxIndex;
+                    }
+                }
+                else
+                {
+                    float visibleAreaY = visibleArea.Y - (hasHeader ? headerSize : 0);
+                    //Need to Consider GroupHeight!!!!
+                    found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / StepCandidate)) + 1) * spanSize + adds;
+                    if (hasHeader) found.end += 1;
+                }
+                if (found.end > (MaxIndex)) found.end = MaxIndex;
+            }
+            return found;
+        }
+
+        private (float X, float Y) GetItemPosition(int index)
+        {
+            float xPos, yPos;
+            if (sizeCandidate == null) return (0, 0);
+
+            if (hasHeader && index == 0)
+            {
+                return (0, 0);
+            }
+            if (hasFooter && index == colView.InternalItemSource.Count - 1)
+            {
+                xPos = IsHorizontal ? ScrollContentSize - footerSize : 0;
+                yPos = IsHorizontal ? 0 : ScrollContentSize - footerSize;
+                return (xPos, yPos);
+            }
+            if (isGrouped)
+            {
+                GroupInfo myGroup = GetGroupInfo(index);
+                if (colView.InternalItemSource.IsGroupHeader(index))
+                {
+                    xPos = IsHorizontal ? myGroup.GroupPosition : 0;
+                    yPos = IsHorizontal ? 0 : myGroup.GroupPosition;
+                }
+                else if (colView.InternalItemSource.IsGroupFooter(index))
+                {
+                    xPos = IsHorizontal ? myGroup.GroupPosition + myGroup.GroupSize - groupFooterSize : 0;
+                    yPos = IsHorizontal ? 0 : myGroup.GroupPosition + myGroup.GroupSize - groupFooterSize;
+                }
+                else
+                {
+                    int pureIndex = index - myGroup.StartIndex - 1;
+                    int division = pureIndex / spanSize;
+                    int remainder = pureIndex % spanSize;
+                    int emptyArea = IsHorizontal ? (int)(colView.Size.Height - (sizeCandidate.Height * spanSize)) :
+                                                    (int)(colView.Size.Width - (sizeCandidate.Width * spanSize));
+                    if (division < 0) division = 0;
+                    if (remainder < 0) remainder = 0;
+
+                    xPos = IsHorizontal ? division * sizeCandidate.Width + myGroup.GroupPosition + groupHeaderSize : emptyArea * align + remainder * sizeCandidate.Width;
+                    yPos = IsHorizontal ? emptyArea * align + remainder * sizeCandidate.Height : division * sizeCandidate.Height + myGroup.GroupPosition + groupHeaderSize;
+                }
+            }
+            else
+            {
+                int pureIndex = index - (colView.Header ? 1 : 0);
+                // int convert must be truncate value.
+                int division = pureIndex / spanSize;
+                int remainder = pureIndex % spanSize;
+                int emptyArea = IsHorizontal ? (int)(colView.Size.Height - (sizeCandidate.Height * spanSize)) :
+                                                (int)(colView.Size.Width - (sizeCandidate.Width * spanSize));
+                if (division < 0) division = 0;
+                if (remainder < 0) remainder = 0;
+
+                xPos = IsHorizontal ? division * sizeCandidate.Width + (hasHeader ? headerSize : 0) : emptyArea * align + remainder * sizeCandidate.Width;
+                yPos = IsHorizontal ? emptyArea * align + remainder * sizeCandidate.Height : division * sizeCandidate.Height + (hasHeader ? headerSize : 0);
+            }
+
+            return (xPos, yPos);
+        }
+
+        private RecyclerViewItem GetVisibleItem(int index)
+        {
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (item.Index == index) return item;
+            }
+
+            return null;
+        }
+
+        private GroupInfo GetGroupInfo(int index)
+        {
+            if (Visited != null)
+            {
+                if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
+                    return Visited;
+            }
+            if (hasHeader && index == 0) return null;
+            foreach (GroupInfo group in groups)
+            {
+                if (group.StartIndex <= index && group.StartIndex + group.Count > index)
+                {
+                    Visited = group;
+                    return group;
+                }
+            }
+            Visited = null;
+            return null;
+        }
+
+        /*
+        private object GetGroupParent(int index)
+        {
+            if (Visited != null)
+            {
+                if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
+                return Visited.GroupParent;
+            }
+            if (hasHeader && index == 0) return null;
+            foreach (GroupInfo group in groups)
+            {
+                if (group.StartIndex <= index && group.StartIndex + group.Count > index)
+                {
+                    Visited = group;
+                    return group.GroupParent;
+                }
+            }
+            Visited = null;
+            return null;
+        }
+        */
+
+        class GroupInfo
+        {
+            public object GroupParent;
+            public int StartIndex;
+            public int Count;
+            public float GroupSize;
+            public float GroupPosition;
+            //Items relative position from the GroupPosition
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/ItemsLayouter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/ItemsLayouter.cs
new file mode 100644 (file)
index 0000000..19b1a19
--- /dev/null
@@ -0,0 +1,350 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.BaseComponents;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// Default layout manager for CollectionView.
+    /// Lay out ViewItem and recycle ViewItem.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public abstract class ItemsLayouter : ICollectionChangedNotifier, IDisposable
+    {
+        private bool disposed = false;
+
+        /// <summary>
+        /// Container which contains ViewItems.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected View Container{ get ; set; }
+
+        /// <summary>
+        /// Parent ItemsView.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected RecyclerView ItemsView{ get; set; }
+
+        /// <summary>
+        /// The last scrolled position which is calculated by ScrollableBase. The value should be updated in the Recycle() method.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected float PrevScrollPosition { get; set; }
+
+        /// <summary>
+        /// First index of visible items.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected int FirstVisible { get; set; } = -1;
+
+        /// <summary>
+        /// Last index of visible items.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected int LastVisible { get; set; } = -1;
+
+        /// <summary>
+        /// Visible ViewItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected List<RecyclerViewItem> VisibleItems { get; } = new List<RecyclerViewItem>();
+
+        /// <summary>
+        /// Flag of layouter initialization.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected bool IsInitialized { get; set; } = false;
+
+        /// <summary>
+        /// Candidate item step size for scroll size measure.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected float StepCandidate { get; set; }
+
+        /// <summary>
+        /// Content size of scrollable.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected float ScrollContentSize { get; set; }
+
+        /// <summary>
+        /// boolean flag of scrollable horizontal direction.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected bool IsHorizontal { get; set; }
+
+        /// <summary>
+        /// Clean up ItemsLayouter.
+        /// </summary>
+        /// <param name="view"> ItemsView of layouter.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void Initialize(RecyclerView view)
+        {
+            ItemsView = view ?? throw new ArgumentNullException(nameof(view));
+            Container = view.ContentContainer;
+            PrevScrollPosition = 0.0f;
+
+            IsHorizontal = (view.ScrollingDirection == ScrollableBase.Direction.Horizontal);
+
+            IsInitialized = true;
+        }
+
+        /// <summary>
+        /// This is called to find out where items are lain out according to current scroll position.
+        /// </summary>
+        /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+        /// <param name="force">boolean force flag to layouting forcely.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void RequestLayout(float scrollPosition, bool force = false)
+        {
+           // Layouting Items in scrollPosition.
+        }
+
+        /// <summary>
+        /// Clear the current screen and all properties.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void Clear()
+        {
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (ItemsView != null) ItemsView.UnrealizeItem(item, false);
+            }
+            VisibleItems.Clear();
+            ItemsView = null;
+            Container = null;
+        }
+
+        /// <summary>
+        /// Position of layouting item.
+        /// </summary>
+        /// <param name="item">item of dataset.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual (float X, float Y) GetItemPosition(object item)
+        {
+           if (item == null) throw new ArgumentNullException(nameof(item));
+           // Layouting Items in scrollPosition.
+           return (0, 0);
+        }
+
+        /// <summary>
+        /// Size of layouting item.
+        /// </summary>
+        /// <param name="item">item of dataset.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual (float X, float Y) GetItemSize(object item)
+        {
+           if (item == null) throw new ArgumentNullException(nameof(item));
+           // Layouting Items in scrollPosition.
+           return (0, 0);
+        }
+
+        /// <summary>
+        /// This is called to find out how much container size can be.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual float CalculateLayoutOrientationSize()
+        {
+            return 0.0f;
+        }
+
+        /// <summary>
+        /// Adjust scrolling position by own scrolling rules.
+        /// </summary>
+        /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual float CalculateCandidateScrollPosition(float scrollPosition)
+        {
+            return scrollPosition;
+        }
+
+        /// <summary>
+        /// Notify the relayout of ViewItem.
+        /// </summary>
+        /// <param name="item">updated ViewItem.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void NotifyItemSizeChanged(RecyclerViewItem item)
+        {
+        }
+
+        /// <summary>
+        /// Notify the dataset is Changed.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void NotifyDataSetChanged()
+        {
+            Initialize(ItemsView);
+        }
+
+        /// <summary>
+        /// Notify the observable item in startIndex is changed.
+        /// </summary>
+        /// <param name="source">Dataset source.</param>
+        /// <param name="startIndex">Changed item index.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void NotifyItemChanged(IItemSource source, int startIndex)
+        {
+        }
+
+        /// <summary>
+        /// Notify the observable item is inserted in dataset.
+        /// </summary>
+        /// <param name="source">Dataset source.</param>
+        /// <param name="startIndex">Inserted item index.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void NotifyItemInserted(IItemSource source, int startIndex)
+        {
+        }
+
+        /// <summary>
+        /// Notify the observable item is moved from fromPosition to ToPosition.
+        /// </summary>
+        /// <param name="source">Dataset source.</param>
+        /// <param name="fromPosition">Previous item position.</param>
+        /// <param name="toPosition">Moved item position.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
+        {
+        }
+
+        /// <summary>
+        /// Notify the range of observable items from start to end are changed.
+        /// </summary>
+        /// <param name="source">Dataset source.</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 virtual void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
+        {
+        }
+
+        /// <summary>
+        /// Notify the count range of observable items are inserted in startIndex.
+        /// </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>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
+        {
+        }
+
+        /// <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)
+        {
+        }
+
+        /// <summary>
+        /// Notify the observable item in startIndex is removed.
+        /// </summary>
+        /// <param name="source">Dataset source.</param>
+        /// <param name="startIndex">Index of removed item.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void NotifyItemRemoved(IItemSource source, int startIndex)
+        {
+        }
+
+        /// <summary>
+        /// Gets the next keyboard focusable view in this control towards the given direction.<br />
+        /// A control needs to override this function in order to support two dimensional keyboard navigation.<br />
+        /// </summary>
+        /// <param name="currentFocusedView">The current focused view.</param>
+        /// <param name="direction">The direction to move the focus towards.</param>
+        /// <param name="loopEnabled">Whether the focus movement should be looped within the control.</param>
+        /// <returns>The next keyboard focusable view in this control or an empty handle if no view can be focused.</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            return null;
+        }
+
+        /// <summary>
+        /// Dispose ItemsLayouter and all children on it.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        /// <summary>
+        /// Measure the size of chlid ViewItem manually.
+        /// </summary>
+        /// <param name="parent">Parent ItemsView.</param>
+        /// <param name="child">Child ViewItem to Measure()</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual void MeasureChild(RecyclerView parent, RecyclerViewItem child)
+        {
+            if (parent == null) throw new ArgumentNullException(nameof(parent));
+            if (child == null) throw new ArgumentNullException(nameof(child));
+
+            if (child.Layout == null) return;
+
+            //FIXME: This measure can be restricted size of child to be less than parent size.
+            // but in some multiple-line TextLabel can be long enough to over the it's parent size.
+
+            MeasureSpecification childWidthMeasureSpec = LayoutGroup.GetChildMeasureSpecification(
+                        new MeasureSpecification(new LayoutLength(parent.Size.Width), MeasureSpecification.ModeType.Exactly),
+                        new LayoutLength(0),
+                        new LayoutLength(child.WidthSpecification));
+
+            MeasureSpecification childHeightMeasureSpec = LayoutGroup.GetChildMeasureSpecification(
+                        new MeasureSpecification(new LayoutLength(parent.Size.Height), MeasureSpecification.ModeType.Exactly),
+                        new LayoutLength(0),
+                        new LayoutLength(child.HeightSpecification));
+
+            child.Layout.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
+        }
+
+        /// <summary>
+        /// Find consecutive visible items index.
+        /// </summary>
+        /// <param name="visibleArea">float turple of visible area start position to end position. </param>
+        /// <return>int turple of start index to end index</return>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
+        {
+            return (0, 0);
+        }
+
+        /// <summary>
+        /// Dispose ItemsLayouter and all children on it.
+        /// </summary>
+        /// <param name="disposing">true when it disposed by Dispose(). </param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+            disposed = true;
+            if (disposing) Clear();
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs
new file mode 100644 (file)
index 0000000..a247d0e
--- /dev/null
@@ -0,0 +1,743 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.Linq;
+using Tizen.NUI.BaseComponents;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Components
+{
+
+
+    /// <summary>
+    /// [Draft] This class implements a linear box layout.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class LinearLayouter : ItemsLayouter
+    {
+        private readonly List<float> ItemPosition = new List<float>();
+        private readonly List<float> ItemSize = new List<float>();
+        private int ItemSizeChanged = -1;
+        private CollectionView colView;
+        private bool hasHeader;
+        private float headerSize;
+        private bool hasFooter;
+        private float footerSize;
+        private bool isGrouped;
+        private readonly List<GroupInfo> groups = new List<GroupInfo>();
+        private float groupHeaderSize;
+        private float groupFooterSize;
+        private GroupInfo Visited;
+
+        /// <summary>
+        /// Clean up ItemsLayouter.
+        /// </summary>
+        /// <param name="view"> ItemsView of layouter.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void Initialize(RecyclerView view)
+        {
+            colView = view as CollectionView;
+            if (colView == null)
+            {
+                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();
+
+            FirstVisible = 0;
+            LastVisible = 0;
+
+            IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
+
+            RecyclerViewItem header = colView?.Header;
+            RecyclerViewItem footer = colView?.Footer;
+            float width, height;
+            int count = colView.InternalItemSource.Count;
+
+            if (header != null)
+            {
+                MeasureChild(colView, header);
+
+                width = header.Layout != null ? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
+                height = header.Layout != null ? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
+
+                headerSize = IsHorizontal ? width : height;
+                hasHeader = true;
+                
+                colView.UnrealizeItem(header);
+            }
+            else hasHeader = false;
+
+            if (footer != null)
+            {
+                MeasureChild(colView, footer);
+
+                width = footer.Layout != null ? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
+                height = footer.Layout != null ? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
+
+                footerSize = IsHorizontal ? width : height;
+                footer.Index = count - 1;
+                hasFooter = true;
+                
+                colView.UnrealizeItem(footer);
+            }
+            else hasFooter = false;
+
+            //No Internal Source exist.
+            if (count == (hasHeader? (hasFooter? 2 : 1) : 0)) return;
+
+            int firstIndex = hasHeader ? 1 : 0;
+
+            if (colView.IsGrouped)
+            {
+                isGrouped = true;
+
+                if (colView.GroupHeaderTemplate != null)
+                {
+                    while (!colView.InternalItemSource.IsGroupHeader(firstIndex)) firstIndex++;
+                    //must be always true
+                    if (colView.InternalItemSource.IsGroupHeader(firstIndex))
+                    {
+                        RecyclerViewItem groupHeader = colView.RealizeItem(firstIndex);
+                        firstIndex++;
+
+                        if (groupHeader == null) throw new Exception("["+firstIndex+"] Group Header failed to realize!");
+
+                        // Need to Set proper hieght or width on scroll direciton.
+                        if (groupHeader.Layout == null)
+                        {
+                            width =  groupHeader.WidthSpecification;
+                            height = groupHeader.HeightSpecification;
+                        }
+                        else
+                        {
+                            MeasureChild(colView, groupHeader);
+
+                            width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
+                            height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
+                        }
+                        //Console.WriteLine("[NUI] GroupHeader Size {0} :{0}", width, height);
+                        // pick the StepCandidate.
+                        groupHeaderSize = IsHorizontal ? width : height;
+                        colView.UnrealizeItem(groupHeader);
+                    }
+                }
+                else
+                {
+                    groupHeaderSize = 0F;
+                }
+
+                if (colView.GroupFooterTemplate != null)
+                {
+                    int firstFooter = firstIndex;
+                    while (!colView.InternalItemSource.IsGroupFooter(firstFooter)) firstFooter++;
+                    //must be always true
+                    if (colView.InternalItemSource.IsGroupFooter(firstFooter))
+                    {
+                        RecyclerViewItem groupFooter = colView.RealizeItem(firstFooter);
+
+                        if (groupFooter == null) throw new Exception("["+firstFooter+"] Group Footer failed to realize!");
+                        // Need to Set proper hieght or width on scroll direciton.
+                        if (groupFooter.Layout == null)
+                        {
+                            width =  groupFooter.WidthSpecification;
+                            height = groupFooter.HeightSpecification;
+                        }
+                        else
+                        {
+                            MeasureChild(colView, groupFooter);
+
+                            width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
+                            height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
+                        }
+                        // pick the StepCandidate.
+                        groupFooterSize = IsHorizontal ? width : height;
+
+                        colView.UnrealizeItem(groupFooter);
+                    }
+                }
+                else
+                {
+                    groupFooterSize = 0F;
+                }
+            }
+            else isGrouped = false;
+
+            bool failed = false;
+            //Final Check of FirstIndex
+            while (colView.InternalItemSource.IsHeader(firstIndex) ||
+                    colView.InternalItemSource.IsGroupHeader(firstIndex) ||
+                    colView.InternalItemSource.IsGroupFooter(firstIndex))
+            {
+                if (colView.InternalItemSource.IsFooter(firstIndex))
+                {
+                    StepCandidate = 0F;
+                    failed = true;
+                    break;
+                }
+                firstIndex++;
+            }
+
+            if (!failed)
+            {
+                RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
+                if (sizeDeligate == null)
+                {
+                    // error !
+                    throw new Exception("Cannot create content from DatTemplate.");
+                }
+
+                sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
+
+                // Need to Set proper hieght or width on scroll direciton.
+                if (sizeDeligate.Layout == null)
+                {
+                    width =  sizeDeligate.WidthSpecification;
+                    height = sizeDeligate.HeightSpecification;
+                }
+                else
+                {
+                    MeasureChild(colView, sizeDeligate);
+
+                    width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
+                    height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
+                }
+                //Console.WriteLine("[NUI] Layout Size {0} :{0}", width, height);
+                // pick the StepCandidate.
+                StepCandidate = IsHorizontal ? width : height;
+                if (StepCandidate == 0) StepCandidate = 1; //????
+                
+                colView.UnrealizeItem(sizeDeligate);
+            }
+
+            float Current = 0.0F;
+            IGroupableItemSource source = colView.InternalItemSource;
+            GroupInfo currentGroup = null;
+            for (int i = 0; i < count; i++)
+            {
+                if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                {
+                    if (i == 0 && hasHeader)
+                        ItemSize.Add(headerSize);
+                    else if (i == count -1 && hasFooter)
+                        ItemSize.Add(footerSize);
+                    else if (source.IsGroupHeader(i))
+                        ItemSize.Add(groupHeaderSize);
+                    else if (source.IsGroupFooter(i))
+                        ItemSize.Add(groupFooterSize);
+                    else ItemSize.Add(StepCandidate);
+                }
+                if (isGrouped)
+                {
+                    if (i == 0 && hasHeader)
+                    {
+                        //ItemPosition.Add(Current);
+                        Current += headerSize;
+                    }
+                    else if (i == count -1 && hasFooter)
+                    {
+                        //ItemPosition.Add(Current);
+                        Current += footerSize;
+                    }
+                    else
+                    {
+                        //GroupHeader must always exist in group usage.
+                        if (source.IsGroupHeader(i))
+                        {
+                            currentGroup = new GroupInfo()
+                            {
+                                GroupParent = source.GetGroupParent(i),
+                                //hasHeader = true,
+                                //hasFooter = false,
+                                StartIndex = i,
+                                Count = 1,
+                                GroupSize = groupHeaderSize,
+                                GroupPosition = Current
+                            };
+                            currentGroup.ItemPosition.Add(0);
+                            groups.Add(currentGroup);
+                            Current += groupHeaderSize;
+                        }
+                        //optional
+                        else if (source.IsGroupFooter(i))
+                        {
+                            //currentGroup.hasFooter = true;
+                            currentGroup.Count++;
+                            currentGroup.GroupSize += groupFooterSize;
+                            currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
+                            Current += groupFooterSize;
+                        }
+                        else
+                        {
+                            currentGroup.Count++;
+                            currentGroup.GroupSize += StepCandidate;
+                            currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
+                            Current += StepCandidate;
+                        }
+                    }
+                }
+                else
+                {
+                    ItemPosition.Add(Current);
+
+                    if (i == 0 && hasHeader) Current += headerSize;
+                    else if (i == count -1 && hasFooter) Current += footerSize;
+                    else Current += StepCandidate;
+                }
+            }
+
+            ScrollContentSize = Current;
+            if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+            else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+
+            base.Initialize(view);
+            //Console.WriteLine("[NUI] Init Done, StepCnadidate{0}, Scroll{1}", StepCandidate, ScrollContentSize);
+        }
+
+        /// <summary>
+        /// This is called to find out where items are lain out according to current scroll position.
+        /// </summary>
+        /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+        /// <param name="force">boolean force flag to layouting forcely.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void RequestLayout(float scrollPosition, bool force = false)
+        {
+            // Layouting is only possible after once it intialized.
+            if (!IsInitialized) return;
+            int LastIndex = colView.InternalItemSource.Count -1;
+
+            if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
+            PrevScrollPosition = Math.Abs(scrollPosition);
+
+            if (ItemSizeChanged >= 0)
+            {
+                for (int i = ItemSizeChanged; i <= LastIndex; i++)
+                    UpdatePosition(i);
+                ScrollContentSize = ItemPosition[LastIndex - 1] + GetItemSize(LastIndex);
+            }
+
+            int prevFirstVisible = FirstVisible;
+            int prevLastVisible = LastVisible;
+
+            (float X, float Y) visibleArea = (PrevScrollPosition,
+                PrevScrollPosition + ( IsHorizontal ? colView.Size.Width : colView.Size.Height)
+            );
+
+            // 1. Set First/Last Visible Item Index. 
+            (int start, int end) = FindVisibleItems(visibleArea);
+            FirstVisible = start;
+            LastVisible = end;
+
+            // 2. Unrealize invisible items.
+            List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (item.Index < FirstVisible || item.Index > LastVisible)
+                {
+                   //Console.WriteLine("[NUI] Unrealize{0}!", item.Index);
+                   unrealizedItems.Add(item);
+                   colView.UnrealizeItem(item);
+                }
+            }
+            VisibleItems.RemoveAll(unrealizedItems.Contains);
+
+            // 3. Realize and placing visible items.
+            for (int i = FirstVisible; i <= LastVisible; i++)
+            {
+                RecyclerViewItem item = null;
+                // 4. Get item if visible or realize new.
+                if (i >= prevFirstVisible && i <= prevLastVisible)
+                {
+                    item = GetVisibleItem(i);
+                    if (item) continue;
+                }
+                if (item == null) item = colView.RealizeItem(i);
+
+                VisibleItems.Add(item);
+
+                // 5. Placing item.
+                float posX = 0F, posY = 0F;
+                if (isGrouped)
+                {
+                    //isHeader?
+                    if (colView.Header == item)
+                    {
+                        posX = 0F;
+                        posY = 0F;
+                    }
+                    else if (colView.Footer == item)
+                    {
+                        posX = (IsHorizontal ? ScrollContentSize - item.SizeWidth : 0F);
+                        posY =(IsHorizontal ? 0F : ScrollContentSize - item.SizeHeight); 
+                    }
+                    else
+                    {
+                        GroupInfo gInfo = GetGroupInfo(i);
+                        posX = (IsHorizontal ? gInfo.GroupPosition + gInfo.ItemPosition[i - gInfo.StartIndex] : 0F);
+                        posY = (IsHorizontal ? 0F : gInfo.GroupPosition + gInfo.ItemPosition[i - gInfo.StartIndex]);
+                    }
+                }
+                else
+                {
+                    posX = (IsHorizontal ? ItemPosition[i] : 0F);
+                    posY = (IsHorizontal ? 0F : ItemPosition[i]);
+                }
+
+                item.Position = new Position(posX, posY);
+                //Console.WriteLine("[NUI] ["+item+"]["+item.Index+"] :: ["+item.Position.X+", "+item.Position.Y+"] ==== \n");
+            }
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override (float X, float Y) GetItemPosition(object item)
+        {
+           if (item == null) throw new ArgumentNullException(nameof(item));
+           // Layouting Items in scrollPosition.
+           float pos = ItemPosition[colView.InternalItemSource.GetPosition(item)];
+
+           return (IsHorizontal ? (pos, 0.0F) : (0.0F, pos));
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override (float X, float Y) GetItemSize(object item)
+        {
+           if (item == null) throw new ArgumentNullException(nameof(item));
+           // Layouting Items in scrollPosition.
+           float size = GetItemSize(colView.InternalItemSource.GetPosition(item));
+           float view = (IsHorizontal ? colView.Size.Height : colView.Size.Width);
+
+           return (IsHorizontal ? (size, view) : (view, size));
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void NotifyItemSizeChanged(RecyclerViewItem item)
+        {
+            if (item == null)
+                throw new ArgumentNullException(nameof(item));
+
+            if (!IsInitialized ||
+                (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst &&
+                item.Index != 0) ||
+                (item.Index < 0))
+                return;
+
+            float PrevSize, CurrentSize;
+            if (item.Index == (colView.InternalItemSource.Count-1))
+            {
+                PrevSize = ScrollContentSize - ItemPosition[item.Index];
+            }
+            else
+            {
+                PrevSize = ItemPosition[item.Index + 1] - ItemPosition[item.Index];
+            }
+
+            CurrentSize = (IsHorizontal ? item.Size.Width : item.Size.Height);
+
+            if (CurrentSize != PrevSize)
+            {
+                if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+                  ItemSize[item.Index] = CurrentSize;
+                else
+                  StepCandidate = CurrentSize;
+            }
+            if (ItemSizeChanged == -1) ItemSizeChanged = item.Index;
+            else ItemSizeChanged = Math.Min(ItemSizeChanged, item.Index);
+
+            //ScrollContentSize += Diff; UpdateOnce?
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override float CalculateLayoutOrientationSize()
+        {
+            //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
+            return ScrollContentSize;
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override float CalculateCandidateScrollPosition(float scrollPosition)
+        {
+            //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
+            return scrollPosition;
+        }
+
+        /// <Inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            if (currentFocusedView == null)
+                throw new ArgumentNullException(nameof(currentFocusedView));
+
+            View nextFocusedView = null;
+            int targetSibling = -1;
+
+            switch(direction)
+            {
+                case View.FocusDirection.Left :
+                {
+                    targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder - 1 : targetSibling;
+                    break;
+                }
+                case View.FocusDirection.Right :
+                {
+                    targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder + 1 : targetSibling;
+                    break;
+                }
+                case View.FocusDirection.Up :
+                {
+                    targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder - 1;
+                    break;
+                }
+                case View.FocusDirection.Down :
+                {
+                    targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder + 1;
+                    break;
+                }
+            }
+
+            if(targetSibling > -1 && targetSibling < Container.Children.Count)
+            {
+                RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
+                if(candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
+                {
+                    nextFocusedView = candidate;
+                }
+            }
+
+            return nextFocusedView;
+        }
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
+        {
+            int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter ? 1 : 0);
+            int adds = 5;
+            int skipGroup = -2;
+            (int start, int end) found = (0, 0);
+
+            // 1. Find the start index.
+            // Header is Showing
+            if (hasHeader && visibleArea.X <= headerSize)
+            {
+                found.start = 0;
+            }
+            else
+            {
+                if (isGrouped)
+                {
+                    bool failed = true;
+                    foreach(GroupInfo gInfo in groups)
+                    {
+                        skipGroup++;
+                        // in the Group
+                        if (gInfo.GroupPosition <= visibleArea.X &&
+                            gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
+                        {
+                            for (int i = 0; i < gInfo.Count; i++)
+                            {
+                                // Reach last index of group.
+                                if (i == (gInfo.Count - 1))
+                                {
+                                    found.start = gInfo.StartIndex + i - adds;
+                                    failed = false;
+                                    break;
+                                    
+                                }
+                                else if (gInfo.ItemPosition[i] <= visibleArea.X - gInfo.GroupPosition &&
+                                        gInfo.ItemPosition[i + 1] >= visibleArea.X - gInfo.GroupPosition)
+                                {
+                                    found.start = gInfo.StartIndex + i - adds;
+                                    failed = false;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    //footer only shows?
+                    if (failed)
+                    {
+                        found.start = MaxIndex;
+                    }
+                }
+                else
+                {
+                    float visibleAreaX = visibleArea.X - (hasHeader ? headerSize : 0);
+                    found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / StepCandidate)) - adds);
+                }
+
+                if (found.start < 0) found.start = 0;
+            }
+
+            if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
+            {
+                found.end = MaxIndex + 1;
+            }
+            else
+            {
+                if (isGrouped)
+                {
+                    bool failed = true;
+                    // can it be start from founded group...?
+                    //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
+                    foreach(GroupInfo gInfo in groups)
+                    {
+                        // in the Group
+                        if (gInfo.GroupPosition <= visibleArea.Y &&
+                            gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
+                        {
+                            for (int i = 0; i < gInfo.Count; i++)
+                            {
+                                if (i == (gInfo.Count - 1))
+                                {
+                                    //Sould be groupFooter!
+                                    found.end = gInfo.StartIndex + i + adds;
+                                    failed = false;
+                                    break;
+                                    
+                                }
+                                else if (gInfo.ItemPosition[i] <= visibleArea.Y - gInfo.GroupPosition &&
+                                        gInfo.ItemPosition[i + 1] >= visibleArea.Y - gInfo.GroupPosition)
+                                {
+                                    found.end = gInfo.StartIndex + i + adds;
+                                    failed = false;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    if (failed) found.end = MaxIndex;
+                }
+                else
+                {
+                float visibleAreaY = visibleArea.Y - (hasHeader ? headerSize : 0);
+                    found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / StepCandidate)) + adds);
+                    if (hasHeader) found.end += 1;
+                }
+                if (found.end > (MaxIndex)) found.end = MaxIndex;
+            }
+            return found;
+        }
+
+        private float GetItemSize(int index)
+        {
+            if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+            {
+                return ItemSize[index];
+            }
+            else
+            {
+                if (index == 0 && hasHeader)
+                    return headerSize;
+                if (index == colView.InternalItemSource.Count - 1 && hasFooter)
+                    return footerSize;
+                return StepCandidate;
+            }
+        }
+
+        private void UpdatePosition(int index)
+        {
+            bool IsGroup = (colView.InternalItemSource is IGroupableItemSource);
+
+            if (index <= 0) return;
+            if (index >= colView.InternalItemSource.Count)
+
+            if (IsGroup)
+            {
+                //IsGroupHeader = (colView.InternalItemSource as IGroupableItemSource).IsGroupHeader(index);
+                //IsGroupFooter = (colView.InternalItemSource as IGroupableItemSource).IsGroupFooter(index);
+                //Do Something
+            }
+
+            ItemPosition[index] = ItemPosition[index-1] + GetItemSize(index-1);
+        }
+
+        private RecyclerViewItem GetVisibleItem(int index)
+        {
+            foreach (RecyclerViewItem item in VisibleItems)
+            {
+                if (item.Index == index) return item;
+            }
+            return null;
+        }
+
+        private GroupInfo GetGroupInfo(int index)
+        {
+            if (Visited != null)
+            {
+                if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
+                return Visited;
+            }
+            if (hasHeader && index == 0) return null;
+            foreach (GroupInfo group in groups)
+            {
+                if (group.StartIndex <= index && group.StartIndex + group.Count > index)
+                {
+                    Visited = group;
+                    return group;
+                }
+            }
+            Visited = null;
+            return null;
+        }
+/*
+        private object GetGroupParent(int index)
+        {
+            if (Visited != null)
+            {
+                if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
+                return Visited.GroupParent;
+            }
+            if (hasHeader && index == 0) return null;
+            foreach (GroupInfo group in groups)
+            {
+                if (group.StartIndex <= index && group.StartIndex + group.Count > index)
+                {
+                    Visited = group;
+                    return group.GroupParent;
+                }
+            }
+            Visited = null;
+            return null;
+        }
+*/
+        class GroupInfo
+        {
+            public object GroupParent;
+            public int StartIndex;
+            public int Count;
+            public float GroupSize;
+            public float GroupPosition; 
+            //Items relative position from the GroupPosition
+            public List<float> ItemPosition = new List<float>();
+        }
+    }
+}
index 6cd7779..548aea0 100755 (executable)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  *
  */
 using System;
-using Tizen.NUI.BaseComponents;
+using System.Linq;
+using System.Collections;
 using System.Collections.Generic;
 using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
 
 namespace Tizen.NUI.Components
 {
     /// <summary>
-    /// [Draft] This class provides a View that can recycle items to improve performance.
+    /// [Draft] This class provides a View that can layouting items in list and grid with high performance.
     /// </summary>
-    /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
     [EditorBrowsable(EditorBrowsableState.Never)]
-    public class RecyclerView : ScrollableBase
+    public abstract class RecyclerView : ScrollableBase, ICollectionChangedNotifier
     {
-        private RecycleAdapter adapter;
-        private RecycleLayoutManager layoutManager;
-        private int totalItemCount = 15;
-        private List<PropertyNotification> notifications = new List<PropertyNotification>();
-
+        /// <summary>
+        /// Base Constructor
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
         public RecyclerView() : base()
         {
-            Initialize(new RecycleAdapter(), new RecycleLayoutManager());
+            Scrolling += OnScrolling;
         }
 
         /// <summary>
-        /// Default constructor.
+        /// Item's source data.
         /// </summary>
-        /// <param name="adapter">Recycle adapter of RecyclerView.</param>
-        /// <param name="layoutManager">Recycle layoutManager of RecyclerView.</param>
-        /// <since_tizen> 8 </since_tizen>
-        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public RecyclerView(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
-        {
-            Initialize(adapter, layoutManager);
-        }
+        public virtual IEnumerable ItemsSource { get; set; }
 
-        private void Initialize(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
-        {
-            FocusGroup = true;
-            SetKeyboardNavigationSupport(true);
-            Scrolling += OnScrolling;
+        /// <summary>
+        /// DataTemplate for items.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual DataTemplate ItemTemplate { get; set; }
 
-            this.adapter = adapter;
-            this.adapter.OnDataChanged += OnAdapterDataChanged;
+        /// <summary>
+        /// Internal encapsulated items data source.
+        /// </summary>
+        internal IItemSource InternalItemSource { get; set;}
 
-            this.layoutManager = layoutManager;
-            this.layoutManager.Container = ContentContainer;
-            this.layoutManager.ItemSize = this.adapter.CreateRecycleItem().Size;
-            this.layoutManager.DataCount = this.adapter.Data.Count;
+        /// <summary>
+        /// RecycleCache of ViewItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected List<RecyclerViewItem> RecycleCache { get; } = new List<RecyclerViewItem>();
 
-            InitializeItems();
-        }
+        /// <summary>
+        /// Internal Items Layouter.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected ItemsLayouter InternalItemsLayouter {get; set; }
 
-        private void OnItemSizeChanged(object source, PropertyNotification.NotifyEventArgs args)
-        {
-            layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
-        }
+        /// <summary>
+        /// Max size of RecycleCache. Default is 50.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected int CacheMax { get; set; } = 50;
 
-        public int TotalItemCount
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void OnRelayout(Vector2 size, RelayoutContainer container)
         {
-            get
-            {
-                return totalItemCount;
-            }
-            set
+            //Console.WriteLine("[NUI] On ReLayout [{0} {0}]", size.X, size.Y);
+            base.OnRelayout(size, container);
+            if (InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null) 
             {
-                totalItemCount = value;
-                InitializeItems();
+                InternalItemsLayouter.Initialize(this);
+                InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y, true);
             }
         }
 
-        private void InitializeItems()
+        /// <summary>
+        /// Notify Dataset is Changed.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void NotifyDataSetChanged()
         {
-            for (int i = Children.Count - 1; i > -1; i--)
-            {
-                Children[i].Unparent();
-                notifications[i].Notified -= OnItemSizeChanged;
-                notifications.RemoveAt(i);
-            }
-
-            for (int i = 0; i < totalItemCount; i++)
+            //Need to update view.
+            if (InternalItemsLayouter != null)
             {
-                RecycleItem item = adapter.CreateRecycleItem();
-                item.DataIndex = i;
-                item.Name = "[" + i + "] recycle";
-
-                if (i < adapter.Data.Count)
+                InternalItemsLayouter.NotifyDataSetChanged();
+                if (ScrollingDirection == Direction.Horizontal)
                 {
-                    adapter.BindData(item);
+                    ContentContainer.SizeWidth =
+                        InternalItemsLayouter.CalculateLayoutOrientationSize();
+                }
+                else
+                {
+                    ContentContainer.SizeHeight =
+                        InternalItemsLayouter.CalculateLayoutOrientationSize();
                 }
-                Add(item);
-
-                PropertyNotification noti = item.AddPropertyNotification("size", PropertyCondition.Step(0.1f));
-                noti.Notified += OnItemSizeChanged;
-                notifications.Add(noti);
             }
+        }
 
-            layoutManager.Layout(0.0f);
-
-            if (ScrollingDirection == Direction.Horizontal)
-            {
-                ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
-            }
-            else
+        /// <summary>
+        /// Notify observable item is changed.
+        /// </summary>
+        /// <param name="source">Dataset source.</param>
+        /// <param name="startIndex">Changed item index.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void NotifyItemChanged(IItemSource source, int startIndex)
+        {
+            if (InternalItemsLayouter != null)
             {
-                ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
+                InternalItemsLayouter.NotifyItemChanged(source, startIndex);
             }
         }
 
-
-        public new Direction ScrollingDirection
+        /// <summary>
+        /// Notify observable item is inserted in dataset.
+        /// </summary>
+        /// <param name="source">Dataset source.</param>
+        /// <param name="startIndex">Inserted item index.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void NotifyItemInserted(IItemSource source, int startIndex)
         {
-            get
+            if (InternalItemsLayouter != null)
             {
-                return base.ScrollingDirection;
-            }
-            set
-            {
-                base.ScrollingDirection = value;
-
-                if (ScrollingDirection == Direction.Horizontal)
-                {
-                    ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
-                }
-                else
-                {
-                    ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
-                }
+                InternalItemsLayouter.NotifyItemInserted(source, startIndex);
             }
         }
 
         /// <summary>
-        /// Recycler adpater.
+        /// Notify observable item is moved from fromPosition to ToPosition.
         /// </summary>
-        /// <since_tizen> 8 </since_tizen>
-        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        /// <param name="source">Dataset source.</param>
+        /// <param name="fromPosition">Previous item position.</param>
+        /// <param name="toPosition">Moved item position.</param>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public RecycleAdapter Adapter
+        public void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
         {
-            get
+            if (InternalItemsLayouter != null)
             {
-                return adapter;
+                InternalItemsLayouter.NotifyItemMoved(source, fromPosition, toPosition);
             }
-            set
-            {
-                if (adapter != null)
-                {
-                    adapter.OnDataChanged -= OnAdapterDataChanged;
-                }
+        }
 
-                adapter = value;
-                adapter.OnDataChanged += OnAdapterDataChanged;
-                layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
-                layoutManager.DataCount = adapter.Data.Count;
-                InitializeItems();
+        /// <summary>
+        /// Notify range of observable items from start to end are changed.
+        /// </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>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void NotifyItemRangeChanged(IItemSource source, int start, int end)
+        {
+            if (InternalItemsLayouter != null)
+            {
+                InternalItemsLayouter.NotifyItemRangeChanged(source, start, end);
             }
         }
 
         /// <summary>
-        /// Recycler layoutManager.
+        /// Notify count range of observable count items are inserted in startIndex.
         /// </summary>
-        /// <since_tizen> 8 </since_tizen>
-        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        /// <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>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public RecycleLayoutManager LayoutManager
+        public void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
         {
-            get
+            if (InternalItemsLayouter != null)
             {
-                return layoutManager;
+                InternalItemsLayouter.NotifyItemRangeInserted(source, startIndex, count);
             }
-            set
+        }
+
+        /// <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 void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
+        {
+            if (InternalItemsLayouter != null)
             {
-                layoutManager = value;
-                layoutManager.Container = ContentContainer;
-                layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
-                layoutManager.DataCount = adapter.Data.Count;
-                InitializeItems();
+                InternalItemsLayouter.NotifyItemRangeRemoved(source, startIndex, count);
             }
         }
 
-        private void OnScrolling(object source, ScrollEventArgs args)
+        /// <summary>
+        /// Notify the observable item in startIndex is removed.
+        /// </summary>
+        /// <param name="source">Dataset source.</param>
+        /// <param name="startIndex">Index of removed item.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void NotifyItemRemoved(IItemSource source, int startIndex)
         {
-            layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
-            List<RecycleItem> recycledItemList = layoutManager.Recycle(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
-            BindData(recycledItemList);
+            if (InternalItemsLayouter != null)
+            {
+                InternalItemsLayouter.NotifyItemRemoved(source, startIndex);
+            }
         }
 
-        private void OnAdapterDataChanged(object source, EventArgs args)
+        /// <summary>
+        /// Realize indexed item.
+        /// </summary>
+        /// <param name="index"> Index position of realizing item </param>
+        internal virtual RecyclerViewItem RealizeItem(int index)
         {
-            List<RecycleItem> changedData = new List<RecycleItem>();
+            object context = InternalItemSource.GetItem(index);
+            // Check DataTemplate is Same!
+            if (ItemTemplate is DataTemplateSelector)
+            {
+                // Need to implements for caching of selector!
+            }
+            else
+            {
+               // pop item
+               RecyclerViewItem item = PopRecycleCache(ItemTemplate);
+               if (item != null)
+               {
+                    DecorateItem(item, index, context);
+                    return item;
+               }
+            }
 
-            foreach (RecycleItem item in Children)
+            object content = DataTemplateExtensions.CreateContent(ItemTemplate, context, (BindableObject)this) ?? throw new Exception("Template return null object.");
+            if (content is RecyclerViewItem)
             {
-                changedData.Add(item);
+                RecyclerViewItem item = (RecyclerViewItem)content;
+                ContentContainer.Add(item);
+                DecorateItem(item, index, context);
+                return item;
+            }
+            else
+            {
+                throw new Exception("Template content must be type of ViewItem");
             }
 
-            BindData(changedData);
         }
 
-        private void BindData(List<RecycleItem> changedData)
+        /// <summary>
+        /// Unrealize indexed item.
+        /// </summary>
+        /// <param name="item"> Target item for unrealizing </param>
+        /// <param name="recycle"> Allow recycle. default is true </param>
+        internal virtual void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
         {
-            foreach (RecycleItem item in changedData)
+            item.Index = -1;
+            item.ParentItemsView = null;
+            // Remove BindingContext null set for performance improving.
+            //item.BindingContext = null; 
+            item.IsPressed = false;
+            item.IsSelected = false;
+            item.IsEnabled = true;
+            // Remove Update Style on default for performance improving.
+            //item.UpdateState();
+            item.Relayout -= OnItemRelayout;
+
+            if (!recycle || !PushRecycleCache(item))
             {
-                if (item.DataIndex > -1 && item.DataIndex < adapter.Data.Count)
-                {
-                    item.Show();
-                    item.Name = "[" + item.DataIndex + "]";
-                    adapter.BindData(item);
-                }
-                else
-                {
-                    item.Hide();
-                }
+                //ContentContainer.Remove(item);
+                Utility.Dispose(item);
             }
         }
 
@@ -234,138 +278,116 @@ namespace Tizen.NUI.Components
         /// Adjust scrolling position by own scrolling rules.
         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
         /// </summary>
-        /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
+        /// <param name="position">Scroll position which is calculated by ScrollableBase.</param>
         /// <returns>Adjusted scroll destination</returns>
-        /// <since_tizen> 8 </since_tizen>
-        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
         [EditorBrowsable(EditorBrowsableState.Never)]
         protected override float AdjustTargetPositionOfScrollAnimation(float position)
         {
             // Destination is depending on implementation of layout manager.
             // Get destination from layout manager.
-            return layoutManager.CalculateCandidateScrollPosition(position);
+            return InternalItemsLayouter.CalculateCandidateScrollPosition(position);
         }
 
-        private View focusedView;
-        private int prevFocusedDataIndex = 0;
+        /// <summary>
+        /// Push the item into the recycle cache. this item will be reused in view update.
+        /// </summary>
+        /// <param name="item"> Target item to push into recycle cache. </param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual bool PushRecycleCache(RecyclerViewItem item)
+        {
+            if (item == null) throw new ArgumentNullException(nameof(item));
+            if (RecycleCache.Count >= CacheMax) return false;
+            if (item.Template == null) return false;
+            item.Hide();
+            item.Index = -1;
+            RecycleCache.Add(item);
+            return true;
+        }
 
-        public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        /// <summary>
+        /// Pop the item from the recycle cache.
+        /// </summary>
+        /// <param name="Template"> Template of wanted item. </param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual RecyclerViewItem PopRecycleCache(DataTemplate Template)
         {
-            View nextFocusedView = null;
+           for (int i = 0; i < RecycleCache.Count; i++)
+           {
+               RecyclerViewItem item = RecycleCache[i];
+               if (item.Template == Template)
+               {
+                   RecycleCache.Remove(item);
+                   item.Show();
+                   return item;
+               }
+           }
+           return null;
+        }
 
-            if (!focusedView)
+        /// <summary>
+        /// On scroll event callback.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual void OnScrolling(object source, ScrollEventArgs args)
+        {
+            if (args == null) throw new ArgumentNullException(nameof(args));
+            if (!disposed && InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null)
             {
-                // If focusedView is null, find child which has previous data index
-                if (Children.Count > 0 && Adapter.Data.Count > 0)
-                {
-                    for (int i = 0; i < Children.Count; i++)
-                    {
-                        RecycleItem item = Children[i] as RecycleItem;
-                        if (item.DataIndex == prevFocusedDataIndex)
-                        {
-                            nextFocusedView = item;
-                            break;
-                        }
-                    }
-                }
+                //Console.WriteLine("[NUI] On Scrolling! {0} => {1}", ScrollPosition.Y, args.Position.Y);
+                InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
             }
-            else
+        }
+
+        /// <summary>
+        /// Dispose ItemsView and all children on it.
+        /// </summary>
+        /// <param name="type">Dispose type.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (disposed)
             {
-                // If this is not first focus, request next focus to LayoutManager
-                if (LayoutManager != null)
-                {
-                    nextFocusedView = LayoutManager.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
-                }
+                return;
             }
 
-            if (nextFocusedView != null)
+            if (type == DisposeTypes.Explicit)
             {
-                // Check next focused view is inside of visible area.
-                // If it is not, move scroll position to make it visible.
-                Position scrollPosition = ContentContainer.CurrentPosition;
-                float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
-
-                float left = nextFocusedView.Position.X;
-                float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
-                float top = nextFocusedView.Position.Y;
-                float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
-
-                float visibleRectangleLeft = -scrollPosition.X;
-                float visibleRectangleRight = -scrollPosition.X + Size.Width;
-                float visibleRectangleTop = -scrollPosition.Y;
-                float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
-
-                if (ScrollingDirection == Direction.Horizontal)
-                {
-                    if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
-                    {
-                        targetPosition = left;
-                    }
-                    else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
-                    {
-                        targetPosition = right - Size.Width;
-                    }
-                }
-                else
+                disposed = true;
+                // call the clear!
+                if (RecycleCache != null)
                 {
-                    if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
-                    {
-                        targetPosition = top;
-                    }
-                    else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
+                    foreach (RecyclerViewItem item in RecycleCache)
                     {
-                        targetPosition = bottom - Size.Height;
+                        //ContentContainer.Remove(item);
+                        Utility.Dispose(item);
                     }
+                    RecycleCache.Clear();
                 }
-
-                focusedView = nextFocusedView;
-                if ((nextFocusedView as RecycleItem) != null)
-                {
-                    prevFocusedDataIndex = (nextFocusedView as RecycleItem).DataIndex;
-                }
-
-                ScrollTo(targetPosition, true);
+                InternalItemsLayouter.Clear();
+                InternalItemsLayouter = null;
+                ItemsSource = null;
+                ItemTemplate = null;
+                if (InternalItemSource != null) InternalItemSource.Dispose();
+                //
             }
-            else
-            {
-                // If nextView is null, it means that we should move focus to outside of Control.
-                // Return FocusableView depending on direction.
-                switch (direction)
-                {
-                    case View.FocusDirection.Left:
-                        {
-                            nextFocusedView = LeftFocusableView;
-                            break;
-                        }
-                    case View.FocusDirection.Right:
-                        {
-                            nextFocusedView = RightFocusableView;
-                            break;
-                        }
-                    case View.FocusDirection.Up:
-                        {
-                            nextFocusedView = UpFocusableView;
-                            break;
-                        }
-                    case View.FocusDirection.Down:
-                        {
-                            nextFocusedView = DownFocusableView;
-                            break;
-                        }
-                }
 
-                if (nextFocusedView)
-                {
-                    focusedView = null;
-                }
-                else
-                {
-                    //If FocusableView doesn't exist, not move focus.
-                    nextFocusedView = focusedView;
-                }
-            }
+            base.Dispose(type);
+        }
 
-            return nextFocusedView;
+        private void OnItemRelayout(object sender, EventArgs e)
+        {
+            //FIXME: we need to skip the first relayout and only call size changed when real size change happen.
+            //InternalItemsLayouter.NotifyItemSizeChanged((sender as ViewItem));
+            //InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
+        }
+
+        private void DecorateItem(RecyclerViewItem item, int index, object context)
+        {
+            item.Index = index;
+            item.ParentItemsView = this;
+            item.Template = (ItemTemplate as DataTemplateSelector)?.SelectDataTemplate(InternalItemSource.GetItem(index), this) ?? ItemTemplate;
+            item.BindingContext = context;
+            item.Relayout += OnItemRelayout;
         }
     }
 }
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/SelectionChangedEventArgs.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/SelectionChangedEventArgs.cs
new file mode 100644 (file)
index 0000000..6041b22
--- /dev/null
@@ -0,0 +1,56 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// Selection changed event. this might be deprecated.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class SelectionChangedEventArgs : EventArgs
+    {
+        static readonly IReadOnlyList<object> selectEmpty = new List<object>(0);
+
+        /// <summary>
+        /// Previous selection list.
+        /// </summary>
+       [EditorBrowsable(EditorBrowsableState.Never)]
+        public IReadOnlyList<object> PreviousSelection { get; }
+
+        /// <summary>
+        /// Current selection list.    
+        ///  </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public IReadOnlyList<object> CurrentSelection { get; }
+
+
+        internal SelectionChangedEventArgs(object previousSelection, object currentSelection)
+        {
+            PreviousSelection = previousSelection != null ? new List<object>(1) { previousSelection } : selectEmpty;
+            CurrentSelection = currentSelection != null ? new List<object>(1) { currentSelection } : selectEmpty;
+        }
+
+        internal SelectionChangedEventArgs(IList<object> previousSelection, IList<object> currentSelection)
+        {
+            PreviousSelection = new List<object>(previousSelection ?? throw new ArgumentNullException(nameof(previousSelection)));
+            CurrentSelection = new List<object>(currentSelection ?? throw new ArgumentNullException(nameof(currentSelection)));
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/SelectionList.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/SelectionList.cs
new file mode 100644 (file)
index 0000000..c328fca
--- /dev/null
@@ -0,0 +1,157 @@
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace Tizen.NUI.Components
+{
+    // Used by the CollectionView to keep track of (and respond to changes in) the SelectedItems property
+    internal class SelectionList : IList<object>
+    {
+        static readonly IList<object> selectEmpty = new List<object>(0);
+        readonly CollectionView ColView;
+        readonly IList<object> internalList;
+        IList<object> shadowList;
+        bool externalChange;
+
+        public SelectionList(CollectionView colView, IList<object> items = null)
+        {
+            ColView = colView ?? throw new ArgumentNullException(nameof(colView));
+            internalList = items ?? new List<object>();
+            shadowList = Copy();
+
+            if (items is INotifyCollectionChanged incc)
+            {
+                incc.CollectionChanged += OnCollectionChanged;
+            }
+        }
+
+        public object this[int index] { get => internalList[index]; set => internalList[index] = value; }
+
+        public int Count => internalList.Count;
+
+        public bool IsReadOnly => false;
+
+        public void Add(object item)
+        {
+            externalChange = true;
+            internalList.Add(item);
+            externalChange = false;
+
+            ColView.SelectedItemsPropertyChanged(shadowList, internalList);
+            shadowList.Add(item);
+        }
+
+        public void Clear()
+        {
+            externalChange = true;
+            internalList.Clear();
+            externalChange = false;
+
+            ColView.SelectedItemsPropertyChanged(shadowList, selectEmpty);
+            shadowList.Clear();
+        }
+
+        public bool Contains(object item)
+        {
+            return internalList.Contains(item);
+        }
+
+        public void CopyTo(object[] array, int arrayIndex)
+        {
+            internalList.CopyTo(array, arrayIndex);
+        }
+
+        public IEnumerator<object> GetEnumerator()
+        {
+            return internalList.GetEnumerator();
+        }
+
+        public int IndexOf(object item)
+        {
+            return internalList.IndexOf(item);
+        }
+
+        public void Insert(int index, object item)
+        {
+            externalChange = true;
+            internalList.Insert(index, item);
+            externalChange = false;
+
+            ColView.SelectedItemsPropertyChanged(shadowList, internalList);
+            shadowList.Insert(index, item);
+        }
+
+        public bool Remove(object item)
+        {
+            externalChange = true;
+            var removed = internalList.Remove(item);
+            externalChange = false;
+
+            if (removed)
+            {
+                ColView.SelectedItemsPropertyChanged(shadowList, internalList);
+                shadowList.Remove(item);
+            }
+
+            return removed;
+        }
+
+        public void RemoveAt(int index)
+        {
+            externalChange = true;
+            internalList.RemoveAt(index);
+            externalChange = false;
+
+            ColView.SelectedItemsPropertyChanged(shadowList, internalList);
+            shadowList.RemoveAt(index);
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return internalList.GetEnumerator();
+        }
+
+        List<object> Copy()
+        {
+            var items = new List<object>();
+            for (int n = 0; n < internalList.Count; n++)
+            {
+                items.Add(internalList[n]);
+            }
+
+            return items;
+        }
+
+        void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+        {
+            if (externalChange)
+            {
+                // If this change was initiated by a renderer or direct manipulation of ColllectionView.SelectedItems,
+                // we don't need to send a selection change notification
+                return;
+            }
+
+            // This change is coming from a bound viewmodel property
+            // Emit a selection change notification, then bring the shadow copy up-to-date
+            ColView.SelectedItemsPropertyChanged(shadowList, internalList);
+            shadowList = Copy();
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Style/DefaultGridItemStyle.cs b/src/Tizen.NUI.Components/Style/DefaultGridItemStyle.cs
new file mode 100644 (file)
index 0000000..1149b28
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright(c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// DefaultGridItemStyle is a class which saves DefaultLinearItem's ux data.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class DefaultGridItemStyle : RecyclerViewItemStyle
+    {
+    
+        static DefaultGridItemStyle() { }
+
+        /// <summary>
+        /// Creates a new instance of a DefaultGridItemStyle.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        public DefaultGridItemStyle() : base()
+        {
+        }
+
+        /// <summary>
+        /// Creates a new instance of a DefaultGridItemStyle with style.
+        /// </summary>
+        /// <param name="style">Create DefaultGridItemStyle by style customized by user.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultGridItemStyle(DefaultGridItemStyle style) : base(style)
+        {
+        }
+
+
+        /// <summary>
+        /// Label Text's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TextLabelStyle Caption { get; set; } = new TextLabelStyle();
+
+        /// <summary>
+        /// Icon's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ImageViewStyle Image { get; set; } = new ImageViewStyle();
+
+        /// <summary>
+        /// Extra's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ViewStyle Badge { get; set; } = new ViewStyle();
+
+        /// <summary>
+        /// Style's clone function.
+        /// </summary>
+        /// <param name="bindableObject">The style that need to copy.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void CopyFrom(BindableObject bindableObject)
+        {
+            base.CopyFrom(bindableObject);
+
+            if (bindableObject is DefaultGridItemStyle RecyclerViewItemStyle)
+            {
+                Caption.CopyFrom(RecyclerViewItemStyle.Caption);
+                Image.CopyFrom(RecyclerViewItemStyle.Image);
+                Badge.CopyFrom(RecyclerViewItemStyle.Badge);
+                //Border.CopyFrom(RecyclerViewItemStyle.Border);
+            }
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Style/DefaultLinearItemStyle.cs b/src/Tizen.NUI.Components/Style/DefaultLinearItemStyle.cs
new file mode 100644 (file)
index 0000000..f990a7c
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright(c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// DefaultLinearItemStyle is a class which saves DefaultLinearItem's ux data.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class DefaultLinearItemStyle : RecyclerViewItemStyle
+    {
+        static DefaultLinearItemStyle() { }
+
+        /// <summary>
+        /// Creates a new instance of a DefaultLinearItemStyle.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        public DefaultLinearItemStyle() : base()
+        {
+        }
+
+        /// <summary>
+        /// Creates a new instance of a DefaultLinearItemStyle with style.
+        /// </summary>
+        /// <param name="style">Create DefaultLinearItemStyle by style customized by user.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultLinearItemStyle(DefaultLinearItemStyle style) : base(style)
+        {
+        }
+
+        /// <summary>
+        /// Label Text's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TextLabelStyle Label { get; set; } = new TextLabelStyle();
+
+        /// <summary>
+        /// Sublabel Text's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TextLabelStyle SubLabel { get; set; } = new TextLabelStyle();
+
+        /// <summary>
+        /// Icon's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ViewStyle Icon { get; set; } = new ViewStyle();
+
+        /// <summary>
+        /// Extra's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ViewStyle Extra { get; set; } = new ViewStyle();
+
+        /// <summary>
+        /// Seperator's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ViewStyle Seperator { get; set; } = new ViewStyle();
+
+        /// <summary>
+        /// Style's clone function.
+        /// </summary>
+        /// <param name="bindableObject">The style that need to copy.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void CopyFrom(BindableObject bindableObject)
+        {
+            base.CopyFrom(bindableObject);
+
+            if (bindableObject is DefaultLinearItemStyle RecyclerViewItemStyle)
+            {
+                Label.CopyFrom(RecyclerViewItemStyle.Label);
+                SubLabel.CopyFrom(RecyclerViewItemStyle.SubLabel);
+                Icon.CopyFrom(RecyclerViewItemStyle.Icon);
+                Extra.CopyFrom(RecyclerViewItemStyle.Extra);
+                Seperator.CopyFrom(RecyclerViewItemStyle.Seperator);
+            }
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Style/DefaultTitleItemStyle.cs b/src/Tizen.NUI.Components/Style/DefaultTitleItemStyle.cs
new file mode 100644 (file)
index 0000000..d68c2b9
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright(c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// DefaultTitleItemStyle is a class which saves DefaultLinearItem's ux data.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class DefaultTitleItemStyle : RecyclerViewItemStyle
+    {
+        static DefaultTitleItemStyle() { }
+
+        /// <summary>
+        /// Creates a new instance of a DefaultTitleItemStyle.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        public DefaultTitleItemStyle() : base()
+        {
+        }
+
+        /// <summary>
+        /// Creates a new instance of a DefaultTitleItemStyle with style.
+        /// </summary>
+        /// <param name="style">Create DefaultTitleItemStyle by style customized by user.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public DefaultTitleItemStyle(DefaultTitleItemStyle style) : base(style)
+        {
+        }
+
+        /// <summary>
+        /// Label Text's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TextLabelStyle Label { get; set; } = new TextLabelStyle();
+
+        /// <summary>
+        /// Icon's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ViewStyle Icon { get; set; } = new ViewStyle();
+
+        /// <summary>
+        /// Seperator's style.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ViewStyle Seperator { get; set; } = new ViewStyle();
+
+        /// <summary>
+        /// Style's clone function.
+        /// </summary>
+        /// <param name="bindableObject">The style that need to copy.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void CopyFrom(BindableObject bindableObject)
+        {
+            base.CopyFrom(bindableObject);
+
+            if (bindableObject is DefaultTitleItemStyle RecyclerViewItemStyle)
+            {
+                Label.CopyFrom(RecyclerViewItemStyle.Label);
+                Icon.CopyFrom(RecyclerViewItemStyle.Icon);
+                Seperator.CopyFrom(RecyclerViewItemStyle.Seperator);
+            }
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Style/RecyclerViewItemStyle.cs b/src/Tizen.NUI.Components/Style/RecyclerViewItemStyle.cs
new file mode 100644 (file)
index 0000000..8fa6311
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright(c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// RecyclerViewItemStyle is a class which saves RecyclerViewItem's ux data.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class RecyclerViewItemStyle : ControlStyle
+    {
+        /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty IsSelectableProperty = BindableProperty.Create(nameof(IsSelectable), typeof(bool?), typeof(RecyclerViewItemStyle), null, propertyChanged: (bindable, oldValue, newValue) =>
+        {
+            var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+            RecyclerViewItemStyle.isSelectable = (bool?)newValue;
+        },
+        defaultValueCreator: (bindable) =>
+        {
+            var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+            return RecyclerViewItemStyle.isSelectable;
+        });
+        /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty IsSelectedProperty = BindableProperty.Create(nameof(IsSelected), typeof(bool?), typeof(RecyclerViewItemStyle), null, propertyChanged: (bindable, oldValue, newValue) =>
+        {
+            var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+            RecyclerViewItemStyle.isSelected = (bool?)newValue;
+        },
+        defaultValueCreator: (bindable) =>
+        {
+            var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+            return RecyclerViewItemStyle.isSelected;
+        });
+        /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(nameof(IsEnabled), typeof(bool?), typeof(RecyclerViewItemStyle), null, propertyChanged: (bindable, oldValue, newValue) =>
+        {
+            var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+            RecyclerViewItemStyle.isEnabled = (bool?)newValue;
+        },
+        defaultValueCreator: (bindable) =>
+        {
+            var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+            return RecyclerViewItemStyle.isEnabled;
+        });
+
+        private bool? isSelectable;
+        private bool? isSelected;
+        private bool? isEnabled;
+
+        static RecyclerViewItemStyle() { }
+
+        /// <summary>
+        /// Creates a new instance of a RecyclerViewItemStyle.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        public RecyclerViewItemStyle() : base()
+        {
+        }
+
+        /// <summary>
+        /// Creates a new instance of a RecyclerViewItemStyle with style.
+        /// </summary>
+        /// <param name="style">Create RecyclerViewItemStyle by style customized by user.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecyclerViewItemStyle(RecyclerViewItemStyle style) : base(style)
+        {
+        }
+
+        /// <summary>
+        /// Flag to decide RecyclerViewItem can be selected or not.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public bool? IsSelectable
+        {
+            get => (bool?)GetValue(IsSelectableProperty);
+            set => SetValue(IsSelectableProperty, value);
+        }
+
+        /// <summary>
+        /// Flag to decide selected state in RecyclerViewItem.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public bool? IsSelected
+        {
+            get => (bool?)GetValue(IsSelectedProperty);
+            set => SetValue(IsSelectedProperty, value);
+        }
+
+        /// <summary>
+        /// Flag to decide RecyclerViewItem can be selected or not.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public bool? IsEnabled
+        {
+            get => (bool?)GetValue(IsEnabledProperty);
+            set => SetValue(IsEnabledProperty, value);
+        }
+
+        /// <summary>
+        /// Style's clone function.
+        /// </summary>
+        /// <param name="bindableObject">The style that need to copy.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void CopyFrom(BindableObject bindableObject)
+        {
+            base.CopyFrom(bindableObject);
+
+            /*
+            if (bindableObject is RecyclerViewItemStyle RecyclerViewItemStyle)
+            {
+                //
+            }
+            */
+        }
+    }
+}
index d8dcc42..9910996 100644 (file)
@@ -97,6 +97,11 @@ namespace Tizen.NUI.Components
             ["PaginationIndicatorImageUrlSelected"] = FrameworkInformation.ResourcePath + "nui_component_default_pagination_focus_dot.png",
             ["ScrollbarTrackColor"] = new Color(1, 1, 1, 0.15f),
             ["ScrollbarThumbColor"] = new Color(0.6f, 0.6f, 0.6f, 1.0f),
+            ["RecyclerViewItemBackgroundColorNormal"] = new Color(1, 1, 1, 1),
+            ["RecyclerViewItemBackgroundColorPressed"] = new Color(0.85f, 0.85f, 0.85f, 1),
+            ["RecyclerViewItemBackgroundColorDisabled"] = new Color(0.70f, 0.70f, 0.70f, 1),
+            ["RecyclerViewItemBackgroundColorSelected"] = new Color(0.701f, 0.898f, 0.937f, 1),
+            ["TitleBackgroundColorNormal"] = new Color(0.78f, 0.78f, 0.78f, 1),
         };
 
         public Theme Create() => Create(null);
@@ -372,6 +377,90 @@ namespace Tizen.NUI.Components
                 TrackPadding = 4
             });
 
+            theme.AddStyleWithoutClone("Tizen.NUI.Components.RecyclerViewItem", new RecyclerViewItemStyle()
+            {
+                BackgroundColor = new Selector<Color>()
+                {
+                    Normal = (Color)theme.Resources["RecyclerViewItemBackgroundColorNormal"],
+                    Pressed = (Color)theme.Resources["RecyclerViewItemBackgroundColorPressed"],
+                    Disabled = (Color)theme.Resources["RecyclerViewItemBackgroundColorDisabled"],
+                    Selected = (Color)theme.Resources["RecyclerViewItemBackgroundColorSelected"],
+                },
+            });
+            
+            theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultLinearItem", new DefaultLinearItemStyle()
+            {
+                SizeHeight = 130,
+                Padding = new Extents(20, 20, 5, 5),
+                BackgroundColor = new Selector<Color>()
+                {
+                    Normal = (Color)theme.Resources["RecyclerViewItemBackgroundColorNormal"],
+                    Pressed = (Color)theme.Resources["RecyclerViewItemBackgroundColorPressed"],
+                    Disabled = (Color)theme.Resources["RecyclerViewItemBackgroundColorDisabled"],
+                    Selected = (Color)theme.Resources["RecyclerViewItemBackgroundColorSelected"],
+                },
+                Label = new TextLabelStyle()
+                {
+                    PointSize = 10,
+                    Ellipsis = true,
+                },
+                SubLabel = new TextLabelStyle()
+                {
+                    PointSize = 6,
+                    Ellipsis = true,
+                },
+                Icon = new ViewStyle()
+                {
+                    Margin = new Extents(0, 20, 0, 0)
+                },
+                Extra = new ViewStyle()
+                {
+                    Margin = new Extents(20, 0, 0, 0)
+                },
+                Seperator = new ViewStyle()
+                {
+                    Margin = new Extents(5, 5, 0, 0),
+                    BackgroundColor = new Color(0.78f, 0.78f, 0.78f, 1),
+                },
+            });
+            theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultGridItem", new DefaultGridItemStyle()
+            {
+                Padding = new Extents(5, 5, 5, 5),
+                Caption = new TextLabelStyle()
+                {
+                    PointSize = 9,
+                    Ellipsis = true,
+                },
+                Badge = new ViewStyle()
+                {
+                    Margin = new Extents(5, 5, 5, 5),
+                },
+            });
+
+            theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultTitleItem", new DefaultTitleItemStyle()
+            {
+                SizeHeight = 90,
+                Padding = new Extents(10, 10, 5, 5),
+                BackgroundColor = new Selector<Color>()
+                {
+                    Normal = (Color)theme.Resources["TitleBackgroundColorNormal"],
+                },
+                Label = new TextLabelStyle()
+                {
+                    PointSize = 10,
+                    Ellipsis = true,
+                },
+                Icon = new ViewStyle()
+                {
+                    Margin = new Extents(10, 0, 0, 0)
+                },
+                Seperator = new ViewStyle()
+                {
+                    Margin = new Extents(0, 0, 0, 0),
+                    BackgroundColor = new Color(0.85f, 0.85f, 0.85f, 1),
+                },
+            });
+
             return theme;
         }
     }
index 4a83806..ddc52c1 100644 (file)
@@ -92,6 +92,11 @@ namespace Tizen.NUI.Components
             ["PaginationIndicatorImageUrlSelected"] = FrameworkInformation.ResourcePath + "nui_component_default_pagination_focus_dot.png",
             ["ScrollbarTrackColor"] = new Color(1, 1, 1, 0.15f),
             ["ScrollbarThumbColor"] = new Color(0.6f, 0.6f, 0.6f, 1.0f),
+            ["RecyclerViewItemBackgroundColorNormal"] = new Color(1, 1, 1, 1),
+            ["RecyclerViewItemBackgroundColorPressed"] = new Color(0.85f, 0.85f, 0.85f, 1),
+            ["RecyclerViewItemBackgroundColorDisabled"] = new Color(0.70f, 0.70f, 0.70f, 1),
+            ["RecyclerViewItemBackgroundColorSelected"] = new Color(0.701f, 0.898f, 0.937f, 1),
+            ["TitleBackgroundColorNormal"] = new Color(0.78f, 0.78f, 0.78f, 1),
         };
 
         public Theme Create() => Create(null);
@@ -366,6 +371,90 @@ namespace Tizen.NUI.Components
                 TrackPadding = 4
             });
 
+                        theme.AddStyleWithoutClone("Tizen.NUI.Components.RecyclerViewItem", new RecyclerViewItemStyle()
+            {
+                BackgroundColor = new Selector<Color>()
+                {
+                    Normal = (Color)theme.Resources["RecyclerViewItemBackgroundColorNormal"],
+                    Pressed = (Color)theme.Resources["RecyclerViewItemBackgroundColorPressed"],
+                    Disabled = (Color)theme.Resources["RecyclerViewItemBackgroundColorDisabled"],
+                    Selected = (Color)theme.Resources["RecyclerViewItemBackgroundColorSelected"],
+                },
+            });
+            
+            theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultLinearItem", new DefaultLinearItemStyle()
+            {
+                SizeHeight = 160,
+                Padding = new Extents(10, 10, 20, 20),
+                BackgroundColor = new Selector<Color>()
+                {
+                    Normal = (Color)theme.Resources["RecyclerViewItemBackgroundColorNormal"],
+                    Pressed = (Color)theme.Resources["RecyclerViewItemBackgroundColorPressed"],
+                    Disabled = (Color)theme.Resources["RecyclerViewItemBackgroundColorDisabled"],
+                    Selected = (Color)theme.Resources["RecyclerViewItemBackgroundColorSelected"],
+                },
+                Label = new TextLabelStyle()
+                {
+                    PointSize = 20,
+                    Ellipsis = true,
+                },
+                SubLabel = new TextLabelStyle()
+                {
+                    PointSize = 12,
+                    Ellipsis = true,
+                },
+                Icon = new ViewStyle()
+                {
+                    Margin = new Extents(0, 10, 0, 0)
+                },
+                Extra = new ViewStyle()
+                {
+                    Margin = new Extents(10, 0, 0, 0)
+                },
+                Seperator = new ViewStyle()
+                {
+                    Margin = new Extents(5, 5, 0, 0),
+                    BackgroundColor = new Color(0.78f, 0.78f, 0.78f, 1),
+                },
+            });
+            theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultGridItem", new DefaultGridItemStyle()
+            {
+                Padding = new Extents(5, 5, 5, 5),
+                Caption = new TextLabelStyle()
+                {
+                    PointSize = 9,
+                    Ellipsis = true,
+                },
+                Badge = new ViewStyle()
+                {
+                    Margin = new Extents(5, 5, 5, 5),
+                },
+            });
+
+            theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultTitleItem", new DefaultTitleItemStyle()
+            {
+                SizeHeight = 50,
+                Padding = new Extents(10, 10, 5, 5),
+                BackgroundColor = new Selector<Color>()
+                {
+                    Normal = (Color)theme.Resources["TitleBackgroundColorNormal"],
+                },
+                Label = new TextLabelStyle()
+                {
+                    PointSize = 15,
+                    Ellipsis = true,
+                },
+                Icon = new ViewStyle()
+                {
+                    Margin = new Extents(10, 0, 0, 0)
+                },
+                Seperator = new ViewStyle()
+                {
+                    Margin = new Extents(0, 0, 0, 0),
+                    BackgroundColor = new Color(0.85f, 0.85f, 0.85f, 1),
+                },
+            });
+
             return theme;
         }
     }
  */
 using System;
 using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
 using System.Collections.Generic;
 using System.ComponentModel;
 
-namespace Tizen.NUI.Components
+namespace Tizen.NUI.Wearable
 {
     /// <summary>
     /// [Draft] This class implements a grid box layout.
  */
 using System;
 using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
 using System.Collections.Generic;
 using System.ComponentModel;
 
-namespace Tizen.NUI.Components
+namespace Tizen.NUI.Wearable
 {
     /// <summary>
     /// [Draft] This class implements a linear box layout.
@@ -16,8 +16,9 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
+using Tizen.NUI.Components;
 
-namespace Tizen.NUI.Components
+namespace Tizen.NUI.Wearable
 {
     /// <summary>
     /// [Draft] Defalt adapter for RecyclerView.
@@ -14,8 +14,9 @@
  *
  */
 using System.ComponentModel;
+using Tizen.NUI.Components;
 
-namespace Tizen.NUI.Components
+namespace Tizen.NUI.Wearable
 {
     /// <summary>
     /// [Draft] This class provides a basic item for RecyclerView.
  *
  */
 using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
 using System.Collections.Generic;
 using System.ComponentModel;
 
-namespace Tizen.NUI.Components
+namespace Tizen.NUI.Wearable
 {
     /// <summary>
     /// [Draft] Defalt layout manager for RecyclerView.
diff --git a/src/Tizen.NUI.Wearable/src/public/RecyclerView/RecyclerView.cs b/src/Tizen.NUI.Wearable/src/public/RecyclerView/RecyclerView.cs
new file mode 100755 (executable)
index 0000000..903d9d3
--- /dev/null
@@ -0,0 +1,372 @@
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Wearable
+{
+    /// <summary>
+    /// [Draft] This class provides a View that can recycle items to improve performance.
+    /// </summary>
+    /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class RecyclerView : ScrollableBase
+    {
+        private RecycleAdapter adapter;
+        private RecycleLayoutManager layoutManager;
+        private int totalItemCount = 15;
+        private List<PropertyNotification> notifications = new List<PropertyNotification>();
+
+        public RecyclerView() : base()
+        {
+            Initialize(new RecycleAdapter(), new RecycleLayoutManager());
+        }
+
+        /// <summary>
+        /// Default constructor.
+        /// </summary>
+        /// <param name="adapter">Recycle adapter of RecyclerView.</param>
+        /// <param name="layoutManager">Recycle layoutManager of RecyclerView.</param>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecyclerView(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
+        {
+            Initialize(adapter, layoutManager);
+        }
+
+        private void Initialize(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
+        {
+            FocusGroup = true;
+            SetKeyboardNavigationSupport(true);
+            Scrolling += OnScrolling;
+
+            this.adapter = adapter;
+            this.adapter.OnDataChanged += OnAdapterDataChanged;
+
+            this.layoutManager = layoutManager;
+            this.layoutManager.Container = ContentContainer;
+            this.layoutManager.ItemSize = this.adapter.CreateRecycleItem().Size;
+            this.layoutManager.DataCount = this.adapter.Data.Count;
+
+            InitializeItems();
+        }
+
+        private void OnItemSizeChanged(object source, PropertyNotification.NotifyEventArgs args)
+        {
+            layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
+        }
+
+        public int TotalItemCount
+        {
+            get
+            {
+                return totalItemCount;
+            }
+            set
+            {
+                totalItemCount = value;
+                InitializeItems();
+            }
+        }
+
+        private void InitializeItems()
+        {
+            for (int i = Children.Count - 1; i > -1; i--)
+            {
+                Children[i].Unparent();
+                notifications[i].Notified -= OnItemSizeChanged;
+                notifications.RemoveAt(i);
+            }
+
+            for (int i = 0; i < totalItemCount; i++)
+            {
+                RecycleItem item = adapter.CreateRecycleItem();
+                item.DataIndex = i;
+                item.Name = "[" + i + "] recycle";
+
+                if (i < adapter.Data.Count)
+                {
+                    adapter.BindData(item);
+                }
+                Add(item);
+
+                PropertyNotification noti = item.AddPropertyNotification("size", PropertyCondition.Step(0.1f));
+                noti.Notified += OnItemSizeChanged;
+                notifications.Add(noti);
+            }
+
+            layoutManager.Layout(0.0f);
+
+            if (ScrollingDirection == Direction.Horizontal)
+            {
+                ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
+            }
+            else
+            {
+                ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
+            }
+        }
+
+
+        public new Direction ScrollingDirection
+        {
+            get
+            {
+                return base.ScrollingDirection;
+            }
+            set
+            {
+                base.ScrollingDirection = value;
+
+                if (ScrollingDirection == Direction.Horizontal)
+                {
+                    ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
+                }
+                else
+                {
+                    ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Recycler adpater.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecycleAdapter Adapter
+        {
+            get
+            {
+                return adapter;
+            }
+            set
+            {
+                if (adapter != null)
+                {
+                    adapter.OnDataChanged -= OnAdapterDataChanged;
+                }
+
+                adapter = value;
+                adapter.OnDataChanged += OnAdapterDataChanged;
+                layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
+                layoutManager.DataCount = adapter.Data.Count;
+                InitializeItems();
+            }
+        }
+
+        /// <summary>
+        /// Recycler layoutManager.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecycleLayoutManager LayoutManager
+        {
+            get
+            {
+                return layoutManager;
+            }
+            set
+            {
+                layoutManager = value;
+                layoutManager.Container = ContentContainer;
+                layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
+                layoutManager.DataCount = adapter.Data.Count;
+                InitializeItems();
+            }
+        }
+
+        private void OnScrolling(object source, ScrollEventArgs args)
+        {
+            layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
+            List<RecycleItem> recycledItemList = layoutManager.Recycle(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
+            BindData(recycledItemList);
+        }
+
+        private void OnAdapterDataChanged(object source, EventArgs args)
+        {
+            List<RecycleItem> changedData = new List<RecycleItem>();
+
+            foreach (RecycleItem item in Children)
+            {
+                changedData.Add(item);
+            }
+
+            BindData(changedData);
+        }
+
+        private void BindData(List<RecycleItem> changedData)
+        {
+            foreach (RecycleItem item in changedData)
+            {
+                if (item.DataIndex > -1 && item.DataIndex < adapter.Data.Count)
+                {
+                    item.Show();
+                    item.Name = "[" + item.DataIndex + "]";
+                    adapter.BindData(item);
+                }
+                else
+                {
+                    item.Hide();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Adjust scrolling position by own scrolling rules.
+        /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
+        /// </summary>
+        /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
+        /// <returns>Adjusted scroll destination</returns>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override float AdjustTargetPositionOfScrollAnimation(float position)
+        {
+            // Destination is depending on implementation of layout manager.
+            // Get destination from layout manager.
+            return layoutManager.CalculateCandidateScrollPosition(position);
+        }
+
+        private View focusedView;
+        private int prevFocusedDataIndex = 0;
+
+        public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            View nextFocusedView = null;
+
+            if (!focusedView)
+            {
+                // If focusedView is null, find child which has previous data index
+                if (Children.Count > 0 && Adapter.Data.Count > 0)
+                {
+                    for (int i = 0; i < Children.Count; i++)
+                    {
+                        RecycleItem item = Children[i] as RecycleItem;
+                        if (item.DataIndex == prevFocusedDataIndex)
+                        {
+                            nextFocusedView = item;
+                            break;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                // If this is not first focus, request next focus to LayoutManager
+                if (LayoutManager != null)
+                {
+                    nextFocusedView = LayoutManager.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
+                }
+            }
+
+            if (nextFocusedView != null)
+            {
+                // Check next focused view is inside of visible area.
+                // If it is not, move scroll position to make it visible.
+                Position scrollPosition = ContentContainer.CurrentPosition;
+                float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
+
+                float left = nextFocusedView.Position.X;
+                float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
+                float top = nextFocusedView.Position.Y;
+                float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
+
+                float visibleRectangleLeft = -scrollPosition.X;
+                float visibleRectangleRight = -scrollPosition.X + Size.Width;
+                float visibleRectangleTop = -scrollPosition.Y;
+                float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
+
+                if (ScrollingDirection == Direction.Horizontal)
+                {
+                    if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
+                    {
+                        targetPosition = left;
+                    }
+                    else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
+                    {
+                        targetPosition = right - Size.Width;
+                    }
+                }
+                else
+                {
+                    if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
+                    {
+                        targetPosition = top;
+                    }
+                    else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
+                    {
+                        targetPosition = bottom - Size.Height;
+                    }
+                }
+
+                focusedView = nextFocusedView;
+                if ((nextFocusedView as RecycleItem) != null)
+                {
+                    prevFocusedDataIndex = (nextFocusedView as RecycleItem).DataIndex;
+                }
+
+                ScrollTo(targetPosition, true);
+            }
+            else
+            {
+                // If nextView is null, it means that we should move focus to outside of Control.
+                // Return FocusableView depending on direction.
+                switch (direction)
+                {
+                    case View.FocusDirection.Left:
+                        {
+                            nextFocusedView = LeftFocusableView;
+                            break;
+                        }
+                    case View.FocusDirection.Right:
+                        {
+                            nextFocusedView = RightFocusableView;
+                            break;
+                        }
+                    case View.FocusDirection.Up:
+                        {
+                            nextFocusedView = UpFocusableView;
+                            break;
+                        }
+                    case View.FocusDirection.Down:
+                        {
+                            nextFocusedView = DownFocusableView;
+                            break;
+                        }
+                }
+
+                if (nextFocusedView)
+                {
+                    focusedView = null;
+                }
+                else
+                {
+                    //If FocusableView doesn't exist, not move focus.
+                    nextFocusedView = focusedView;
+                }
+            }
+
+            return nextFocusedView;
+        }
+    }
+}
diff --git a/src/Tizen.NUI/src/internal/XamlBinding/Internals/IDataTemplate.cs b/src/Tizen.NUI/src/internal/XamlBinding/Internals/IDataTemplate.cs
deleted file mode 100755 (executable)
index 168ed7e..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-using System;
-using System.ComponentModel;
-
-namespace Tizen.NUI.Binding.Internals
-{
-    internal interface IDataTemplate
-    {
-        Func<object> LoadTemplate { get; set; }
-    }
-}
@@ -1,26 +1,45 @@
 using System;
+using System.ComponentModel;
 using System.Collections.Generic;
 
 namespace Tizen.NUI.Binding
 {
-    internal class DataTemplate : ElementTemplate
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class DataTemplate : ElementTemplate
     {
+        /// <summary>
+        /// Base constructor.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public DataTemplate()
         {
         }
 
+        /// <summary>
+        /// Base constructor with specific Type.
+        /// </summary>
+        /// <param name="Type">The Type of content.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public DataTemplate(Type type) : base(type)
         {
         }
 
+        /// <summary>
+        /// Base constructor with loadTemplate function.
+        /// </summary>
+        /// <param name="loadTemplate">The function of loading templated object.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public DataTemplate(Func<object> loadTemplate) : base(loadTemplate)
         {
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public IDictionary<BindableProperty, BindingBase> Bindings { get; } = new Dictionary<BindableProperty, BindingBase>();
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public IDictionary<BindableProperty, object> Values { get; } = new Dictionary<BindableProperty, object>();
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public void SetBinding(BindableProperty property, BindingBase binding)
         {
             if (property == null)
@@ -32,6 +51,7 @@ namespace Tizen.NUI.Binding
             Bindings[property] = binding;
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public void SetValue(BindableProperty property, object value)
         {
             if (property == null)
@@ -3,8 +3,9 @@
 namespace Tizen.NUI.Binding
 {
     [EditorBrowsable(EditorBrowsableState.Never)]
-    internal static class DataTemplateExtensions
+    public static class DataTemplateExtensions
     {
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public static DataTemplate SelectDataTemplate(this DataTemplate self, object item, BindableObject container)
         {
             var selector = self as DataTemplateSelector;
@@ -14,6 +15,7 @@ namespace Tizen.NUI.Binding
             return selector.SelectTemplate(item, container);
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public static object CreateContent(this DataTemplate self, object item, BindableObject container)
         {
             return self.SelectDataTemplate(item, container).CreateContent();
@@ -1,12 +1,15 @@
 using System;
+using System.ComponentModel;
 using System.Collections.Generic;
 
 namespace Tizen.NUI.Binding
 {
-    internal abstract class DataTemplateSelector : DataTemplate
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public abstract class DataTemplateSelector : DataTemplate
     {
         Dictionary<Type, DataTemplate> _dataTemplates = new Dictionary<Type, DataTemplate>();
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public DataTemplate SelectTemplate(object item, BindableObject container)
         {
             DataTemplate dataTemplate = null;
@@ -23,6 +26,7 @@ namespace Tizen.NUI.Binding
             return dataTemplate;
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         protected abstract DataTemplate OnSelectTemplate(object item, BindableObject container);
     }
 }
@@ -9,7 +9,7 @@ namespace Tizen.NUI.Binding
     /// Base class for DataTemplate and ControlTemplate classes.
     /// </summary>
     [EditorBrowsable(EditorBrowsableState.Never)]
-    internal class ElementTemplate : IElement, IDataTemplate
+    public class ElementTemplate : IElement, IDataTemplate
     {
         List<Action<object, ResourcesChangedEventArgs>> _changeHandlers;
         Element _parent;
@@ -80,6 +80,7 @@ namespace Tizen.NUI.Binding
         /// Used by the XAML infrastructure to load data templates and set up the content of the resulting UI.
         /// </summary>
         /// <returns></returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public object CreateContent()
         {
             if (LoadTemplate == null)
diff --git a/src/Tizen.NUI/src/public/Template/IDataTemplate.cs b/src/Tizen.NUI/src/public/Template/IDataTemplate.cs
new file mode 100755 (executable)
index 0000000..99ba11f
--- /dev/null
@@ -0,0 +1,11 @@
+using System;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Binding
+{
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public interface IDataTemplate
+    {
+        Func<object> LoadTemplate { get; set; }
+    }
+}
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewGridSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewGridSample.cs
new file mode 100755 (executable)
index 0000000..36b2992
--- /dev/null
@@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Samples
+{
+    public class CollectionViewGridSample : IExample
+    {
+        CollectionView colView;
+        int itemCount = 500;
+        int selectedCount;
+        ItemSelectionMode selMode;
+
+        public void Activate()
+        {
+            Window window = NUIApplication.GetDefaultWindow();
+
+            var myViewModelSource = new GalleryViewModel(itemCount);
+            selMode = ItemSelectionMode.MultipleSelections;
+            DefaultTitleItem myTitle = new DefaultTitleItem();
+            myTitle.Text = "Grid Sample Count["+itemCount+"] Selected["+selectedCount+"]";
+            //Set Width Specification as MatchParent to fit the Item width with parent View.
+            myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
+
+            colView = new CollectionView()
+            {
+                ItemsSource = myViewModelSource,
+                ItemsLayouter = new GridLayouter(),
+                ItemTemplate = new DataTemplate(() =>
+                {
+                    DefaultGridItem item = new DefaultGridItem();
+                    item.WidthSpecification = 180;
+                    item.HeightSpecification = 240;
+                    //Decorate Label
+                    item.Caption.SetBinding(TextLabel.TextProperty, "ViewLabel");
+                    item.Caption.HorizontalAlignment = HorizontalAlignment.Center;
+                    //Decorate Image
+                    item.Image.SetBinding(ImageView.ResourceUrlProperty, "ImageUrl");
+                    item.Image.WidthSpecification = 170;
+                    item.Image.HeightSpecification = 170;
+                    //Decorate Badge checkbox.
+                    //[NOTE] This is sample of CheckBox usage in CollectionView.
+                    // Checkbox change their selection by IsSelectedProperty bindings with
+                    // SelectionChanged event with MulitpleSelections ItemSelectionMode of CollectionView.
+                    item.Badge = new CheckBox();
+                    //FIXME : SetBinding in RadioButton crashed as Sensitive Property is disposed.
+                    //item.Badge.SetBinding(CheckBox.IsSelectedProperty, "Selected");
+                    item.Badge.WidthSpecification = 30;
+                    item.Badge.HeightSpecification = 30;
+
+                    return item;
+                }),
+                Header = myTitle,
+                ScrollingDirection = ScrollableBase.Direction.Vertical,
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                HeightSpecification = LayoutParamPolicies.MatchParent,
+                               SelectionMode = selMode
+            };
+            colView.SelectionChanged += SelectionEvt;
+
+            window.Add(colView);
+        }
+
+        public void SelectionEvt(object sender, SelectionChangedEventArgs ev)
+        {
+            List<object> oldSel = new List<object>(ev.PreviousSelection);
+            List<object> newSel = new List<object>(ev.CurrentSelection);
+
+            foreach (object item in oldSel)
+            {
+                if (item != null && item is Gallery)
+                {
+                    Gallery galItem = (Gallery)item;
+                    if (!(newSel.Contains(item)))
+                    {
+                        galItem.Selected = false;
+                        Tizen.Log.Debug("Unselected: {0}", galItem.ViewLabel);
+                        selectedCount--;
+                    }
+                }
+                else continue;
+            }
+            foreach (object item in newSel)
+            {
+                if (item != null && item is Gallery)
+                {
+                    Gallery galItem = (Gallery)item;
+                    if (!(oldSel.Contains(item)))
+                    {
+                        galItem.Selected = true;
+                        Tizen.Log.Debug("Selected: {0}", galItem.ViewLabel);
+                        selectedCount++;
+                    }
+                }
+                else continue;
+            }
+            if (colView.Header != null && colView.Header is DefaultTitleItem)
+            {
+                DefaultTitleItem title = (DefaultTitleItem)colView.Header;
+                title.Text = "Grid Sample Count["+itemCount+"] Selected["+selectedCount+"]";
+            }
+        }
+
+        public void Deactivate()
+        {
+            if (colView != null)
+            {
+                colView.Dispose();
+            }
+        }
+    }
+}
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewLinearSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewLinearSample.cs
new file mode 100755 (executable)
index 0000000..693cf1e
--- /dev/null
@@ -0,0 +1,112 @@
+using System.Collections.Generic;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.Binding;
+using System.ComponentModel;
+using System;
+
+namespace Tizen.NUI.Samples
+{
+    public class CollectionViewLinearSample : IExample
+    {
+        CollectionView colView;
+        int itemCount = 500;
+        string selectedItem;
+        ItemSelectionMode selMode;
+
+        public void Activate()
+        {
+            Window window = NUIApplication.GetDefaultWindow();
+
+            var myViewModelSource = new GalleryViewModel(itemCount);
+            selMode = ItemSelectionMode.SingleSelection;
+            DefaultTitleItem myTitle = new DefaultTitleItem();
+            myTitle.Text = "Linear Sample Count["+itemCount+"]";
+            //Set Width Specification as MatchParent to fit the Item width with parent View.
+            myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
+
+            colView = new CollectionView()
+            {
+                ItemsSource = myViewModelSource,
+                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;
+                    //Decorate Label
+                    item.Label.SetBinding(TextLabel.TextProperty, "ViewLabel");
+                    item.Label.HorizontalAlignment = HorizontalAlignment.Begin;
+                    //Decorate Icon
+                    item.Icon.SetBinding(ImageView.ResourceUrlProperty, "ImageUrl");
+                    item.Icon.WidthSpecification = 80;
+                    item.Icon.HeightSpecification = 80;
+                    //Decorate Extra RadioButton.
+                    //[NOTE] This is sample of RadioButton usage in CollectionView.
+                    // RadioButton change their selection by IsSelectedProperty bindings with
+                    // SelectionChanged event with SingleSelection ItemSelectionMode of CollectionView.
+                    // be aware of there are no RadioButtonGroup. 
+                    item.Extra = new RadioButton();
+                    //FIXME : SetBinding in RadioButton crashed as Sensitive Property is disposed.
+                    //item.Extra.SetBinding(RadioButton.IsSelectedProperty, "Selected");
+                    item.Extra.WidthSpecification = 80;
+                    item.Extra.HeightSpecification = 80;
+                    */
+
+                    return item;
+                }),
+                Header = myTitle,
+                ScrollingDirection = ScrollableBase.Direction.Vertical,
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                HeightSpecification = LayoutParamPolicies.MatchParent,
+                               SelectionMode = selMode
+            };
+            colView.SelectionChanged += SelectionEvt;
+
+            window.Add(colView);
+
+        }
+
+        public void SelectionEvt(object sender, SelectionChangedEventArgs ev)
+        {
+            //Tizen.Log.Debug("NUI", "LSH :: SelectionEvt called");
+
+            //SingleSelection Only have 1 or nil object in the list.
+            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);
+            }
+            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 (colView.Header != null && colView.Header is DefaultTitleItem)
+            {
+                DefaultTitleItem title = (DefaultTitleItem)colView.Header;
+                title.Text = "Linear Sample Count[" + itemCount + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]");
+            }
+        }
+        public void Deactivate()
+        {
+            if (colView != null)
+            {
+                colView.Dispose();
+            }
+        }
+    }
+}
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Gallery.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Gallery.cs
new file mode 100644 (file)
index 0000000..4a08d75
--- /dev/null
@@ -0,0 +1,208 @@
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.Binding;
+
+
+
+class Gallery : INotifyPropertyChanged
+{
+    string sourceDir = Tizen.NUI.Samples.CommonResource.GetDaliResourcePath()+"ItemViewDemo/gallery/gallery-medium-";
+    private int index;
+    private string name;
+    private bool selected;
+
+    public event PropertyChangedEventHandler PropertyChanged;
+
+    private void OnPropertyyChanged(string propertyName)
+    {
+
+        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+    }
+
+    public Gallery(int galleryIndex, string galleryName)
+    {
+        index = galleryIndex;
+        name = galleryName;
+    }
+
+    public string Name {
+        get
+        {
+            return name;
+        }
+        set
+        {
+            name = value;
+            OnPropertyyChanged("Name");
+            OnPropertyyChanged("ViewLabel");
+        }
+    }
+    public string ViewLabel
+    {
+        get
+        {
+            return "[" + index + "] : " + name;
+        }
+    }
+
+    public string ImageUrl
+    {
+        get
+        {
+            return sourceDir+(index%20)+".jpg";
+        }
+    }
+
+    public bool Selected {
+        get
+        {
+            return selected;
+        }
+        set
+        {
+            selected = value;
+            OnPropertyyChanged("Selected");
+        }
+    }
+}
+
+class Album : ObservableCollection<Gallery>
+{
+    private int index;
+    private string name;
+    private DateTime date;
+
+    public Album(int albumIndex, string albumName, DateTime albumDate)
+    {
+        index = albumIndex;
+        name = albumName;
+        date = albumDate;
+    }
+
+    public string Title
+    {
+        get
+        {
+            return "[" + index + "] " + name;
+        }
+    }
+
+    public string Date
+    {
+        get
+        {
+            return date.ToLongDateString();
+        }
+    }
+}
+
+class GalleryViewModel : ObservableCollection<Gallery>
+{
+    string[] namePool = {
+        "Cat",
+        "Boy",
+        "Arm muscle",
+        "Girl",
+        "House",
+        "Cafe",
+        "Statue",
+        "Sea",
+        "hosepipe",
+        "Police",
+        "Rainbow",
+        "Icicle",
+        "Tower with the Moon",
+        "Giraffe",
+        "Camel",
+        "Zebra",
+        "Red Light",
+        "Banana",
+        "Lion",
+        "Espresso",
+    };
+    public GalleryViewModel(int count)
+    {
+        CreateData(this, count);
+    }
+
+    public ObservableCollection<Gallery> CreateData(ObservableCollection<Gallery> result , int count)
+    {
+        for (int i = 0; i < count; i++)
+        {
+            result.Add(new Gallery(i, namePool[i%20]));
+        }
+        return result;
+    }
+}
+
+class AlbumViewModel : ObservableCollection<Album>
+{
+    string[] namePool = {
+        "Cat",
+        "Boy",
+        "Arm muscle",
+        "Girl",
+        "House",
+        "Cafe",
+        "Statue",
+        "Sea",
+        "hosepipe",
+        "Police",
+        "Rainbow",
+        "Icicle",
+        "Tower with the Moon",
+        "Giraffe",
+        "Camel",
+        "Zebra",
+        "Red Light",
+        "Banana",
+        "Lion",
+        "Espresso",
+    };
+
+    (string name, DateTime date)[] titlePool = {
+        ("House Move", new DateTime(2021, 2, 26)),
+        ("Covid 19", new DateTime(2020, 1, 20)),
+        ("Porto Trip", new DateTime(2019, 11, 23)),
+        ("Granada Trip", new DateTime(2019, 11, 20)),
+        ("Barcelona Trip", new DateTime(2019, 11, 17)),
+        ("Developer's Day", new DateTime(2019, 11, 16)),
+        ("Tokyo Trip", new DateTime(2019, 7, 5)),
+        ("Otaru Trip", new DateTime(2019, 3, 2)),
+        ("Sapporo Trip", new DateTime(2019, 2, 28)),
+        ("Hakodate Trip", new DateTime(2019, 2, 26)),
+        ("Friend's Wedding", new DateTime(2018, 11, 23)),
+        ("Grandpa Birthday", new DateTime(2018, 9, 14)),
+        ("Family Jeju Trip", new DateTime(2018, 7, 15)),
+        ("HongKong Trip", new DateTime(2018, 3, 30)),
+        ("Mom's Birthday", new DateTime(2017, 12, 21)),
+        ("Buy new Car", new DateTime(2017, 10, 18)),
+        ("Graduation", new DateTime(2017, 6, 30)),
+    };
+
+
+    public AlbumViewModel()
+    {
+        CreateData(this);
+    }
+
+    public ObservableCollection<Album> CreateData(ObservableCollection<Album> result)
+    {
+        for (int i = 0; i < titlePool.Length; i++)
+        {
+            (string name, DateTime date) = titlePool[i];
+            Album cur = new Album(i, name, date);
+            for (int j = 0; j < 20; j++)
+            {
+                cur.Add(new Gallery(j, namePool[j]));
+            }
+            result.Add(cur);
+        }
+        return result;
+    }
+
+}
\ No newline at end of file
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewGridGroupSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewGridGroupSample.cs
new file mode 100644 (file)
index 0000000..5aa0b9c
--- /dev/null
@@ -0,0 +1,128 @@
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Samples
+{
+    public class CollectionViewGridGroupSample : IExample
+    {
+        CollectionView colView;
+        int selectedCount;
+        ItemSelectionMode selMode;
+        ObservableCollection<Album> groupSource;
+
+        public void Activate()
+        {
+            Window window = NUIApplication.GetDefaultWindow();
+
+            groupSource = new AlbumViewModel();
+            selMode = ItemSelectionMode.MultipleSelections;
+            DefaultTitleItem myTitle = new DefaultTitleItem();
+            myTitle.Text = "Grid Sample Count["+ groupSource.Count+"] Selected["+selectedCount+"]";
+            //Set Width Specification as MatchParent to fit the Item width with parent View.
+            myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
+
+            colView = new CollectionView()
+            {
+                ItemsSource = groupSource,
+                ItemsLayouter = new GridLayouter(),
+                ItemTemplate = new DataTemplate(() =>
+                {
+                    DefaultGridItem item = new DefaultGridItem();
+                    item.WidthSpecification = 180;
+                    item.HeightSpecification = 240;
+                    //Decorate Label
+                    item.Caption.SetBinding(TextLabel.TextProperty, "ViewLabel");
+                    item.Caption.HorizontalAlignment = HorizontalAlignment.Center;
+                    //Decorate Image
+                    item.Image.SetBinding(ImageView.ResourceUrlProperty, "ImageUrl");
+                    item.Image.WidthSpecification = 170;
+                    item.Image.HeightSpecification = 170;
+                    //Decorate Badge checkbox.
+                    //[NOTE] This is sample of CheckBox usage in CollectionView.
+                    // Checkbox change their selection by IsSelectedProperty bindings with
+                    // SelectionChanged event with MulitpleSelections ItemSelectionMode of CollectionView.
+                    item.Badge = new CheckBox();
+                    //FIXME : SetBinding in RadioButton crashed as Sensitive Property is disposed.
+                    //item.Badge.SetBinding(CheckBox.IsSelectedProperty, "Selected");
+                    item.Badge.WidthSpecification = 30;
+                    item.Badge.HeightSpecification = 30;
+
+                    return item;
+                }),
+                GroupHeaderTemplate = new DataTemplate(() =>
+                {
+                    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 group;
+                }),
+                Header = myTitle,
+                IsGrouped = true,
+                ScrollingDirection = ScrollableBase.Direction.Vertical,
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                HeightSpecification = LayoutParamPolicies.MatchParent,
+                               SelectionMode = selMode
+            };
+            colView.SelectionChanged += SelectionEvt;
+
+            window.Add(colView);
+        }
+
+        public void SelectionEvt(object sender, SelectionChangedEventArgs ev)
+        {
+            List<object> oldSel = new List<object>(ev.PreviousSelection);
+            List<object> newSel = new List<object>(ev.CurrentSelection);
+
+            foreach (object item in oldSel)
+            {
+                if (item != null && item is Gallery)
+                {
+                    Gallery galItem = (Gallery)item;
+                    if (!(newSel.Contains(item)))
+                    {
+                        galItem.Selected = false;
+                        Tizen.Log.Debug("Unselected: {0}", galItem.ViewLabel);
+                        selectedCount--;
+                    }
+                }
+                else continue;
+            }
+            foreach (object item in newSel)
+            {
+                if (item != null && item is Gallery)
+                {
+                    Gallery galItem = (Gallery)item;
+                    if (!(oldSel.Contains(item)))
+                    {
+                        galItem.Selected = true;
+                        Tizen.Log.Debug("Selected: {0}", galItem.ViewLabel);
+                        selectedCount++;
+                    }
+                }
+                else continue;
+            }
+            if (colView.Header != null && colView.Header is DefaultTitleItem)
+            {
+                DefaultTitleItem title = (DefaultTitleItem)colView.Header;
+                title.Text = "Grid Sample Count["+ groupSource.Count + "] Selected["+selectedCount+"]";
+            }
+        }
+
+        public void Deactivate()
+        {
+            if (colView != null)
+            {
+                colView.Dispose();
+            }
+        }
+    }
+}
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewLinearGroupSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewLinearGroupSample.cs
new file mode 100644 (file)
index 0000000..f8a759b
--- /dev/null
@@ -0,0 +1,131 @@
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Samples
+{
+    public class CollectionViewLinearGroupSample : IExample
+    {
+        CollectionView colView;
+        string selectedItem;
+        ItemSelectionMode selMode;
+        ObservableCollection<Album> groupSource;
+
+        public void Activate()
+        {
+            Window window = NUIApplication.GetDefaultWindow();
+
+            groupSource = new AlbumViewModel();
+            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+"]";
+            //Set Width Specification as MatchParent to fit the Item width with parent View.
+            myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
+
+            colView = new CollectionView()
+            {
+                ItemsSource = groupSource,
+                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;
+                    //Decorate Label
+                    item.Label.SetBinding(TextLabel.TextProperty, "ViewLabel");
+                    item.Label.HorizontalAlignment = HorizontalAlignment.Begin;
+                    //Decorate Icon
+                    item.Icon.SetBinding(ImageView.ResourceUrlProperty, "ImageUrl");
+                    item.Icon.WidthSpecification = 80;
+                    item.Icon.HeightSpecification = 80;
+                    //Decorate Extra RadioButton.
+                    //[NOTE] This is sample of RadioButton usage in CollectionView.
+                    // RadioButton change their selection by IsSelectedProperty bindings with
+                    // SelectionChanged event with SingleSelection ItemSelectionMode of CollectionView.
+                    // be aware of there are no RadioButtonGroup. 
+                    item.Extra = new RadioButton();
+                    //FIXME : SetBinding in RadioButton crashed as Sensitive Property is disposed.
+                    //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;
+                }),
+                Header = myTitle,
+                IsGrouped = true,
+                ScrollingDirection = ScrollableBase.Direction.Vertical,
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                HeightSpecification = LayoutParamPolicies.MatchParent,
+                               SelectionMode = selMode
+            };
+            colView.SelectionChanged += SelectionEvt;
+
+            window.Add(colView);
+
+        }
+
+        public void SelectionEvt(object sender, SelectionChangedEventArgs ev)
+        {
+            //Tizen.Log.Debug("NUI", "LSH :: SelectionEvt called");
+
+            //SingleSelection Only have 1 or nil object in the list.
+            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);
+            }
+            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 (colView.Header != null && colView.Header is DefaultTitleItem)
+            {
+                DefaultTitleItem title = (DefaultTitleItem)colView.Header;
+                title.Text = "Linear Sample Count[" + groupSource + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]");
+            }
+        }
+        public void Deactivate()
+        {
+            if (colView != null)
+            {
+                colView.Dispose();
+            }
+        }
+    }
+}