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
- 基本上,你可以多次点击“随机旋转”来制作
随机儿童旋转。
- 单击“删除并添加 2 个父项”后,您
可以看到removed/reinserted项不再旋转了。
编辑 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-AnimatedGrid的Refresh
属性 仍然绑定到 ChildItem
的 AnimationInt
属性.
当再次添加 ChildItem
(删除过滤器)时,会创建一个新视图。
现在问题很明显:当 ChildItem
的 AnimationInt
属性 发生变化时(点击 ROTATE RANDOM 按钮)旧的通知ChildItem-AnimatedGrid的Refresh
,然后旋转旧的View,但是新的ramains不变(不旋转)。
一旦理解了问题,我们需要弄清楚如何在视图 dettached 时删除旧视图的绑定:好吧,为此我使用了事实VisualElement 的 Renderer
属性 是 set/modified 当元素被附加和分离时:第一次它被称为 i将 isAttached
标志设置为 true
。第二次调用它时,我将标志设置为 false
,并删除了绑定。去掉旧View的绑定,可以让新View正确绑定。
我很难弄清楚我的动画发生了什么。
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- 基本上,你可以多次点击“随机旋转”来制作 随机儿童旋转。
- 单击“删除并添加 2 个父项”后,您 可以看到removed/reinserted项不再旋转了。
编辑 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-AnimatedGrid的Refresh
属性 仍然绑定到 ChildItem
的 AnimationInt
属性.
当再次添加 ChildItem
(删除过滤器)时,会创建一个新视图。
现在问题很明显:当 ChildItem
的 AnimationInt
属性 发生变化时(点击 ROTATE RANDOM 按钮)旧的通知ChildItem-AnimatedGrid的Refresh
,然后旋转旧的View,但是新的ramains不变(不旋转)。
一旦理解了问题,我们需要弄清楚如何在视图 dettached 时删除旧视图的绑定:好吧,为此我使用了事实VisualElement 的 Renderer
属性 是 set/modified 当元素被附加和分离时:第一次它被称为 i将 isAttached
标志设置为 true
。第二次调用它时,我将标志设置为 false
,并删除了绑定。去掉旧View的绑定,可以让新View正确绑定。