Windows Composition Visual的偏移值未正确初始化

时间:2018-12-08 00:52:10

标签: c# animation uwp uwp-xaml windows-composition-api

我正在使用UWP并与Composition API一起以编程方式在屏幕上滑动项目,我发现当应用程序首次启动时,许多元素的初始滑动均无法正常执行。经过进一步检查,我发现我用于计算和设置动画的初始位置和目标位置的ElementVisual的Offset属性有时在刚开始的动画中会出现X,Y,Z值全部为零的情况。最好的办法是,只要该应用程序继续运行,我便可以告诉它该元素的所有动画都可以正常工作。

我正在使用的应用程序更加复杂有趣,但是我创建了一个简化的测试应用程序来演示我遇到的问题。

从一个新的空白UWP项目中,将以下GridView和Button添加到页面的根网格中:

        <GridView x:Name="TestGridView" HorizontalAlignment="Center" VerticalAlignment="Center">
        <GridViewItem >
            <Border Width="125" Height="125">
                <Grid>
                    <TextBlock Text="Content String 1" />
                </Grid>
            </Border>
        </GridViewItem>
        <GridViewItem>
            <Border Width="125" Height="125">
                <Grid>
                    <TextBlock Text="Content String 2" />
                </Grid>
            </Border>
        </GridViewItem>
        <GridViewItem>
            <Border Width="125" Height="125">
                <Grid>
                    <TextBlock Text="Content String 3"/>
                </Grid>
            </Border>
        </GridViewItem>
    </GridView>
    <Button Height="125" Width="125" Click="Button_Click"/>

在代码隐藏文件中,我添加了以下其他using语句,变量声明和此事件处理程序:

using System.Numerics;  
using Windows.UI.Composition;
using Windows.UI.Xaml.Hosting;
using System.Diagnostics;

 Compositor compositor;

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        foreach (var element in TestGridView.ItemsPanelRoot.Children)
        {
            var slideVisual = ElementCompositionPreview.GetElementVisual(element);

            Debug.WriteLine("Element Offset: " + slideVisual.Offset);

            var slideAnimation = compositor.CreateVector3KeyFrameAnimation();
            slideAnimation.InsertKeyFrame(0f, slideVisual.Offset);
            slideAnimation.InsertKeyFrame(1f, new Vector3(slideVisual.Offset.X, slideVisual.Offset.Y + 20f, 0f));
            slideAnimation.Duration = TimeSpan.FromMilliseconds(800);

            slideVisual.StartAnimation(nameof(slideVisual.Offset), slideAnimation);
        }
    }

我还在MainPage()构造函数中初始化合成器:

    public MainPage()
    {
        this.InitializeComponent();
        compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
    }

然后我从Visual Studio中以“调试”模式运行该项目。 按下按钮会在“立即窗口”中产生以下输出(为清晰起见,在编辑中添加了分隔线)

Element Offset: <0, 0, 0>
Element Offset: <0, 0, 0>
Element Offset: <0, 0, 0>

Element Offset: <0, 20, 0>
Element Offset: <129, 0, 0>
Element Offset: <258, 0, 0>

Element Offset: <0, 40, 0>
Element Offset: <129, 20, 0>
Element Offset: <258, 20, 0>

Element Offset: <0, 60, 0>
Element Offset: <129, 40, 0>
Element Offset: <258, 40, 0>

请注意,三个元素中的第一个在第一张幻灯片上都设置了动画,而另两个元素则没有,并且它们的“偏移”值都是零值。

在每张幻灯片上,所有三个元素都将按照其应有的位置进行动画处理,尽管第一个幻灯片动画的副作用仍然存在。

