7

I am trying to create a list in xamarin.forms that is a bit complex. The user should be able to click on an item in the list and it should expand into something somewhat bigger. The bigger item should now display some additional UI components that are associated specifically with this view.

I wonder how I should approach this problem. It's about a listview that has items of dynamic size. Upon click the item will display additional views related to the item. Should this be done in Xamarin.ios and Xamarin.droid, or is it recommended to try and achieve this in xamarin.forms?

I'll post a picture, it is not good and might need some magnification but it shows 4 items. The 3rd one is expanded and therefore you can see the spinner and button on it.

Only one item can be expanded at a time(I might have to handle that in the ViewModel) and upon pressing another item the old one should be hidden.

The list

Edit:

Thanks to Rohit I started implementing a solution in Xamarin.Forms but it doesn't really work still, just some small problems in how the row is expanded. See picture below. I'm skipping the spinner and just using a button for simplicity. The expanded row overlaps the row below itself. First picture is before click, second is after clicking on the item called "Two".

View

<?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:Class="simpletest.PlayGroundPage">
    <ContentPage.Content>
        <StackLayout>
            <ListView VerticalOptions="FillAndExpand" HasUnevenRows="True" ItemsSource="{Binding AllItems}" SelectedItem="{Binding MySelectedItem}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout VerticalOptions="FillAndExpand">
                            <StackLayout VerticalOptions="FillAndExpand" Orientation="Horizontal">
                                <Label Text="{Binding MyText}" />
                                <Image Source="{Binding MyImage}" />
                            </StackLayout>
                            <Button Text="button1" IsVisible="{Binding IsExtraControlsVisible}" />  
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Item

    public class Item : INotifyPropertyChanged
    {


        public Item(string text, string image, int id)
        {
            _myText = text;
            _myImage = image;
            _id = id;
            _isExtraControlsVisible = false;

        }

        private string _myText;
        private string _myImage;
        private bool _isExtraControlsVisible;
        private int _id;

        public event PropertyChangedEventHandler PropertyChanged;

        public int Id { get { return _id; } set { _id = value; } }

        public string MyText
        {
            get { return _myText; }
            set { _myText = value; OnPropertyChanged("MyText"); }
        }
        public string MyImage
        {
            get { return _myImage; }
            set { _myImage = value; OnPropertyChanged("MyImage"); }
        }
        public bool IsExtraControlsVisible
        {
            get { return _isExtraControlsVisible; }
            set { _isExtraControlsVisible = value; OnPropertyChanged("IsExtraControlsVisible"); }
        }

        private void OnPropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }

ViewModel:

    class PlayGroundViewModel : INotifyPropertyChanged
    {
        private Item _mySelectedItem;



        public PlayGroundViewModel(ObservableCollection<Item> allItems)
        {
            AllItems = allItems;
            _mySelectedItem = allItems.First();
        }
        public ObservableCollection<Item> AllItems { get; set; }

        public Item MySelectedItem
        {
            get { return _mySelectedItem; } //Added a field for this one, mainly for debugging.
            set
            {

                foreach (Item x in AllItems) //Changed to non-linq since it is not a list.
                {
                    x.IsExtraControlsVisible = false;
                }

                if (value != null)
                {
                    foreach (Item x in AllItems)
                    {
                        if (x.Id.Equals(value.Id))
                        {
                            x.IsExtraControlsVisible = true;
                            _mySelectedItem = x;
                        }
                    }
                }

                SetChangedProperty("MySelectedItem");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void SetChangedProperty(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }

    }

CodeBehind:

    public partial class PlayGroundPage : ContentPage
    {
        public PlayGroundPage()
        {
            InitializeComponent();
            ObservableCollection<Item> items = new ObservableCollection<Item>();
            items.Add(new Item("One", "", 0));
            items.Add(new Item("Two", "", 1));
            items.Add(new Item("Three", "", 2));
            PlayGroundViewModel weekViewModel = new PlayGroundViewModel(items);
            BindingContext = weekViewModel;
        }
    }

Before clicking on a row

After having clicked

why_vincent
  • 2,172
  • 9
  • 33
  • 49
  • I have done this before. If you're using MVVM and you have your ItemsSource bound to an IList on your ViewModel, then you just need to bind the IsVisible property of the Button and Spinner to 2 bool properties on your ViewModel. When the user clicks the item, this should call an ICommand on your ViewModel and you can set both those IsVisible properties to true. – Jon Aug 01 '16 at 15:25
  • @Mangist can you show your code? – Jeremy Thompson Aug 01 '16 at 15:29
  • @JeremyThompson can you try my suggestion first? – Jon Aug 01 '16 at 15:29
  • @Mangist Why? I am not the OP. – Jeremy Thompson Aug 01 '16 at 15:31
  • @JeremyThompson ? and? – Jon Aug 01 '16 at 15:31
  • I know it would work, the aim of [SO] is to become a resource for programming Q & A's, having to dig through the comments a user might or might not be bothered to do is pointless. Why dont you want to post an answer? No skin of my neck if you dont, you wont earn rep if you dont and your time and effort will not be rewarded. Worse off if someone posted the code as per your suggestion and steals your thunder – Jeremy Thompson Aug 01 '16 at 15:34
  • The OP did ask "I wonder how I should approach this problem", so I pointed him in the right direction. He didn't ask for the full code solution. Stack Overflow is also not a place you come to get people to write your code for you. If he doesn't try and implement a solution based on someones guidelines, he will not learn anything and will be back asking the same questions again. Are you actually looking for a solution to OPs problem? – Jon Aug 01 '16 at 15:44
  • @Mangist will `IsVisible`property changing have animation or it will be sharp appearing/disappearing? – Yehor Hromadskyi Aug 01 '16 at 16:06
  • @EgorGromadskiy there won't be an animation, it will just hide and show. You will need to add your own animation code in the XAML file. – Jon Aug 01 '16 at 16:18

1 Answers1

2

You can implement it in the following way using XAML, ViewModel, ObservableCollection.

XAML :

<ListView VerticalOption="FillAndExpand" HasUnevenRows="True" 
 ItemsSource="{Binding AllItems}" SelectedItem="{Binding MySelectedItem}" >
 <ListView.ItemTemplate>
   <DataTemplate>
      <ViewCell>
         <StackLayout VerticalOptions="FillAndExpand">
            <StackLayout VerticalOptions="FillAndExpand" Orientation="Horizontal">
               <Label Text="{Binding MyText}" />
               <Image Source="{Binding MyImage}" />
            </StackLayout>
            <Button Text="button" IsVisible="{Binding IsExtraControlsVisible}" />
            <Spinner IsVisible="{Binding IsExtraControlsVisible}" />
         </StackLayout>
      </ViewCell> 
   </DataTemplate>
 </ListView.ItemTemplate>
</ListView>

ViewModel :

public ObservableCollection<Item> AllItems
{
    get { return _allItems; }
    set
    {
        _allItems = value;
        OnPropertyChanged();
     }
}
public Item MySelectedItem
{
    get { return _mySelectedItem; }
    set
    {
        _mySelectedItem = value;
        OnPropertyChanged();

        foreach (var item in AllItems)
        {
            item.IsExtraControlsVisible = false;
        }
        var selectedItem = AllItems.FirstOrDefault(x => x.Equals(value));
        if (selectedItem != null)
        {
            selectedItem.IsExtraControlsVisible = true;
        }
    }
}

Item.cs :

public class Item : INotifyPropertyChanged
{
   private string _myText;
   private string _myImage;
   private bool _isExtraControlsVisible;
   private int _id;

   public int Id { get; set; }
   public string MyText 
   {
       get{ return _myText; } 
       set
       { _myText = value; 
          OnPropertyChanged();
       } 
   }
   public string MyImage
   {
       get{ return _myImage; } 
       set
       {
            _myImage = value; 
            OnPropertyChanged();
       } 
   }
   public bool IsExtraControlsVisible
   {
       get{ return _isExtraControlsVisible; } 
       set
       {
           _isExtraControlsVisible = value;
           OnPropertyChanged();
       } 
   }
}

Please find the demo here - XamarinForms_Dynamic_ListView_Item.

Rohit Vipin Mathews
  • 11,629
  • 15
  • 57
  • 112
  • 1
    Thanks but didn't get it to work. I changed the foreach on the observablecollection due to limitations in that class and just added a little s to VerticalOption. Also implemented a get for MySelectedItem. Populated with a few items. When I click on the item and debug then I see that the Items visibleproperty becomes true. I've also implemented an OnPropertyChanged that takes a string as an argument and passes for example "IsExtraControlsVisible". The row simply doesn't expand. Do you think I've misunderstood something? The solution looks good and I would love to solve it like this. – why_vincent Aug 02 '16 at 12:00
  • Can you add what you have implemented to the Question? – Rohit Vipin Mathews Aug 02 '16 at 12:12
  • I have edited the post now and added your code with my modifications. I am a Java developer that started with .net recently so I might break some conventions. – why_vincent Aug 02 '16 at 12:47
  • I corrected one mistake of missing `` its working fine for me. i will post the sample. – Rohit Vipin Mathews Aug 02 '16 at 15:13
  • Thank you so much! Finally I have an expandable Item. I am truly grateful. The issue at the moment is that the expanded Item overlaps the row below. Any idea of why? I have updated the code with my changes and added pictures of my current issue. Meanwhile I'll look at the demo. – why_vincent Aug 03 '16 at 09:33
  • If it solves the issue, please accept the answer so that it would be useful for others in the future. The "HasUnevenRow" property should take care of your overlap issue. – Rohit Vipin Mathews Aug 03 '16 at 09:38
  • It is still messing with me a bit but I'm really grateful and of course I'll accept it. Thanks a bunch. – why_vincent Aug 03 '16 at 09:43
  • Try hiding the listview separator and use a box view of height 1 in the item template for separation. – Rohit Vipin Mathews Aug 03 '16 at 09:45
  • Fyi the demo works on android but not on iOS for me. Does it work on iOS for you? – why_vincent Aug 03 '16 at 13:39
  • @why_vincent - I haven't tested it on all platforms, i just provided you with a solution. You can expand on it. I will try to fix it from my end as well. – Rohit Vipin Mathews Aug 03 '16 at 15:07