我早些时候写了一个简短的blog 来介绍我的解决方案。您可以找到 here 的示例 repo。
对于这个项目,我使用了一个简单的 MVVM 设置,因此我们至少需要一个模型、一个视图和一个视图模型来使用。
我将只使用在右侧打开的 SwipeView,但您可以以您喜欢的任何方式为 SwipeView 实施此解决方案。我会说可能性是无数的......
型号
我设置的第一件事是模型,在本例中是具有两个属性的 Person 类。 IsOpen 属性是绑定到自定义 SwipeView 所必需的,我将在后面解释。我实现了 INotifyPropertyChanged 接口,以便能够通知视图有关 IsOpen 属性的更新。
public class Person : INotifyPropertyChanged
{
public string Name { get; set; }
private bool _isOpen;
public bool IsOpen
{
get => _isOpen;
set
{
if (_isOpen != value)
{
_isOpen = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
视图模型
只需创建一个 Person 列表作为 CollectionView 的源。 DeleteCommand 处理向左滑动项目时显示的 SwipeView 删除按钮。 OpenItemChangedCommand 将任何其他 Person 项目的 IsOpen 属性设置为 false,这不是最后一个刷过的 Person 项目。
当从列表中删除项目时,需要 OnPropertyChanged() 来更新页面。
public class SwipeViewCollectionViewModel : INotifyPropertyChanged
{
public SwipeViewCollectionViewModel()
{
Persons = new List<Person>
{
new Person { Name = "Albert"},
new Person { Name = "Burak"},
new Person { Name = "Conny"},
new Person { Name = "Dolly"},
new Person { Name = "Erik"},
};
}
public event PropertyChangedEventHandler PropertyChanged;
private List<Person> _persons;
public List<Person> Persons
{
get => _persons;
set
{
if (_persons != value)
{
_persons = value;
OnPropertyChanged();
}
}
}
private Command<Person> _deletePersonCommand;
public Command<Person> DeletePersonCommand => _deletePersonCommand ??= new Command<Person>((person) => Persons.Remove(person));
private Command<Person> _openItemChangedCommand;
public Command<Person> OpenItemChangedCommand => _openItemChangedCommand ??= new Command<Person>(ChangeOpenPersonItem);
private void ChangeOpenPersonItem(Person swipedPersonItem)
{
if (swipedPersonItem != null && swipedPersonItem.IsOpen)
{
if (Persons.Count < 2 || Persons.Select(x => x.IsOpen).Count() < 2)
return;
foreach (var person in Persons.Where(x => x != swipedPersonItem && x.IsOpen))
{
person.IsOpen = false;
}
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
自定义滑动视图
为了使解决方案正常工作,我们需要使用我们自己的一些属性来扩展现有的 SwipeView。在 SwipeView 的官方文档中,我已经看到了一个可绑定的属性 IsOpen,但是在当前稳定的 Xamarin 版本中,你还不能使用它。在本例中,我创建了一个可绑定的 IsOpen 属性,该属性将绑定到 Person 模型中的 IsOpen 属性。当该属性更改时,将执行命令。还要确保将 BindingMode 设置为 TwoWay,因为我们需要能够以两种方式更新 View 和 ViewModel。
SwipeView 使我们能够订阅 SwipeEnded 事件。使用 SwipeEndedEventArgs,我们可以检查滑动手势结束时 SwipeView 是否打开。这是此实现成功的关键:如果 SwipeView 是打开的,则 IsOpen 属性将被更新。这将更新滑动的 Person 项的 IsOpen 属性,并执行命令以确保列表中的所有其他 Person 项的 IsOpen 属性设置为 false。
public class CustomSwipeView : SwipeView, IDisposable
{
public CustomSwipeView()
{
SwipeEnded += CustomSwipeView_SwipeEnded;
}
public static readonly BindableProperty IsOpenProperty = BindableProperty.Create(
nameof(IsOpen),
typeof(bool),
typeof(CustomSwipeView),
false,
BindingMode.TwoWay,
propertyChanged: IsOpenPropertyChanged);
public bool IsOpen
{
get => (bool)GetValue(IsOpenProperty);
set => SetValue(IsOpenProperty, value);
}
public static readonly BindableProperty CommandProperty = BindableProperty.Create(
nameof(Command), typeof(ICommand), typeof(CustomSwipeView), null);
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
nameof(CommandParameter), typeof(object), typeof(CustomSwipeView), null);
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public void Dispose()
{
SwipeEnded -= CustomSwipeView_SwipeEnded;
}
private static void IsOpenPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (!(bindable is CustomSwipeView control) || !(oldValue is bool wasOpen) || !(newValue is bool isOpen))
return;
if (!wasOpen)
return;
if (!isOpen)
control.Close();
}
private void CustomSwipeView_SwipeEnded(object sender, SwipeEndedEventArgs e)
{
if (e.IsOpen && Command.CanExecute(CommandParameter))
{
IsOpen = true;
Command.Execute(CommandParameter);
}
}
}
集合视图
最后但同样重要的是,我们需要使用 CollectionView 创建页面。我使用相对绑定能够从 DataTemplate 绑定到 ViewModel。模型本身被绑定为 CommandParameter 以便能够从列表中更新或删除模型。确保将 Person.IsOpen 属性绑定到 CustomSwipeView.IsOpen 属性。
<CollectionView
ItemsSource="{Binding Persons}"
SelectionMode="None">
<CollectionView.ItemsLayout>
<LinearItemsLayout
Orientation="Vertical"
ItemSpacing="10"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<ContentView>
<views:CustomSwipeView
Margin="8"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:SwipeViewCollectionViewModel}}, Path=OpenItemChangedCommand}"
CommandParameter="{Binding .}"
IsOpen="{Binding IsOpen}">
<views:CustomSwipeView.RightItems>
<SwipeItems>
<SwipeItemView
CommandParameter="{Binding .}"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:SwipeViewCollectionViewModel}}, Path=DeletePersonCommand}">
<Frame
HasShadow="False"
BackgroundColor="Red">
<Label Text="Delete" TextColor="White"/>
</Frame>
</SwipeItemView>
</SwipeItems>
</views:CustomSwipeView.RightItems>
<Frame
HasShadow="False"
BackgroundColor="LightGray">
<Label Text="{Binding Name}" HorizontalTextAlignment="Center"/>
</Frame>
</views:CustomSwipeView>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
这是我的解决方案,我敢打赌还有很多其他解决方案。如果你有,请告诉我!
我创建了一个example repo 供大家参考。你也可以在 Twitter (@rickavdijk) 和 LinkedIn 上找到我。