【问题标题】:How can I make Xamarin Forms close open swiped views?如何让 Xamarin Forms 关闭打开的滑动视图?
【发布时间】:2020-11-19 01:17:26
【问题描述】:

我在 CollectionView 中使用 SwipeView:

           <CollectionView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" ItemsSource="{Binding ListOfDecks}" 
                                SelectionMode="None">
                <CollectionView.ItemsLayout>
                    <LinearItemsLayout ItemSpacing="0" Orientation="Vertical"/>
                </CollectionView.ItemsLayout>
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <SwipeView>
                            <SwipeView.RightItems>
                                <SwipeItems Mode="Reveal" SwipeBehaviorOnInvoked="Auto">
                                    <SwipeItem Command="{Binding BindingContext.DeleteDeckCmd, Source={x:Reference ThisPage}}" CommandParameter="{Binding .}" Text="aa" BackgroundColor="Red" />
                                    <SwipeItem Command="{Binding BindingContext.RenameDeckCmd, Source={x:Reference ThisPage}}" CommandParameter="{Binding .}" Text="xx" BackgroundColor="LightGray"/>
                                </SwipeItems>
                            </SwipeView.RightItems>
                            <t:DeckGridTemplate />

                        </SwipeView>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>

当我向左滑动时效果很好。但是当我滑动另一行时,第一行并没有关闭。

所以最后,如果我不进去手动关闭所有刷过的行,我的用户体验看起来很糟糕。有没有一种方法可以在运行另一次滑动时自动关闭一次滑动,并在我进入下一个屏幕并返回此屏幕时关闭一次滑动?

【问题讨论】:

  • 您应该将此作为错误提交。这仍然是一个有效的问题,因为有可能的解决方法。
  • 这不是错误,这是 SwipeView 的预期行为方式。您可以创建增强请求,而不是错误。话虽如此,您可以遍历模板中的所有滑动视图并使用swipeView.Close(); Ref 手动关闭它们。 docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/…
  • 谢谢 Mihail,您知道如何在后端代码中完成此操作吗?如果您能花时间添加答案,我很乐意接受。

标签: xamarin xamarin.forms


【解决方案1】:

SwipViewSwipeStartedSwipeEnded方法,使用它们可以关闭之前打开的项目。

例如,将SwipeStartedSwipeEnded 添加到Xaml (code based on the official sample) 中,如下所示:

...
<SwipeView SwipeStarted="SwipeView_SwipeStarted" SwipeEnded="SwipeView_SwipeEnded">
            <SwipeView.LeftItems>
                <SwipeItems SwipeBehaviorOnInvoked="Close"
                            Mode="Reveal">
                    <SwipeItem Text="Favorite"
                                IconImageSource="favorite.png"
                                BackgroundColor="LightGreen"
                                Command="{Binding Source={x:Reference collectionView}, Path=BindingContext.FavoriteCommand}"
                                CommandParameter="{Binding}" />
                    <SwipeItem Text="Delete"
                                IconImageSource="delete.png"
                                BackgroundColor="LightPink"
                                Command="{Binding Source={x:Reference collectionView}, Path=BindingContext.DeleteCommand}"
                                CommandParameter="{Binding}" />
                </SwipeItems>
            </SwipeView.LeftItems>
  ...

在 ContentPage 中声明 List&lt;SwipeView&gt;。调用SwipeEnded 时,将项目添加到List&lt;SwipeView&gt;。稍后在调用 SwipeStarted 时,关闭并删除上一项。

List<SwipeView> swipeViews { set; get; }
public VerticalListSwipeContextItemsPage()
{
    InitializeComponent();
    BindingContext = new MonkeysViewModel();

    swipeViews = new List<SwipeView>();
}

private void SwipeView_SwipeStarted(object sender, SwipeStartedEventArgs e)
{
    Console.WriteLine("SwipeView_SwipeStarted");

    if(swipeViews.Count == 1)
    {
        swipeViews[0].Close();
        swipeViews.Remove(swipeViews[0]);
    }
}

private void SwipeView_SwipeEnded(object sender, SwipeEndedEventArgs e)
{
    Console.WriteLine("SwipeView_SwipeEnded");
    swipeViews.Add(swipView);
}

效果:

【讨论】:

    【解决方案2】:

    我早些时候写了一个简短的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 上找到我。

    【讨论】:

    • 请不要使用外部 git 存储库来显示您的代码,因为它不能保证是静态的,并非所有读者都可以访问,并且在哪里可以立即找到相关代码。相反,请edit 在您的答案中包含与问题相关的代码示例(使用formatted text,请不要提供代码图片!)。
    • @Hoppeduppeanut 你是对的,我在这里上传了代码示例
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-10
    • 2014-09-28
    相关资源
    最近更新 更多