Posted on 24. July 2017

Новые освещения и PropertySet Interop – XAML и Visual Layer Interop

В предыдушем посте команда пользовательского интерфейса Windows изучала XamlCompositionBrushBase и LoadedImageSurface, чтобы создать пользовательские CompositionBrushes, с помощью которых можно рисовать XAML элементы прямо в Вашей разметке. Сегодня, мы ознакомим Вас с новыми улучшениями, добавленными в XAML и Visual Layer Interop API в Windows 10 Creators Update.

 

В этом посте мы рассмотрим некоторые из этих улучшений Creators Update, а именно новые API:

В Части 1:

 

  • XamlCompositionBrushBase – удобное и простое рисование XAML UIElement с CompositionBrush
  • LoadedImageSurface – легкая загрузка изображений и их использование с помощь Composition API

 

В Части 2: 

 

  • XamlLights – применение освещений в Вашем пользовательском интерфейсе XAML с помощью всего лишь одной строки XAML
  • PointerPositionPropertySet – создание 60 FPS анимаций, используя позицию указателя, из потока пользовательского интерфейса!
  • Включение свойств перевода - оживление элемента пользовательского интерфейса XAML с помощью Composition анимаций

 

Если Вы хотите ознакомиться с ранее доступными API ElementCompositionPreview, например, работа с «ручными» и «раздаточными» визуальными средствами, Вы можете изучить это здесь.

Осветление интерфейса с XamlLights

Новая великолепная функция в Creators Update - это возможность устанавливать и использовать эффект освещения прямо в XAML с помощью абстрактного класса XamlLight.

Создание XamlLight начинается так же, как и XamlCompositionBrushBase, с помощью метода OnConnected и OnDisconnected (см. Часть 1), но на этой раз источником будет подкласс XamlLight, чтобы создать Ваше собственное уникальное освещение, которое можно использовать прямо в XAML. Microsoft использует это с Reveal эффектом, которые доступен в Creators Update.

Чтобы показать эту возможность в работе, Microsoft разработали демоверсию для создания анимированных GIF, которые показаны выше. Данная демоверсия объединяет все, что Вы уже знаете о XamlCompositionBrushBase и LoadedImageSurface, с добавлением двух XamlLights: HoverLight и AmbientLight.

Начнем с создания AmbientLight: аналогично XamlCompositionBrushBase с помощью метода OnConnected и OnDisconnected. Однако для XamlLight установка свойства CompositionLight подкласса XamlLight.

public class AmbLight : XamlLight
{
    protected override void OnConnected(UIElement newElement)
    {
        Compositor compositor = Window.Current.Compositor;

        // Create AmbientLight and set its properties
        AmbientLight ambientLight = compositor.CreateAmbientLight();
        ambientLight.Color = Colors.White;

        // Associate CompositionLight with XamlLight
        CompositionLight = ambientLight;

        // Add UIElement to the Light's Targets
        AmbLight.AddTargetElement(GetId(), newElement);
    }

    protected override void OnDisconnected(UIElement oldElement)
    {
        // Dispose Light when it is removed from the tree
        AmbLight.RemoveTargetElement(GetId(), oldElement);
        CompositionLight.Dispose();
    }

    protected override string GetId() => typeof(AmbLight).FullName;
}

Охватывающее освещение готово, давайте построим SpotLight XamlLight. Одна из главных задач SpotLight - это следование за указателями пользователя. Для этого можно использовать метод GetPointerPositionPropertySet для ElementCompositionPreview для получения CompositionPropertySet, который можно использовать с помощью Composition ExpressionAnimation (PointerPositionPropertySet более подробно описан в разделе PropertySets ниже).

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

public class HoverLight : XamlLight 
{
    private ExpressionAnimation _lightPositionExpression;
    private Vector3KeyFrameAnimation _offsetAnimation;

