Grouped ListView and TileView

You can create grouped ListView with GroupedList<TItem> (group items does not exists and will be automatically created) or LinearGroupedList<TItem> (group items already exists in DataSource).

Grouped ListView

public class GroupedItem
{
        public string Name;
        public bool IsGroup = false;
        public bool IsEmpty = false;
}

public class GroupedView : ListViewCustom<GroupedListViewComponent, GroupedItem>
{
        // GroupedData used to add and remove items instead of the DataSource.
        public GroupedList<GroupedItem> GroupedData = new GroupedList<GroupedItem>((groups, item) =>
        {
                var name = item.Name.Length > 0 ? item.Name[0].ToString() : string.Empty;

                foreach (var group in groups)
                {
                        if (group.Name == name)
                        {
                                return group;
                        }
                }

                return new GroupedListGroup() { Name = name, IsGroup = true, };
        });

        bool isGroupedViewInited;

        public override void Init()
        {
                if (isGroupedViewInited)
                {
                        return;
                }

                isGroupedViewInited = true;

                base.Init();

                GroupedData.GroupComparison = (x, y) => x.Name.CompareTo(y.Name);
                GroupedData.Data = DataSource;

                CanSelect = index => !DataSource[index].IsGroup;
        }
}

Grouped TileView

using UIWidgets;
using UnityEngine;

public class GroupedTileViewSelector : MonoBehaviour, IListViewTemplateSelector<GroupedListViewComponent, GroupedItem>
{
        [SerializeField]
        protected GroupedListViewComponent HeaderTemplate;

        [SerializeField]
        protected GroupedListViewComponent HeaderEmptyTemplate;

        [SerializeField]
        protected GroupedListViewComponent ItemTemplate;

        [SerializeField]
        protected GroupedListViewComponent ItemEmptyTemplate;

        GroupedListViewComponent[] templates;

        public GroupedListViewComponent[] AllTemplates()
        {
                if (templates == null)
                {
                        templates = new[] { this.headerTemplate, this.headerEmptyTemplate, this.itemTemplate, this.itemEmptyTemplate, };
                }

                return templates;
        }

        public GroupedListViewComponent Select(int index, GroupedItem item)
        {
                if (item.IsGroup)
                {
                        return item.IsEmpty ? headerEmptyTemplate : headerTemplate;
                }

                return item.IsEmpty ? itemEmptyTemplate : itemTemplate;
        }
}

public class GroupedTileView : ListViewCustom<GroupedListViewComponent, GroupedItem>
{
        public GroupedList<Photo> GroupedData = new GroupedList<Photo>((groups, item) =>
        {
                var date = item.Created.Date;

                foreach (var group in groups)
                {
                        if (group.Created == date)
                        {
                                return group;
                        }
                }

                return new Photo() { Created = item.Created.Date, IsGroup = true };
        });

        bool isGroupedListViewInited;

        public override void Init()
        {
                if (isGroupedListViewInited)
                {
                        return;
                }

                isGroupedListViewInited = true;

                base.Init();

                GroupedData.GroupComparison = (x, y) => x.Created.CompareTo(y.Created);
                GroupedData.EmptyGroupItem = new Photo() { IsGroup = true, IsEmpty = true };
                GroupedData.EmptyItem = new Photo() { IsEmpty = true };
                GroupedData.ItemsPerBlock = ListRenderer.GetItemsPerBlock();

                GroupedData.Output = DataSource;
        }

        public override void UpdateItems()
        {
                base.UpdateItems();

                GroupedData.ItemsPerBlock = ListRenderer.GetItemsPerBlock();
        }

        public override void Resize()
        {
                base.Resize();

                GroupedData.ItemsPerBlock = ListRenderer.GetItemsPerBlock();
        }
}

Linear GroupedTileView

public class LinearGroupedTileViewSelector : MonoBehaviour, IListViewTemplateSelector<GroupedListViewComponent, GroupedItem>
{
        [SerializeField]
        protected GroupedListViewComponent HeaderTemplate;

        [SerializeField]
        protected GroupedListViewComponent HeaderEmptyTemplate;

        [SerializeField]
        protected GroupedListViewComponent ItemTemplate;

        [SerializeField]
        protected GroupedListViewComponent ItemEmptyTemplate;

        GroupedListViewComponent[] templates;

        public GroupedListViewComponent[] AllTemplates()
        {
                if (templates == null)
                {
                        templates = new[] { this.headerTemplate, this.headerEmptyTemplate, this.itemTemplate, this.itemEmptyTemplate, };
                }

                return templates;
        }

        public GroupedListViewComponent Select(int index, GroupedItem item)
        {
                if (item.IsGroup)
                {
                        return item.IsEmpty ? headerEmptyTemplate : headerTemplate;
                }

                return item.IsEmpty ? itemEmptyTemplate : itemTemplate;
        }
}

public class LinearGroupedTileView : ListViewCustom<GroupedListViewComponent, GroupedItem>
{
        // Real DataSource (use instead of DataSource).
        public ObservableList<GroupedItem> RealDataSource = new ObservableList<GroupedItem>();

        public LinearGroupedList<GroupedItem> GroupedData = new LinearGroupedList<GroupedItem>(x => x.IsGroup);

        bool isGroupedListViewInited;

        public override void Init()
        {
                if (isGroupedListViewInited)
                {
                        return;
                }

                isGroupedListViewInited = true;

                base.Init();

                GroupedData.EmptyHeaderItem = new GroupedItem() { IsGroup = true, IsEmpty = true };
                GroupedData.EmptyItem = new GroupedItem() { IsEmpty = true };
                GroupedData.ItemsPerBlock = ListRenderer.GetItemsPerBlock();

                GroupedData.Input = RealDataSource;
                GroupedData.Output = DataSource;
        }

        public override void UpdateItems()
        {
                base.UpdateItems();

                GroupedData.ItemsPerBlock = ListRenderer.GetItemsPerBlock();
        }

        public override void Resize()
        {
                base.Resize();

                GroupedData.ItemsPerBlock = ListRenderer.GetItemsPerBlock();
        }
}