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();
   }
}