1

我正在使用 UWP 并使用 Composition API 以编程方式在屏幕上滑动项目,我发现当应用程序第一次启动时,许多元素的初始滑动都无法正确执行。经过进一步检查,我发现我用于计算和设置动画初始位置和目标位置的 ElementVisual 的偏移属性有时会在第一个动画上为 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>

请注意,三个元素中的第一个在第一张幻灯片上确实有动画,但其他两个没有,并且它们的 Offset 值都是零值。

在每张幻灯片上,所有三个元素都从它们之前的位置开始动画,尽管第一张幻灯片动画的副作用仍然存在。

我试图找到一种方法来强制我们应用程序中的元素更新它们的偏移值,以便每个元素的初始幻灯片动画正常工作,包括在实际预期动画之前插入一个虚拟的 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 组合 API 中的一个错误,但我希望有人能提出一个简单有效的解决方法,这将迫使 ElementVisual 在加载后正确初始化。

4

2 回答 2

1

两件事情:

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);

这在旧的合成动画文档中确实非常明显,但它们似乎在很大程度上消除了最近更新中提到的内容。一般来说,组合文档一点都不好。我现在什至找不到解释为什么你应该使用 Translation over Offset 的页面(这与 XAML 属性覆盖合成属性有关)。

本质上,Offset 是元素的绝对 XY 位置,它也是 XAML 渲染引擎设置的父元素,Translation 类似于在此之上的 RenderTransform - 不同的是 XAML 将用它的值覆盖 Composition 的 Offset,而不关心什么您在合成级别输入,但它没有翻译属性的“知识”,因此它永远不会覆盖它。平移始终作为当前偏移量的附加项。它也适用于从 FrameworkElements 创建的视觉对象 - 任何其他类型的 CompositionVisual 都不需要它,因为 XAML 不会更改它们的偏移量。

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

于 2019-03-05T10:31:55.153 回答
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 循环,并将“async”添加到单击处理程序:

    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);

视觉效果或多或少是应有的动画效果。但是,第一遍动画的最后两个元素似乎仍然存在轻微延迟。

于 2019-03-05T02:10:28.100 回答