动画之前在StackPanel Flicker上使用FluidMoveBehavior

FluidMoveBehavior On a StackPanel Flickers Before Animating
2020-09-18
  •  译文(汉语)
  •  原文(英语)

我有一个这样ItemsControlFluidMoveBehavior附件:

<ScrollViewer VerticalScrollBarVisibility="Auto">
    <ItemsControl ItemsSource="{Binding RequirementsSource}" ItemTemplateSelector="{StaticResource RequirementTemplateSelector}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel IsItemsHost="True">
                    <i:Interaction.Behaviors>
                        <ic:FluidMoveBehavior AppliesTo="Children" Duration="0:0:00.5" Tag="DataContext">
                            <ic:FluidMoveBehavior.EaseX>
                                <BackEase EasingMode="EaseInOut" Amplitude="0.5"/>
                            </ic:FluidMoveBehavior.EaseX>
                            <ic:FluidMoveBehavior.EaseY>
                                <BackEase EasingMode="EaseInOut" Amplitude="0.5"/>
                            </ic:FluidMoveBehavior.EaseY>
                            </ic:FluidMoveBehavior>
                    </i:Interaction.Behaviors>
                </StackPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</ScrollViewer>

当项目更改其排序顺序时,它们会进行动画处理,但是首先将项目进行排序,就像没有FluidMoveBehavior附件一样,然后再进行排序.整个过程发生得足够快,以至于动画开始之前看起来像是闪烁的.

ItemsControl的ItemsSource看起来是这样的:

RequirementsSource = new ListCollectionView(Requirements);
ICollectionViewLiveShaping live = (ICollectionViewLiveShaping)RequirementsSource;
live.IsLiveSorting = True;
live.IsLiveFiltering = True;
live.LiveSortingProperties.Add("Rank");
live.LiveFilteringProperties.Add("Owner");
RequirementsSource.SortDescriptions.Add(
    new SortDescription("Rank", ListSortDirection.Ascending)
);
RequirementsSource.Filter = _filterPred;

排序是通过以下方法触发的:

private void SwapRanks(SwapArgs args)
{
    IPropertyRequirement first = args.Warning.Requirements
        .First(r => r.Rank == args.RankToSwap);
    IPropertyRequirement second = args.Warning.Requirements
        .First(r => r.Rank == args.SwapWith);

    int temp = first.Rank;
    first.Rank = second.Rank;
    second.Rank = temp;

    RequirementsSource.Refresh();
}

有谁知道导致此"闪烁"的原因以及如何制止它?

速聊1:
您能否提供任何代码来显示您的排序方式,就像您发布的内容一样好-除了没有可以使用的模板之外
速聊2:
我加入了代码,显示ItemsSource同样,实际的代码使用的模板,我只是把一切行的例子
速聊3:
我认为声明:RequirementsSource = New ListCollectionView(Requirements)将刷新视图初始状态(通过有效地设置新的ItemsSource),然后在进行排序时,您将使用FluidMoveBehavior看到它,然后从它认为的初始位置移开.(我可能会离开)
速聊4:
该代码仅在viewmodel的构造函数中.每次对代码进行排序时都不会调用它.我添加了导致排序触发该问题的方法.
速聊5:
现在,这变得更加有意义.然后可能需要推迟刷新数据源,例如,RequirementsSource.DeferRefresh();.在开始排序之前,然后进行刷新,以便只用一个更新命中ItemsPanel.
解决过程1

RequirementsSource.Refresh();从代码中删除行.

由于您正在使用实时排序,因此当Requirement.Rank更改(激发了PropertyChanged事件)时,动画将开始.但是,当您调用refresh时,它将使用Reset参数引发CollectionChanged,并强制刷新列表框.

编辑:

public class MainWindowViewModel 
{
    ObservableCollection<Requirement> requirements;
    public MainWindowViewModel()
    {
        requirements = new ObservableCollection<Requirement>(Enumerable
            .Range(0, 10)
            .Select(i => new Requirement {Rank = 10 - i, Name = "Item " + i}));

        RequirementsSource = new ListCollectionView(requirements);
        var live = (ICollectionViewLiveShaping)RequirementsSource;
        live.IsLiveSorting = true;
        live.IsLiveFiltering = true;
        live.LiveSortingProperties.Add("Rank");
        RequirementsSource.SortDescriptions.Add(
            new SortDescription("Rank", ListSortDirection.Ascending)
        );
        ReorderCommand = new DelegateCommand(Reorder);
    }

    public ListCollectionView RequirementsSource { get; set; }
    public DelegateCommand ReorderCommand { get; private set; }

    private void Reorder()
    {
        for (int i = 0; i < requirements.Count - 1; i += 2)
        {
            var temp = requirements[i].Rank;
            requirements[i].Rank = requirements[i + 1].Rank;
            requirements[i + 1].Rank = temp;
        }
    }
}
public class Requirement : BindableBase
{
    private double _rank;

    public double Rank
    {
        get { return _rank; }
        set { SetProperty(ref _rank, value); }
    }

    public string Name { get; set; }
}
速聊1:
最终得到正确答案(关键部分是刷新触发了Reset参数).为了做到这一点,我需要提取PropertyRequirementViewModelINotifyPropertyChanged为方法中的Rank属性实现的s,SwapRanks而不是实际对象本身.我也DeferRefresh按照Cadogi的建议将它们包裹起来.
速聊2:
我不确定为什么要使用DeferRefresh,然后从SwapRank调用PropertyChanged.我添加了对我有用的示例代码
速聊3:
现在,当那些对象的Rank属性更改时将触发propertychanged事件,它将触发两次:一次是在我第一次设置时触发,一次是在我设置第二个触发时触发.我添加了DeferRefresh,因此这只会引起一次刷新.我从方法中删除了显式的刷新调用.
速聊4:
我认为使用LiveShaping绑定到ListCollectionView的ItemsControl数据已经过优化,不需要使用DefferRefresh.简单地说,每次在循环中调用PropertyChanged时都不会刷新ItemsControl

I have a ItemsControl With the FluidMoveBehavior attached to it like this:

<ScrollViewer VerticalScrollBarVisibility="Auto">
    <ItemsControl ItemsSource="{Binding RequirementsSource}" ItemTemplateSelector="{StaticResource RequirementTemplateSelector}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel IsItemsHost="True">
                    <i:Interaction.Behaviors>
                        <ic:FluidMoveBehavior AppliesTo="Children" Duration="0:0:00.5" Tag="DataContext">
                            <ic:FluidMoveBehavior.EaseX>
                                <BackEase EasingMode="EaseInOut" Amplitude="0.5"/>
                            </ic:FluidMoveBehavior.EaseX>
                            <ic:FluidMoveBehavior.EaseY>
                                <BackEase EasingMode="EaseInOut" Amplitude="0.5"/>
                            </ic:FluidMoveBehavior.EaseY>
                            </ic:FluidMoveBehavior>
                    </i:Interaction.Behaviors>
                </StackPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</ScrollViewer>

When the items change their sort order they do animate, but first the items sort as if there was no FluidMoveBehavior attached and then sort back. That whole thing happens quickly enough that it looks like a flicker before the animation starts.

The ItemsControl ItemsSource looks like this:

RequirementsSource = new ListCollectionView(Requirements);
ICollectionViewLiveShaping live = (ICollectionViewLiveShaping)RequirementsSource;
live.IsLiveSorting = True;
live.IsLiveFiltering = True;
live.LiveSortingProperties.Add("Rank");
live.LiveFilteringProperties.Add("Owner");
RequirementsSource.SortDescriptions.Add(
    new SortDescription("Rank", ListSortDirection.Ascending)
);
RequirementsSource.Filter = _filterPred;

The sort is triggered by the following method:

private void SwapRanks(SwapArgs args)
{
    IPropertyRequirement first = args.Warning.Requirements
        .First(r => r.Rank == args.RankToSwap);
    IPropertyRequirement second = args.Warning.Requirements
        .First(r => r.Rank == args.SwapWith);

    int temp = first.Rank;
    first.Rank = second.Rank;
    second.Rank = temp;

    RequirementsSource.Refresh();
}

