0
votes

I'm implementing a swipe to delete feature using a custom view cell and renderer. I have a list view like so:

https://i.stack.imgur.com/AzBQk.png

and when swiped, a button is revealed, as below

https://i.stack.imgur.com/S87uj.png

However, after clicking the delete button (which successfully removes the item from the list) the view cell that moved to the delete cell's position still has the delete button exposed:

https://i.stack.imgur.com/aMwux.png

Anyone have any idea how to stop this from happening? My custom renderer simply exposes the OnFling() and OnScroll() events.

I have also tried a similar thing using the Xamarin forms Pan Gesture Recogniser and come across the same issue.

EDIT 1: Added code snippets

Custom view cell xaml

<ViewCell xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SwipeViewCell" xmlns:local="CustomControls">
<Grid x:Name="SwipeViewCellGrid">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="70*"/>
        <ColumnDefinition Width="30*"/>
    </Grid.ColumnDefinitions>
    <StackLayout x:Name="HiddenContainer" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Grid.Column="1"/>
    <local:GestureStackLayout x:Name="SwipeContainer" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Grid.ColumnSpan="2" Grid.Column="0"/>
</Grid>

Custom View Cell .cs:

public partial class SwipeViewCell : ViewCell
{       

    private View _swipeContent;
    private View _hiddenContent; 


    public SwipeViewCell()
    {
        InitializeComponent();

        SwipeContainer.SwipeLeft += SwipeContainer_SwipeLeft;
        SwipeContainer.SwipeRight += SwipeContainer_SwipeRight; 
    }

    public View SwipeContent
    {
        get
        {
            return _swipeContent;
        }
        set
        {
            _swipeContent = value;
            SwipeContainer.Children.Add(SwipeContent);
            OnPropertyChanged();
        }
    }

    public View HiddenContent
    {
        get
        {
            return _hiddenContent;
        }
        set
        {
            _hiddenContent = value;
            HiddenContainer.Children.Add(HiddenContent);
            OnPropertyChanged();
        }
    }

    private void SwipeContainer_SwipeRight(object sender, EventArgs e)
    {
        SwipeContainer.TranslateTo(SwipeContainer.X, 0, 250, Easing.Linear);
    }

    private void SwipeContainer_SwipeLeft(object sender, EventArgs e)
    {
        SwipeContainer.TranslateTo(SwipeContainer.X - HiddenContainer.Width, 0, 250, Easing.Linear);
    }

}

View Model:

public class ViewModel
{

    private ObservableCollection<string> _data;     

    public ViewModel()
    {
         Data = new ObservableCollection<string>();
         Data.Add("a");
         Data.Add("c");
         Data.Add("b");
    }

    public ObservableCollection<string> Data
    {
        get { return _data; }
        set
        {
            _data= value;
            OnPropertyChanged();
        }
    }

     public ICommand Delete
    {
        get
        {
            return new Command((e) =>
            {
                _data.Remove(e as string);                    
                Data = _data
            });
        }
    }

}

XAML:

 <ListView ItemsSource="{Binding Data}" x:Name="Model" HorizontalOptions="FillAndExpand" IsVisible="True" VerticalOptions="FillAndExpand" SeparatorVisibility="Default" SeparatorColor="Black">
                <CustomControls:MultiListView.ItemTemplate>
                    <DataTemplate>                       
                            <CustomControls:SwipeViewCell  x:Name="Item">
                                <CustomControls:SwipeViewCell.SwipeContent>
                                    <StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="White">
                                        <Label Text="{Binding }"/>
                                    </StackLayout>
                                </CustomControls:SwipeViewCell.SwipeContent>
                                <CustomControls:SwipeViewCell.HiddenContent>
                                    <Button Text="Delete" Command="{Binding Delete}"  BindingContext="{Binding Source={x:Reference Model}, Path=BindingContext}" CommandParameter="{Binding Source={x:Reference Item}, Path=BindingContext}"  BackgroundColor="Red"></Button>
                                </CustomControls:SwipeViewCell.HiddenContent>
                            </CustomControls:SwipeViewCell>                          
                    </DataTemplate>
                <ListView.ItemTemplate>
            <ListView>
1
could you please add your code? i can give you a good solutionMohamad Mahmoud
@MikeDarwish I've added some snippetsDParry
Hi @DParry. I am having a similar issue of old cells lingering in my UI. Did you find a solution to this problem?Chucky
@Chucky Nope. If I recall correctly it is to do with how Xamarin.Android caches the views and recycles elements in a list. One thing you could do (depending on the size of your list and performance) is implement another custom control that emulates a list view by rendering a bunch of stacklayouts. This would obviously be less performant, however you would not have to worry about cached views. If you need an example on how to do this let me know :)DParry

1 Answers

-1
votes

On the following the complete Example, how you can do that, it is working fine for me:

1- Add a class contains a number have been showing on your list view

 public class NumberClass
    {
        public string Number{ get; set; }
    }

2-try to use ObservableCollection for NumberClass as the following:

public class NumberClassList : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public System.Collections.ObjectModel.ObservableCollection<NumberClass> _items;
        public ObservableCollection<NumberClass> Items
        {
            get { return _items; }
            set { _items = value; OnPropertyChanged("Items"); }
        }
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged == null)
                return;
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public SerialClassList(List<NumberClass> itemList)
        {
            Items = new ObservableCollection<NumberClass>();
            foreach (NumberClassitm in itemList)
            {
                Items.Add(itm);
            }
        }
    }

3-put your List of number on List<NumberClass>

    public List<NumberClass> allItems;

4-Use NumberClassList as the following:

 NumberClassList items;
    items = new NumberClassList(allItems);

5-set the item Source for the your ListView as the following:

ItemsSource = items.Items;

6-Create a DataTemplate for your ListView as the following: Notes: Delete Button and Number Have same binding

DataTemplate dt = new DataTemplate(() =>
            {
                var Number = new Label
                {
                    TextColor=Color.Black,
                    FontAttributes=FontAttributes.Bold,
                    HorizontalOptions = LayoutOptions.StartAndExpand
                };
               Number.SetBinding(Label.TextProperty, new Binding("Number"));
                var Deletebutton = new Button { Text = "Delete", HorizontalOptions = LayoutOptions.StartAndExpand  };
                Deletebutton.SetBinding(Button.CommandParameterProperty, new Binding("Number"));
                Deletebutton.Clicked += DeleteSerial;
                return new ViewCell
                {
                    View = new StackLayout
                    {
                        Orientation = StackOrientation.Horizontal,
                        VerticalOptions = LayoutOptions.FillAndExpand,                          
                        Children =
                        {
                            Number,
                            Deletebutton
                        }
                    }
                };
            });
 YourListView.ItemTemplate = dt;

7-this is the implementation for Delete button:

 public void DeleteSerial(object sender,EventArgs e)
            {
                var item = (Xamarin.Forms.Button)sender;
                SerialClass listitem = (from itm in items.Items
                                        where itm.Number == item.CommandParameter.ToString()
                             select itm)
                            .FirstOrDefault<SerialClass>();
            items.Items.Remove(listitem);             
        }