Expandable ViewCell with DataTrigger

Ha Do
naxam-blogs
Published in
3 min readApr 6, 2018

--

This is a common feature and Xamarin.Forms has an official sample for it. There’re plenty ways to achieve this, but I prefer working with triggers (no special reason, just personal interest), so in this article I’ll introduce my method using DataTrigger.

  1. Create the ListView and fill data

Firstly, let’s create a ListView and list item model like you (perhaps) normally do.

<ListView
ItemsSource="{Binding ItemsList}"
HasUnevenRows="true">

</ListView>

The key property is HasUnevenRows=”true”

Add an hide-able label and a toggle button into the cell’s template:

<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout
Orientation="Vertical">
<Label
Text="{Binding Text}" />
<Label
Style="{StaticResource ExpandableLabel}"
BackgroundColor="White"
Text="{Binding ExpandedContent}"
HeightRequest="0"
/>
<Button
Text="Expand"
Command="{Binding BindingContext.ToggleItemCommand, Source={Reference PAGE}}"
CommandParameter="{Binding }">
<Button.Triggers>
<DataTrigger
TargetType="Button"
Binding="{Binding IsExpanded}"
Value="True">
<Setter
Property="Text"
Value="Collapse" />
</DataTrigger>
</Button.Triggers>
</Button>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>

Item model:

public class ListItem : INotifyPropertyChanged
{
private string _Text;
public string Text
{
get { return _Text; }
set
{
if (_Text != value)
{
_Text = value;
OnPropertyChanged("Text");
}
}
}

private string _ExpandedContent;
public string ExpandedContent
{
get { return _ExpandedContent; }
set
{
if (_ExpandedContent != value)
{
_ExpandedContent = value;
OnPropertyChanged("ExpandedContent");
}
}
}

private bool _IsExpanded;
public bool IsExpanded
{
get { return _IsExpanded; }
set
{
if (_IsExpanded != value)
{
_IsExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
}

...
}

Toggle the IsExpanded value on tapped:

private Command<ListItem> _ToggleItemCommand;
public Command<ListItem> ToggleItemCommand
{
get { return _ToggleItemCommand = _ToggleItemCommand
?? new Command<ListItem>((item) => {
item.IsExpanded ^= true;
//Whatever task you want the view model to do here, for example fetching the number of likes, views of a videos, the detailed review of a book...
});
}
}

2. Add TriggerAction and DataTrigger

We show / hide the expandable label by changing its HeightRequest. So we use an TriggerAction to change this value, and tell the ViewCell to update its size.

public class ForceUpdateSizeTriggerAction : TriggerAction<VisualElement>
{
public int HeighRequest { set; get; }

public ForceUpdateSizeTriggerAction() : base() {

}
protected override void Invoke(VisualElement sender)
{
var parent = sender.Parent;
while (parent != null && !(parent is ViewCell)) {
parent = parent.Parent;
}
if (parent is ViewCell cell)
{
Device.BeginInvokeOnMainThread(() =>
{
sender.HeightRequest = HeighRequest;
cell.ForceUpdateSize();
});
}
}
}

This action is called when the IsExpanded value is changed. To prevent the ForceUpdateSizeTriggerAction being initialized multiple times, I defined a style for the trigger:

<ContentPage.Resources>
<ResourceDictionary>
<Style
TargetType="Label"
x:Key="ExpandableLabel">
<Style.Triggers>
<DataTrigger
TargetType="Label"
Binding="{Binding IsExpanded}"
Value="True">
<DataTrigger.EnterActions>
<local:ForceUpdateSizeTriggerAction
HeighRequest="120" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<local:ForceUpdateSizeTriggerAction
HeighRequest="0" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</ContentPage.Resources>

Alternative solutions

  1. Toggle the expandable view’s visibility

If you don’t want to fix the view’s height request, you may bind its IsVisible property to the item’s IsExpanded property.

2. Subclass ViewCell

You may notice that the key method to make the cell expand / collapse is ViewCell.ForceUpdateSize(). So instead of using ActionTrigger, you may create a new custom class subclassing ViewCell and call ForceUpdateSize() there.

Known issue

Calling ViewCell.ForceUpdateSize() multiple times in iOS may cause the ListView lagging or irresponsive.

--

--