    protected override void OnConnected(UIElement targetElement)
    {
        Compositor compositor = Window.Current.Compositor;

        // Create SpotLight and set its properties
        SpotLight spotLight = compositor.CreateSpotLight();
        spotLight.InnerConeAngleInDegrees = 50f;
        spotLight.InnerConeColor = Colors.FloralWhite;
        spotLight.OuterConeAngleInDegrees = 0f;
        spotLight.ConstantAttenuation = 1f;
        spotLight.LinearAttenuation = 0.253f;
        spotLight.QuadraticAttenuation = 0.58f;

        // Associate CompositionLight with XamlLight
        this.CompositionLight = spotLight;

        // Define resting position Animation
        Vector3 restingPosition = new Vector3(200, 200, 400);
        CubicBezierEasingFunction cbEasing = compositor.CreateCubicBezierEasingFunction( new Vector2(0.3f, 0.7f), new Vector2(0.9f, 0.5f));
        _offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
        _offsetAnimation.InsertKeyFrame(1, restingPosition, cbEasing);
        _offsetAnimation.Duration = TimeSpan.FromSeconds(0.5f);

        spotLight.Offset = restingPosition;

        // Define expression animation that relates light's offset to pointer position 
        CompositionPropertySet hoverPosition = ElementCompositionPreview.GetPointerPositionPropertySet(targetElement);
        _lightPositionExpression = compositor.CreateExpressionAnimation("Vector3(hover.Position.X, hover.Position.Y, height)");
        _lightPositionExpression.SetReferenceParameter("hover", hoverPosition);
        _lightPositionExpression.SetScalarParameter("height", 15.0f);

        // Configure pointer entered/ exited events
        targetElement.PointerMoved += TargetElement_PointerMoved;
        targetElement.PointerExited += TargetElement_PointerExited;

        // Add UIElement to the Light's Targets
        HoverLight.AddTargetElement(GetId(), targetElement);
    }

    private void MoveToRestingPosition()
    {
        // Start animation on SpotLight's Offset 
        CompositionLight?.StartAnimation("Offset", _offsetAnimation);
    }

    private void TargetElement_PointerMoved(object sender, PointerRoutedEventArgs e)
    {
        if (CompositionLight == null) return;

        // touch input is still UI thread-bound as of the Creators Update
        if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch)
        {
            Vector2 offset = e.GetCurrentPoint((UIElement)sender).Position.ToVector2();
            (CompositionLight as SpotLight).Offset = new Vector3(offset.X, offset.Y, 15);
        }
        else
        {
            // Get the pointer's current position from the property and bind the SpotLight's X-Y Offset
            CompositionLight.StartAnimation("Offset", _lightPositionExpression);
        }
    }

    private void TargetElement_PointerExited(object sender, PointerRoutedEventArgs e)
    {
        // Move to resting state when pointer leaves targeted UIElement
        MoveToRestingPosition();
    }

    protected override void OnDisconnected(UIElement oldElement)
    {
        // Dispose Light and Composition resources when it is removed from the tree
        HoverLight.RemoveTargetElement(GetId(), oldElement);
        CompositionLight.Dispose();
        _lightPositionExpression.Dispose();
        _offsetAnimation.Dispose();
    }

    protected override string GetId() => typeof(HoverLight).FullName;
}

Теперь, с помощью HoverLight класса, можно добавить оба AmbLight и HoverLight к предыдущему ImageEffectBrush (смотрите ImageEffectBrush в этом посте):
          
<Grid>
            <Grid.Background>
                <brushes:ImageEffectBrush ImageUriString="ms-appx:///Images/Background.png" />
            </Grid.Background>
  
            <Grid.Lights>
                <lights:HoverLight/>
                <lights:AmbLight/>
            </Grid.Lights>
</Grid>


Примечание: Для добавления XamlLight в разметку, Ваша минимальная SDK-версия должна быть установлена в Creators Update, или же Вы можете установить ее в коде после.
Для получения дополнительной информации об использовании XamlLight перейдите сюда; Вы можете изучить документацию Lighting здесь.

Использование CompositionPropertySets

Если Вы хотите использовать преимущества ScrollViewer Offset или Pointer положения X и Y (например, курсор мыши) для выполнения таких действий, как эффекты анимации, Вы можете использовать ElementCompositionPreview для извлечения PropertySets. Это позволяет создавать удивительно бесперебойные 60 FPS анимации, которые не привязаны к потоку пользовательского интерфейса. Эти методы позволяют вам получать преимущества пользовательского взаимодействия для создания анимаций и освещения.

