Xamarin.Forms 如果集合发生变化,Listview 中的动画将停止工作

Xamarin.Forms animation inside Listview stops working if collection changes

我很难弄清楚我的动画发生了什么。

Viewmodel 由一个 ObservableCollection 组成,每个项目都包含一个子项 ObservableCollection

Parents 集合绑定到 BindableLayout。该布局的 ItemTemplate 包含一个 Listview 以显示子元素。

<StackLayout BindableLayout.ItemsSource ="{Binding Parents}">
    <BindableLayout.ItemTemplate>
        <DataTemplate x:DataType="models:ParentRows">
            <StackLayout>      
                <Grid BackgroundColor="White" >
                     <!-- Some bindable content there -->
                </Grid>
            
               <ListView ItemsSource="{Binding Childs}" CachingStrategy="RecycleElementAndDataTemplate" RowHeight="50">
                    <ListView.ItemTemplate>
                        <DataTemplate x:DataType="models:ChildRows">
                            <ViewCell>
                                <StackLayout BackgroundColor="White">
                                    <controls:AnimatedGrid Refresh="{Binding Animation}"
                                          <!-- Some bindable content there -->
                                    </controls:AnimatedGrid>
                                </StackLayout>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </StackLayout>
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</StackLayout>

我在子列表视图上使用 AnimatedGrid,此控件继承自 Grid。它有一个名为 Refresh 的额外 BindableProperty 和一个 Animation 代码,只要 Refresh 属性 发生变化 .

就会调用该代码
        private async void AnimateItem()
        {
            await Device.InvokeOnMainThreadAsync(() =>
            {
                this.RotateTo(360,500);
            });
        }

一切正常,直到我开始过滤列表。 过滤列表后,后续调用 AnimateItem 将无效。 更准确地说,如果父项从列表中删除,然后再次添加,则此父项的子项将永远不会再设置动画。 过滤列表包括 Removing/Inserting 可观察集合的父级 (myCollection.Remove(item), myCollection.Insert(index, item),使用框架中的集合方法).

这似乎不是一个可观察到的集合绑定问题,因为父集合和子集合中的值仍然可以完美地更新。 更改 CachingStrategy 对问题也没有影响。

我发现,如果我用 CollectionView 替换 ListView 控件,问题就会消失。但是,我真的很想找到一个解决方案,允许我保留 listview 控件,因为切换到 CollectionView 会带来许多其他不良影响。

编辑 22/02/2022: 我在 github.

上做了一个 sample project to reproduce the issue

编辑 2022 年 3 月 15 日: 我仍然无法弄清楚出了什么问题。 但是,出于测试目的,我在控件构造函数中添加了一个 task.delay,然后是一个动画调用,并且此调用适用于已过滤的项目。这超出了我的理解范围。

解决方案

在您的 AnimatedGrid class 中,添加一个 isAttached 标志,并将以下行添加到您的 OnPropertyChanged 覆盖:

bool isAttached = false;

protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    base.OnPropertyChanged(propertyName);

    if (propertyName == "Renderer")
    {
        isAttached = !isAttached;
        if (!isAttached) this.RemoveBinding(RefreshProperty);
    }

    if (propertyName == nameof(Refresh))
    {
    ...

原因

免责声明:我花了很长时间才弄清楚这里发生了什么,虽然我能够解决问题,但我不会声称我完全和完全理解它。我相信这是 Xamarin 中的一个错误,在你的情况下,我会在 Github 中提交一个问题(尽管 MAUI 可能会对此进行更正...)

当一个ChildItem被移除(做过滤)时,旧的ChildItem-AnimatedGridRefresh 属性 仍然绑定到 ChildItemAnimationInt 属性.

当再次添加 ChildItem删除过滤器)时,会创建一个新视图。

现在问题很明显:当 ChildItemAnimationInt 属性 发生变化时(点击 ROTATE RANDOM 按钮)旧的通知ChildItem-AnimatedGridRefresh,然后旋转旧的View,但是新的ramains不变(不旋转)。


一旦理解了问题,我们需要弄清楚如何在视图 dettached 时删除旧视图的绑定:好吧,为此我使用了事实VisualElementRenderer 属性 是 set/modified 当元素被附加和分离时:第一次它被称为 i将 isAttached 标志设置为 true。第二次调用它时,我将标志设置为 false,并删除了绑定。去掉旧View的绑定,可以让新View正确绑定。