Does anyone have any idea what is causing this "flicker" and what I can do to stop it?

Talk1:
can you give any code to show how you are sorting, as what you have posted looks fine - other than not having the templates to work with
Talk2:
I have added code that shows the ItemsSource Also, the actual code is using templates, I just threw everything in line for the example
Talk3:
I think declaring: RequirementsSource = New ListCollectionView(Requirements) would refresh the view initialy (by effectively setting a new ItemsSource), then when you are sorting that is what you see using FluidMoveBehavior as it is then moving from what it thinks is the initial position. (I could be way off)
Talk4:
That code is only in the constructor for the viewmodel. It is not called every time the code is sorted. I added the method that causes the sort to trigger to the question.
Talk5:
That makes much more sense now. It may be worth holding back the refresh of your data source then e.g. RequirementsSource.DeferRefresh(); before starting your sort then refreshing afterwards so the ItemsPanel is only hit with one update.
Solutions1

remove RequirementsSource.Refresh(); line from your code.

Since you are using livesorting, animations starts when Requirement.Rank changes (PropertyChanged event is fired). But when you call refresh, it raises CollectionChanged with Reset argument and it forces the listbox to refresh.

EDIT:

public class MainWindowViewModel 
{
    ObservableCollection<Requirement> requirements;
    public MainWindowViewModel()
    {
        requirements = new ObservableCollection<Requirement>(Enumerable
            .Range(0, 10)
            .Select(i => new Requirement {Rank = 10 - i, Name = "Item " + i}));

        RequirementsSource = new ListCollectionView(requirements);
        var live = (ICollectionViewLiveShaping)RequirementsSource;
        live.IsLiveSorting = true;
        live.IsLiveFiltering = true;
        live.LiveSortingProperties.Add("Rank");
        RequirementsSource.SortDescriptions.Add(
            new SortDescription("Rank", ListSortDirection.Ascending)
        );
        ReorderCommand = new DelegateCommand(Reorder);
    }

    public ListCollectionView RequirementsSource { get; set; }
    public DelegateCommand ReorderCommand { get; private set; }

    private void Reorder()
    {
        for (int i = 0; i < requirements.Count - 1; i += 2)
        {
            var temp = requirements[i].Rank;
            requirements[i].Rank = requirements[i + 1].Rank;
            requirements[i + 1].Rank = temp;
        }
    }
}
public class Requirement : BindableBase
{
    private double _rank;

    public double Rank
    {
        get { return _rank; }
        set { SetProperty(ref _rank, value); }
    }

    public string Name { get; set; }
}
Talk1:
This ended up being the correct answer (the key part being that refresh was firing the Reset argument). In order to do this, I needed to pull the PropertyRequirementViewModels I had created that had INotifyPropertyChanged implemented for the Rank property in the SwapRanks method rather than the actual object itself. I also wrapped them in DeferRefresh as Cadogi suggested.
Talk2:
I'm not sure why you use DeferRefresh and then you call PropertyChanged from SwapRank. I have added sample code that worked for me just fine
Talk3:
now that the propertychanged event fires when the Rank property changes for those object, it is firing twice: once when I set first, and once when I set second. I added DeferRefresh so that this will only cause one refresh. I removed the explicit refresh call from the method.
Talk4:
I believe that ItemsControl databound to ListCollectionView with LiveShaping is already optimized and DefferRefresh in not necessary. Simply said, the ItemsControl is not refreshed each time the PropertyChanged is called in a loop
转载于:https://stackoverflow.com/questions/32361999/fluidmovebehavior-on-a-stackpanel-flickers-before-animating

本人是.net程序员,因为英语不行,使用工具翻译,希望对有需要的人有所帮助
如果本文质量不好,还请谅解,毕竟这些操作还是比较费时的,英语较好的可以看原文

留言回复
我们只提供高质量资源,素材,源码,坚持 下了就能用 原则,让客户花了钱觉得值
上班时间 : 周一至周五9:00-17:30 期待您的加入