Значения смещения для Windows Composition Visual неправильно инициализируются

Я использую 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"/>

В файле codebehind я добавляю следующие дополнительные операторы 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 корректно инициализироваться после загрузки.

0 ответов

Две вещи:

1 - Как правило, НЕ используйте Offset для анимации положения визуалов, созданных из FrameworkElement. Вместо этого используйте свойство "Перевод" руководства.

Это можно сделать, вызвав следующее для каждого элемента, предпочтительно в их событиях 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, перезаписывающими Свойства композиции).

По существу, смещение - это абсолютная XY-позиция элемента относительно его родительского элемента, установленного механизмом рендеринга XAML, а трансляция сродни RenderTransform, кроме того, XAML перезапишет смещение композиции с его значениями, не обращая внимания на то, что Вы вводите на уровне Composition, но он не "знает" о свойстве перевода, поэтому никогда не перезаписывает его. Перевод всегда применяется как добавка к текущему смещению. Он также работает только для визуалов, созданных из FrameworkElements - он не нужен для любого другого типа CompositionVisual, поскольку их смещения не изменяются в XAML.

2 - Это может помочь вызвать ElementCompositionPreview.GetElementVisual(...) для каждого целевого элемента, который, как вы знаете, вы собираетесь анимировать в какой-то момент в их загруженных событиях - обратите внимание, что он должен быть непосредственно на элементе, который будет анимирован, Это гарантирует, что визуальная передача будет создана и полностью готова к тому времени, когда вам это нужно. (Созданный может быть слегка асинхронным из-за особенностей работы API композиции с системным композитором)

Я продолжаю сталкиваться с этой же проблемой при использовании API Composition всякий раз, когда использую его в приложениях UWP. По какой-либо причине начальные вызовы GetElementVisual для UIElements часто не дают правильных результатов возврата.

Я прибег к выполнению теста в цикле, прежде чем впервые использовать визуальные эффекты.

В качестве простого примера, если вы добавите следующую функцию в приведенный выше пример:

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

А затем вставьте следующий цикл while в начало сегмента foreach в процедуре Button_Click и добавьте "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);

Визуальные эффекты оживляют более или менее должным образом. Тем не менее, кажется, что последние два элемента при первом проходе анимации все еще имеют небольшую задержку.

Другие вопросы по тегам