Использование ScrollViewerManipulationPropertySet

Этот PropertySet полезен для анимации таких объектов, как Parallax, Translation и Opacity.

// Gets the manipulation 
CompositionPropertySet scrollViewerManipulationPropertySet = 
    ElementCompositionPreview.GetScrollViewerManipulationPropertySet(MyScrollViewer);

Чтобы ознакомиться с примером, перейдите к посту о Smooth Interaction и Motion, раздел, посвященный использованию ScrollViewerManipulationPropertySet для управления анимацией.

Использование Нового PointerPositionPropertySet

PointerPositionPropertySet - это новая возможность, добавленная в Creators Update. Этот PropertySet полезен для создания анимаций для освещения и наклона. Также как и ScrollViewerManipulationPropertySet, PointerPositionPropertySet обеспечивает быструю, бесперебойную и зависящую от потока анимацию.

Отличный пример - это механизм анимации Fluent Design RevealBrush, где Вы можете увидить эффекты освещения по бокам UIElements. Этот эффект создается CompositionLight, который имеет свойство Offset, анимированное с помощью ExpressionAnimation, используя значения, полученные из PointerPositionPropertySet.

// Useful for creating an ExpressionAnimation
CompositionPropertySet pointerPositionPropertySet = ElementCompositionPreview.GetPointerPositionPropertySet(targetElement);
ExpressionAnimation expressionAnimation = compositor.CreateExpressionAnimation("Vector3(param.Position.X, param.Position.Y, height)");
expressionAnimation.SetReferenceParameter("param", pointerPositionPropertySet);

Чтобы лучше понять, как использовать эти возможности для того, чтобы внедрить анимации в Ваше приложение, давайте ознакомимся с XamlLights и создадим демоверсию, которая использует PointerPositionPropertySet для анимации SpotLight.

Включение свойства перевода - анимация смещения элемента XAML с использованием анимации композиции

Как уже известно, совместное использование ресурсов между Framework Layer и Visual Layer было достаточно сложным до выпуска Creators Update. Данные визуальные свойства разделяются между UIElements и их Visuals поддержкой:

  • Offset
  • Scale
  • Opacity
  • TransformMatrix
  • InsetClip
  • CompositeMode

До выпуска Creators Update, Scale и Offset были особенно сложными, потому что, как упоминалось ранее, UIElement не знал об изменениях значений свойств на hand-out Visual, даже несмотря на то, что hand-out Visual знал об изменениях в UIElement. Следовательно, если Вы изменяли значение свойства смещения или размера Visual Offset, позиция UIElement также изменялась из-за размера страницы, но все предыдущие значения позиции UIElement следовали всем Вашим визуальным значениям.

С выпуском Creators Update это стало намного легче, поскольку теперь Вы можете предотвратить масштабирование Scale и Offset, добавив новое свойство Translation в Ваш элемент посредством объекта ElementCompositionPreview.

ElementCompositionPreview.SetIsTranslationEnabled(Rectangle1, true);

//Now initialize the value of Translation in the PropertySet to zero for first use to avoid timing issues. This ensures that the property is ready for use immediately.

var rect1VisualPropertySet = ElementCompositionPreview.GetElementVisual(Rectangle1).Properties;
rect1VisualPropertySet.InsertVector3("Translation", Vector3.Zero);

Затем анимируйте визуальную функцию Translation, где ранее Вы анимировали свойство Offset.

// Old way, subject to property stomping:
visual.StartAnimation("Offset.Y", animation);
// New way, available in the Creators Update
visual.StartAnimation("Translation.Y", animation);
Анимируя другое свойство из того, которое было затронуто во время пропусков макета, Вы избегаете нежелательного смещения, исходящего из XAML.

В завершении

В прошлых постах были рассмотрены некоторые новые функции XAML и Composition Interop, а также упрощенные основы использования Composition функций в Вашей XAML разметке.  Начиная от рисования Ваших UIElements с помощью CompositionBrushes и применения подсветки, чтобы выровнять анимацию UIThread. Теперь Composition API становится более доступной, чем когда-либо.
В следующем посте мы расскажем о создании удивительных Composition эффектов и об эволюции Fluent Design.