我没有尝试找到一种方法来强制我们的应用程序中的元素更新其偏移值,从而使每个元素的初始幻灯片动画都能正常工作,包括执行一些操作,例如在实际的预期动画之前插入虚拟WarmUp动画。很快地告诉该元素要么从其当前位置移动到相同位置,要么将其开始位置稍加修改就移动到其实际当前位置。

            var warmUpAnimation = compositor.CreateVector3KeyFrameAnimation();                
            warmUpAnimation.InsertKeyFrame(0.0f, new Vector3(slideVisual.Offset.X + 1, slideVisual.Offset.Y, slideVisual.Offset.Z));
            warmUpAnimation.InsertKeyFrame(1f, slideVisual.Offset);
            warmUpAnimation.Duration = TimeSpan.FromMilliseconds(1);

我猜想这是Windows Composition API中的错误,但我希望有人可以提出一种简单有效的解决方法,以迫使ElementVisual在加载后正确初始化。

2 个答案:

答案 0 :(得分:0)

每当在UWP应用程序中使用Composition api时,我都会遇到同样的问题。无论出于何种原因,对UIElements的GetElementVisual的初始调用通常都无法提供正确的返回结果。

在第一次使用视觉效果之前,我已经采取了循环运行测试的方式。

作为一个简单的示例,如果将以下函数添加到上述示例中:

    private bool IsCompositionVisualReady(UIElement element)
    {
        var visual = ElementCompositionPreview.GetElementVisual(element);
        if (visual.Size.X == 0 && visual.Size.Y == 0)
        {
            return false;
        }
        return true;
    }

然后在Button_Click例程的foreach段的开头插入以下while循环,并将“异步”添加到点击处理程序中:

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        foreach (var element in TestGridView.ItemsPanelRoot.Children)
        {
            while (!IsCompositionVisualReady(element))
            {
                await Task.Delay(TimeSpan.FromMilliseconds(1));
            }

            var slideVisual = ElementCompositionPreview.GetElementVisual(element);

视觉效果或多或少会产生动画效果。但是,在动画的第一遍中,最后两个元素似乎仍然略有延迟。

答案 1 :(得分:0)

两件事:

1-通常,不要使用Offset来动画从FrameworkElement创建的Visual的位置。请改用手动的“翻译”属性。

这可以通过在每个元素上调用以下命令来完成,最好在其Loaded事件中调用

ElementCompositionPreview.SetIsTranslationEnabled(myFrameworkElement, true);

然后您将动画更改为

var warmUpAnimation = compositor.CreateVector3KeyFrameAnimation();
warmUpAnimation.InsertExpressionKeyFrame(0.0f, "Vector3(this.Target.Translation.X + 100f, this.Target.Translation.Y, 0f)");
warmUpAnimation.InsertKeyFrame(1f, new Vector3(0f));
warmUpAnimation.Duration = TimeSpan.FromMilliseconds(1);

slideVisual.StartAnimation("Translation", warmUpAnimation);

这在旧的Composition动画文档中确实非常明显,但是它们似乎已在很大程度上消除了最近更新中的提及。总的来说,成分文档并不是很好。我现在甚至根本找不到找到解释为什么应使用平移而不是偏移的页面(这与XAML属性覆盖“合成属性”有关)。

基本上偏移是绝对X.Y. XAML渲染引擎设置的元素相对于其父元素的相对位置,并且Translation类似于RenderTransform,除此之外,XAML会用其值覆盖Composition的Offset,而不必关心您在Composition中输入的内容级别,但它没有“翻译”属性的“知识”,因此它永远不会覆盖它。平移总是作为当前偏移量的加法运算。它也仅适用于从FrameworkElements创建的视觉效果-不需要任何其他类型的CompositionVisual,因为XAML不会更改其偏移量。

2-在您知道要在其加载的事件中的某个时刻进行动画处理的每个目标元素上调用ElementCompositionPreview.GetElementVisual(...)可能会有所帮助-请注意,它必须直接位于将要播放的元素上进行动画处理。这样可确保在需要时创建切换视觉效果并完全准备就绪。 (由于Composition API与系统合成器一起工作的性质,创建的内容可能有点异步)