I recently wanted to implement the same appearance within a ListView. In my case, however, I needed to have swipe context actions for the items in the ListView. Using the approach described above, there was a size difference between the height of displayed items in the list vs the height of the context action menu.
The solution I came up with was to utilize grouping in the ListView with each group containing a single item and adding a custom group header view which was just a transparent view with the desired spacing height. This will ensure that the context menu size is equal to the item's view size.
Here is a simple class that I use to create and manage the grouping:
public class SingleItemGrouping<T>
{
private ObservableCollection<SingleItemGroup<int, T>> _groups { get; set; } = new ObservableCollection<SingleItemGroup<int, T>>();
public ObservableCollection<SingleItemGroup<int, T>> Groups { get { return _groups; } }
private SingleItemGrouping(ObservableCollection<T> collection = null)
{
if (collection != null)
{
foreach (var item in collection)
{
this.Add(item);
}
collection.CollectionChanged += Collection_CollectionChanged;
}
}
public static ObservableCollection<SingleItemGroup<int, T>> Create(ObservableCollection<T> collection)
{
SingleItemGrouping<T> ret = new SingleItemGrouping<T>(collection);
return ret.Groups;
}
private void Collection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
var collection = sender as ObservableCollection<T>;
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
this.Remove((T)item);
}
}
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
if (collection != null)
{
var index = collection.IndexOf((T)item);
if (index >= 0)
{
Insert(index, (T)item);
continue;
}
}
Add((T)item);
}
}
}
public void Insert(int index, T item)
{
int groupKey = item.GetHashCode();
_groups.Insert(index, new SingleItemGroup<int, T>(groupKey, item));
}
public void Add(T item)
{
int groupKey = item.GetHashCode();
_groups.Add(new SingleItemGroup<int, T>(groupKey, item));
}
public void Remove(T item)
{
int groupKey = item.GetHashCode();
var remove = _groups.FirstOrDefault(x => x.GroupKey == groupKey);
if (remove != null)
{
_groups.Remove(remove);
}
}
}
public class SingleItemGroup<K, TItem> : ObservableCollection<TItem>
{
public K GroupKey { get; private set; }
public TItem Item { get { return Items[0]; } }
public SingleItemGroup(K key, TItem item)
{
GroupKey = key;
Items.Add(item);
}
}
And here is the implementation:
XAML:
<ListView x:Name="listView"
HasUnevenRows="true"
SeparatorVisibility="None"
IsGroupingEnabled="true">
<ListView.GroupHeaderTemplate >
<DataTemplate >
<ViewCell Height="20">
<Label />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Clicked="DeleteRecipe" CommandParameter="{Binding .}" Text="Delete" IsDestructive="True" />
</ViewCell.ContextActions>
<!-- define viewcell contents here -->
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView.ItemTemplate>
.CS
listView.ItemsSource = SingleItemGrouping<MyViewModel>.Create(myObservableCollection);