Thought I'd give back as opposed to always asking questions
Two upfront notes - no WP8 renderer (because I'm not targeting WP8) and also, use at your own risk. I'm just trying to be nice, not looking to provide support.
Here's how I'm handling changing the selection mode of a ListView as well as adding spacing between rows.
First, the attached properties (no ListView subclass):
public enum SelectionMode
{
Single = 0,
Multiple,
None
}
public class List
{
public static SelectionMode GetSelectionMode(BindableObject obj)
{
return (SelectionMode)obj.GetValue(SelectionModeProperty);
}
public static void SetSelectionMode(BindableObject obj, SelectionMode value)
{
obj.SetValue(SelectionModeProperty, value);
}
public static readonly BindableProperty SelectionModeProperty =
BindableProperty.CreateAttached<List, SelectionMode>(b => GetSelectionMode(b), default(SelectionMode));
public static double GetSpacing(BindableObject obj)
{
return (double)obj.GetValue(SpacingProperty);
}
public static void SetSpacing(BindableObject obj, double value)
{
obj.SetValue(SpacingProperty, value);
}
public static readonly BindableProperty SpacingProperty =
BindableProperty.CreateAttached<List, double>(b => GetSpacing(b), default(double));
}
Android Renderer:
public class ListViewRenderer : Xamarin.Forms.Platform.Android.ListViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
UpdateSpacing();
UpdateSelectionMode();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Attached.List.SelectionModeProperty.PropertyName)
{
UpdateSelectionMode();
}
else if (e.PropertyName == Attached.List.SpacingProperty.PropertyName)
{
UpdateSpacing();
}
}
private void UpdateSpacing()
{
if (this.Element == null || this.Control == null)
{
return;
}
var spacing = Attached.List.GetSpacing(this.Element);
this.Control.Divider = new ColorDrawable(Android.Graphics.Color.Transparent);
this.Control.DividerHeight = (int)spacing;
}
private void UpdateSelectionMode()
{
if (this.Element == null || this.Control == null)
{
return;
}
var selectionMode = Attached.List.GetSelectionMode(this.Element);
switch (selectionMode)
{
case Attached.SelectionMode.Multiple:
this.Control.ChoiceMode = Android.Widget.ChoiceMode.Multiple;
break;
case Attached.SelectionMode.Single:
this.Control.ChoiceMode = Android.Widget.ChoiceMode.Single;
break;
case Attached.SelectionMode.None:
this.Control.ChoiceMode = Android.Widget.ChoiceMode.None;
break;
}
}
}
iOS Renderer - this one, like all things iOS ListView, is a little trickier but I adapted the answer here:
public class ListViewRenderer : Xamarin.Forms.Platform.iOS.ListViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
UpdateSpacing();
UpdateSelectionMode();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Attached.List.SelectionModeProperty.PropertyName)
{
UpdateSelectionMode();
}
else if (e.PropertyName == Attached.List.SpacingProperty.PropertyName)
{
UpdateSpacing();
}
}
private void UpdateSpacing()
{
if (this.Control == null || this.Element == null)
{
return;
}
var baseSource = this.Control.Source;
var spacingSource = baseSource as SpacingDataSourceWrapper;
if (spacingSource != null)
{
baseSource = spacingSource.WrappedSource;
}
var spacing = Attached.List.GetSpacing(this.Element);
if (spacing > 0)
{
this.Control.Source = new SpacingDataSourceWrapper(spacing, baseSource);
}
else if (spacingSource != null)
{
this.Control.Source = baseSource;
}
}
private void UpdateSelectionMode()
{
if (this.Control == null || this.Element == null)
{
return;
}
var selectionMode = Attached.List.GetSelectionMode(this.Element);
switch (selectionMode)
{
case Attached.SelectionMode.Multiple:
this.Control.AllowsSelection = true;
this.Control.AllowsMultipleSelection = true;
break;
case Attached.SelectionMode.Single:
this.Control.AllowsMultipleSelection = false;
this.Control.AllowsSelection = true;
break;
case Attached.SelectionMode.None:
this.Control.AllowsSelection = false;
this.Control.AllowsMultipleSelection = false;
break;
}
}
}
public abstract class UITableViewSourceWrapper : UITableViewSource
{
protected UITableViewSourceWrapper(UITableViewSource underlyingSource)
{
WrappedSource = underlyingSource;
}
public UITableViewSource WrappedSource { get; private set; }
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
return this.GetCellInternal(tableView, indexPath);
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return this.WrappedSource.RowsInSection(tableview, section);
}
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
{
return this.WrappedSource.GetHeightForHeader(tableView, section);
}
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
return this.WrappedSource.GetViewForHeader(tableView, section);
}
public override nint NumberOfSections(UITableView tableView)
{
return this.WrappedSource.NumberOfSections(tableView);
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
this.WrappedSource.RowSelected(tableView, indexPath);
}
public override string[] SectionIndexTitles(UITableView tableView)
{
return this.WrappedSource.SectionIndexTitles(tableView);
}
public override string TitleForHeader(UITableView tableView, nint section)
{
return this.WrappedSource.TitleForHeader(tableView, section);
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return this.WrappedSource.GetHeightForRow(tableView, indexPath);
}
protected virtual UITableViewCell GetCellInternal(UITableView tableView, NSIndexPath indexPath)
{
return this.WrappedSource.GetCell(tableView, indexPath);
}
}
public class SpacingDataSourceWrapper : UITableViewSourceWrapper
{
public SpacingDataSourceWrapper(double rowSpacing, UITableViewSource underlyingTableSource)
: base(underlyingTableSource)
{
RowSpacing = rowSpacing;
}
public double RowSpacing { get; set; }
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
tableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
if (indexPath.Row % 2 == 1)
{
var cell = tableView.DequeueReusableCell("Spacer");
if (cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Default, "Spacer");
}
cell.BackgroundColor = UIColor.Clear;
return cell;
}
else
{
return base.GetCell(tableView, NSIndexPath.FromRowSection(indexPath.Row / 2, indexPath.Section));
}
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return indexPath.Row % 2 == 0
? base.GetHeightForRow(tableView, NSIndexPath.FromRowSection(indexPath.Row / 2, indexPath.Section))
: (nfloat)RowSpacing;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return (base.RowsInSection(tableview, section) * 2) - 1;
}
}