The More interactive ListView.


List view being one of the most preferred item for displaying data. Out of the box it is well suited for displaying data or single type interactions with the list items, but what if we want to make the list view more interactive? And have different behaviours got different taps on a single list item. Example having a list of contacts, with their profile info and contact details and being able to connect with them in the least number of clicks (a good thing to consider from a UX point of view).

The primitive “ItemTapped” event of list view is good for selecting one item but if you want it to be more interactive, certainly you want to tweak it out. We will try to get the most out of “TapGesture” on the list view items to get multiple tap events on list view items.

ViewCell_interactiveListView

Considering the idea of contact list the only component we will focus on is a list view and nothing else. This thing works perfectly fine, out of the box cross-platform, no custom renderers or components needed!

For the demo point of view, data is generated locally and images are stored in resources you can hit API for data or whatever suits you there are no limitations except one.
If you want you can find the Ready Data here.

To use multiple tap gestures on our list view item we will have to block the ItemTapped event of list view by setting the ListView.SelectedItem as null. And instead have our own tap events.

_myListView.ItemTapped += _myListView_ItemTapped;

private void _myListView_ItemTapped(object sender, ItemTappedEventArgs e) => _myListView.SelectedItem = null;  //We dont want to select the entire Listview item so we disable the item selection here.

According to your requirement you can have as many tap events you need and in the XAML hook these events to your TapGestureRecognizer’s Tapped property.

private async void My_Event(object sender, EventArgs e)
{
//Event Handler Code
}

Here I wanted to have 3 events as Call, Message and View Profile so I made 3 events.

Now, in each event if the functionality is generic you are good to go as is, but if you want to have item specific actions (which we do want to!) we need to identify the Sender and set its binding context and extract the Data Transfer Object out of the list view item.

This can be done by,

((sender as TheTappedXAMLComponent).BindingContext is <T> _selectedTItem)

That being said, we have the DTO with us, handle your actions as needed.

TheInteractiveListViewInAction

XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MultiTapLVItems"
x:Class="MultiTapLVItems.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="MyCardTemplate">
<ViewCell>
<Frame Padding="3" Margin="10,5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="8*" />
</Grid.ColumnDefinitions>
<Image HeightRequest="50" WidthRequest="50" Margin="10,10,10,5" Source="{Binding ProfilePicture}" HorizontalOptions="Center" VerticalOptions="Center" Grid.Row="0" Grid.Column="0" />
<StackLayout Margin="0,10,0,0" Grid.Row="0" Grid.Column="1">
<Label Text="{Binding Name}" TextColor="Black" />
<Label Text="{Binding Designation}" FontSize="Small" TextColor="Gray"/>
</StackLayout>
<StackLayout Margin="20,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<StackLayout Margin="10,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="Call_Tapped" />
</StackLayout.GestureRecognizers>
<Image Source="call.png" HeightRequest="20" WidthRequest="20" />
<Label Text="Call" VerticalOptions="Center"/>
</StackLayout>
<Grid WidthRequest="1" BackgroundColor="LightGray" Opacity="0.5" />
<StackLayout Margin="10,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="Msg_Tapped" />
</StackLayout.GestureRecognizers>
<Image Source="msg.png" HeightRequest="20" WidthRequest="20" />
<Label Text="Message" VerticalOptions="Center"/>
</StackLayout>
<Grid WidthRequest="1" BackgroundColor="LightGray" Opacity="0.5" />
<StackLayout Margin="10,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="View_Tapped" />
</StackLayout.GestureRecognizers>
<Image Source="view.png" HeightRequest="20" WidthRequest="20" />
<Label Text="View" VerticalOptions="Center"/>
</StackLayout>
</StackLayout>
</Grid>
</Frame>
</ViewCell>
</DataTemplate>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<Label Text="The More Interactive ListView!" HorizontalOptions="Center" VerticalOptions="Start" FontAttributes="Bold" FontSize="Large" />
<ListView x:Name="_myListView" ItemTemplate="{StaticResource MyCardTemplate}" SeparatorColor="Transparent" SeparatorVisibility="None" HasUnevenRows="True" BackgroundColor="LightGray"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>

CODE

using System;
using System.Collections.Generic;
using Xamarin.Forms;
using static MultiTapLVItems.DataFactory;
namespace MultiTapLVItems
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
SetContent();
}
private void SetContent()
{
//We dont want to select the entire Listview item so we disable the item selection here.
_myListView.ItemTapped += _myListView_ItemTapped;
//Populating Our Listview
DataFactory dataFactory = new DataFactory();
_myListView.ItemsSource = dataFactory.Users;
}
private void _myListView_ItemTapped(object sender, ItemTappedEventArgs e) => _myListView.SelectedItem = null;
private async void Call_Tapped(object sender, EventArgs e)
{
if (((sender as StackLayout).BindingContext is User _selectedUser))
{
await DisplayAlert("Call", String.Format("Do you want to call {0} on {1}?", _selectedUser.Name, _selectedUser.PhoneNumber), "Cancel", "Call");
}
}
private async void Msg_Tapped(object sender, EventArgs e)
{
if (((sender as StackLayout).BindingContext is User _selectedUser))
{
await DisplayAlert("Message", String.Format("Do you want to Message {0} on {1}?", _selectedUser.Name, _selectedUser.PhoneNumber), "Cancel", "Message");
}
}
private async void View_Tapped(object sender, EventArgs e)
{
if (((sender as StackLayout).BindingContext is User _selectedUser))
{
await DisplayAlert("View Profile", String.Format("The view profile page is unavailable so you cannot view {0}'s profile. Sorry! 😛 But their ID is {1}", _selectedUser.Name, _selectedUser.Id), "Okay");
}
}
}
}

Pingback for assistance, your Feedback’s are always a welcome… 🙂

Regards,
Aditya Deshpande

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s