【问题标题】:Offset values for Windows Composition Visual not getting correctly initializedWindows 组合视觉对象的偏移值未正确初始化
【发布时间】:2019-05-09 18:01:15
【问题描述】:

我正在使用 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 在加载后正确初始化。

【问题讨论】:

  • 这个案例可能是helpful
  • @CoCaIceDew,怎么样?

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


【解决方案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 属性覆盖合成属性有关)。

基本上偏移是绝对 X.Y。元素的相对位置也是由 XAML 呈现引擎设置的父元素,并且 Translation 类似于在此之上的 RenderTransform - 不同的是 XAML 将用它的值覆盖组合的偏移量,而不关心你在组合中输入的内容级别,但它没有翻译属性的“知识”,因此它永远不会覆盖它。平移始终作为当前偏移量的附加项。它也适用于从 FrameworkElements 创建的视觉对象 - 任何其他类型的 CompositionVisual 都不需要它,因为 XAML 不会更改它们的偏移量。

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

【讨论】:

  • AddExpressionKeyFrame 似乎不是一个有效的方法,并且该字符串没有提供任何有用的网络搜索结果。您是否有机会仔细检查您的代码示例以了解建议的翻译方法?正如您所指出的,这些 api 的文档还有一些不足之处。谢谢。
  • @HoloSheep 你是对的!抱歉,我使用自己的扩展,遵循构建器/“流利的”API 模式,通常让我将它们链接在一起 - AddExpressionKeyFrame 就是其中之一。应该是InsertExpressionKeyFrame - 编辑了以上内容
【解决方案2】:

每当我在 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);

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

【讨论】:

  • 为此,您可以通过在每个视觉对象的 Loaded 事件中调用 ElementCompositionPreview.GetElementVisual(...) 来避免所有这些,或者在与调用动画。您也不必存储视觉效果,但事先调用它可以确保创建视觉效果 - 由于组合的性质是通过应用程序外部存在的 DWM 工作的,因此这种事情是异步的。
  • @JonnyWestlake 谢谢,进行“热身”调用以检索每个视觉以预先强制其创建的微妙细节似乎是我一直在寻找的技巧,并且是我所追求的在我尝试warmUpAnimation的上面的帖子中,思考可能有效(但没有)。
  • (因为我无法编辑上面的评论,以供将来参考,它应该是"...or at least once at any point NOT in the same timeslice as the method that calls the animation..."
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多