I am using xamarin.form. I want to create Listview with expand and collapse.
Example:
Please send me your suggestion.
CodePudding user response:
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Padding="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<control:FrameView Grid.Row="0" Text="Popular Music Instrument">
<control:FrameView.ContainerContent>
<StackLayout>
<Label Text="1. Piano/Keyboard" TextColor="Black"></Label>
<Label Text="2. Guitar" TextColor="Black"></Label>
<Label Text="3. Violin" TextColor="Black"></Label>
<Label Text="4. Drums" TextColor="Black"></Label>
<Label Text="5. Saxophone" TextColor="Black"></Label>
</StackLayout>
</control:FrameView.ContainerContent>
</control:FrameView>
<control:FrameView Grid.Row="1" Text="Gender">
<control:FrameView.ContainerContent>
<StackLayout>
<Label Text="1. Male" TextColor="Black"></Label>
<Label Text="2. Female" TextColor="Black"></Label>
<Label Text="3. Transgender" TextColor="Black"></Label>
</StackLayout>
</control:FrameView.ContainerContent>
</control:FrameView>
<control:FrameView Grid.Row="2" Text="Detail">
<control:FrameView.ContainerContent>
<StackLayout>
<Label Text="Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,
when an unknown printer took a galley of type and scrambled it to make a type
specimen book." TextColor="Black"></Label>
</StackLayout>
</control:FrameView.ContainerContent>
</control:FrameView>
</Grid>
Please review this link:
https://xamarinuidesigns.blogspot.com/2021/12/accordion-ui-designs-part-1.html
CodePudding user response:
We can create a complex Expandable ListView
with a sub ListView in Xamarin.Forms.. This Expandable List allows user to Expand and Collapse items via a simple click.
Suppose we have a list of hotels and each hotel contains a list of rooms. One click on a hotel will display its rooms while a double click will collapse it.
We can create the following folders :
- Models
- Views
- ViewModels
1.Create Models
Create a new Class Hotel.cs
public class Hotel
{
public string Name { get; set; }
public List<Room> Rooms { get; set; }
public bool IsVisible { get; set; } = false;
public Hotel()
{
}
public Hotel(string name, List<Room> rooms)
{
Name = name;
Rooms = rooms;
}
}
Create a new Class Room.cs
public class Room
{
public string RoomName { get; set; }
public int TypeID { get; set; }
public Room()
{
}
public Room(string name, int typeID)
{
RoomName = name;
TypeID = typeID;
}
}
}
2.Create ViewModels
BaseViewModel.cs
public class BaseViewModel: INotifyPropertyChanged
{
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
bool isEmpty = false;
public bool IsEmpty
{
get { return isEmpty; }
set
{
isEmpty = value;
OnEmptyChanged(this, new PropertyChangedEventArgs("IsEmpty"));
}
}
private void OnEmptyChanged(BaseViewModel baseViewModel, PropertyChangedEventArgs propertyChangedEventArgs)
{
CrossToastPopUp.Current.ShowToastMessage("No Data Found");
}
string busyText = string.Empty;
string title = string.Empty;
public string Title
{
get => title;
set => SetProperty(ref title, value);
}
public string BusyText
{
get => busyText;
set => SetProperty(ref busyText, value);
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
HotelsGroupViewModel.cs
public class HotelsGroupViewModel: BaseViewModel
{
private HotelViewModel _oldHotel;
private ObservableCollection<HotelViewModel> items;
public ObservableCollection<HotelViewModel> Items
{
get => items;
set => SetProperty(ref items, value);
}
public Command LoadHotelsCommand { get; set; }
public Command<HotelViewModel> RefreshItemsCommand { get; set; }
public HotelsGroupViewModel()
{
items = new ObservableCollection<HotelViewModel>();
Items = new ObservableCollection<HotelViewModel>();
LoadHotelsCommand = new Command(async () => await ExecuteLoadItemsCommandAsync());
RefreshItemsCommand = new Command<HotelViewModel>((item) => ExecuteRefreshItemsCommand(item));
}
public bool isExpanded = false;
private void ExecuteRefreshItemsCommand(HotelViewModel item)
{
if (_oldHotel == item)
{
// click twice on the same item will hide it
item.Expanded = !item.Expanded;
}
else
{
if (_oldHotel != null)
{
// hide previous selected item
_oldHotel.Expanded = false;
}
// show selected item
item.Expanded = true;
}
_oldHotel = item;
}
async System.Threading.Tasks.Task ExecuteLoadItemsCommandAsync()
{
try
{
if (IsBusy)
return;
IsBusy = true;
Items.Clear();
List<Room> Hotel1rooms = new List<Room>() { new Room("Jasmine", 1), new Room("Flower Suite", 2), new Room("narcissus", 1)
};
List<Room> Hotel2rooms = new List<Room>()
{
new Room("Princess", 1), new Room("Royale", 1), new Room("Queen", 1)
};
List<Room> Hotel3rooms = new List<Room>()
{
new Room("Marhaba", 1), new Room("Marhaba Salem", 1), new Room("Salem Royal", 1), new Room("Wedding Roome", 1), new Room("Wedding Suite", 2)
};
List<Hotel> items = new List<Hotel>() { new Hotel("Yasmine Hammamet", Hotel1rooms), new Hotel("El Mouradi Hammamet,", Hotel2rooms), new Hotel("Marhaba Royal Salem", Hotel3rooms) };
if (items != null && items.Count > 0)
{
foreach (var hotel in items)
Items.Add(new HotelViewModel(hotel));
}
else { IsEmpty = true; }
}
catch (Exception ex)
{
IsBusy = false;
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
}
HotelViewModel.cs
public class HotelViewModel: ObservableRangeCollection<RoomViewModel>, INotifyPropertyChanged
{
// It's a backup variable for storing CountryViewModel objects
private ObservableRangeCollection<RoomViewModel> hotelRooms = new ObservableRangeCollection<RoomViewModel>();
public HotelViewModel(Hotel hotel, bool expanded = false)
{
this.Hotel = hotel;
this._expanded = expanded;
foreach (Room room in hotel.Rooms)
{
hotelRooms.Add(new RoomViewModel(room));
}
if (expanded)
this.AddRange(hotelRooms);
}
public HotelViewModel()
{
}
private bool _expanded;
public bool Expanded
{
get { return _expanded; }
set
{
if (_expanded != value)
{
_expanded = value;
OnPropertyChanged(new PropertyChangedEventArgs("Expanded"));
OnPropertyChanged(new PropertyChangedEventArgs("StateIcon"));
if (_expanded)
{
this.AddRange(hotelRooms);
}
else
{
this.Clear();
}
}
}
}
public string StateIcon
{
get
{
if (Expanded)
{
return "arrow_a.png";
}
else
{ return "arrow_b.png"; }
}
}
public string Name { get { return Hotel.Name; } }
public Hotel Hotel { get; set; }
}
RoomViewModel.cs
public class RoomViewModel
{
private Room _room;
public RoomViewModel(Room room)
{
this._room = room;
}
public string RoomName { get { return _room.RoomName; } }
public int TypeID { get { return _room.TypeID; } }
public Room Room
{
get => _room;
}
}
3.Create Page (Hotels)
Hotels.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"
x:Name="currentPage"
xmlns:local="clr-namespace:ListViewWithSubListView.Views"
x:Class="ListViewWithSubListView.Views.Hotels">
<ContentPage.Content>
<Grid >
<StackLayout x:Name="hotelStack" Padding="1,0,1,0" >
<ListView
x:Name="HotelsList"
BackgroundColor="White"
IsGroupingEnabled="True"
IsPullToRefreshEnabled="true"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
ItemsSource="{Binding Items}"
RefreshCommand="{Binding LoadHotelsCommand}"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" VerticalOptions="Center">
<Label
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Medium"
Text="{Binding RoomName}"
TextColor="Black"
VerticalTextAlignment="Center" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Grid >
<Label
FontAttributes="Bold"
FontSize="Small"
Text="{Binding Name}"
TextColor="Gray"
VerticalTextAlignment="Center" />
<Image x:Name="ImgA" Source="{Binding StateIcon}" Margin="0,0,5,0" HeightRequest="20" WidthRequest="20" HorizontalOptions="End"/>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference currentPage}, Path=BindingContext.RefreshItemsCommand}" NumberOfTapsRequired="1" CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</ListView>
</StackLayout>
</Grid>
</ContentPage.Content>
</ContentPage>
Hotels.xaml.cs
public partial class Hotels : ContentPage
{
private HotelsGroupViewModel ViewModel
{
get { return (HotelsGroupViewModel)BindingContext; }
set { BindingContext = value; }
}
private List<Hotels> ListHotel = new List<Hotels>();
protected override void OnAppearing()
{
try
{
base.OnAppearing();
if (ViewModel.Items.Count == 0)
{
ViewModel.LoadHotelsCommand.Execute(null);
}
}
catch (Exception Ex)
{
Debug.WriteLine(Ex.Message);
}
}
public Hotels(HotelsGroupViewModel viewModel)
{
InitializeComponent();
this.ViewModel = viewModel;
}
}
And in App.xaml.cs
MainPage = new Hotels(new HotelsGroupViewModel());
Note: