Udostępnij za pośrednictwem


Wizualizacje kompozycji

Elementy wizualne kompozycji tworzą strukturę drzewa wizualnego, na którym opierają się i bazują wszystkie inne funkcje interfejsu API Microsoft.UI.Composition. Interfejs API umożliwia deweloperom definiowanie i tworzenie jednego lub wielu obiektów wizualnych, z których każdy reprezentuje jeden węzeł w drzewie wizualnym.

Wizualizacje

Istnieje kilka typów wizualizacji tworzących strukturę drzewa wizualnego oraz klasę pędzla podstawowego z wieloma podklasami, które wpływają na zawartość wizualizacji:

Zawartość i efekty można stosować do SpriteVisuals przy użyciu CompositionBrush i jego podklas, w tym CompositionColorBrush, CompositionSurfaceBrush i CompositionEffectBrush. Aby dowiedzieć się więcej na temat pędzli, zobacz KompozycjaBrush — omówienie.

Przykład CompositionVisual

Tutaj przyjrzymy się przykładowemu kodowi WinUI 3, który demonstruje trzy różne typy wizualne wymienione wcześniej. Chociaż ten przykład nie obejmuje pojęć, takich jak animacje lub bardziej złożone efekty, zawiera bloki konstrukcyjne używane przez wszystkie te systemy. (Pełny przykładowy kod znajduje się na końcu tego artykułu).

W przykładzie jest wiele kwadratów koloru stałego, które można kliknąć i przeciągnąć na ekranie. Po kliknięciu na kwadrat, zostanie on przesunięty na przód, obróci się o 45 stopni i stanie się nieprzezroczysty podczas przeciągania.

Przedstawia to szereg podstawowych pojęć dotyczących pracy z interfejsem API, w tym:

  • Tworzenie komponatora
  • Tworzenie elementu SpriteVisual za pomocą elementu CompositionColorBrush
  • Przycinanie obrazu wizualnego
  • Obracanie wizualizacji
  • Ustawianie nieprzezroczystości
  • Zmiana położenia wizualizacji w kolekcji.

Tworzenie kompositora

Tworzenie kompositora i przechowywanie go w zmiennej do użycia jako fabryka jest prostym zadaniem. W aplikacji WinUI zazwyczaj pobierasz kompozytor z elementu XAML, który jest już połączony z drzewem wizualnym.

Compositor compositor = ElementCompositionPreview.GetElementVisual(MyHost).Compositor;

Jeśli potrzebujesz komponatora i nie masz dostępnego elementu UIElement, możesz zamiast tego użyć CompositionTarget.GetCompositorForCurrentThread() .

Tworzenie SpriteVisual i ColorBrush

Korzystając z kompositora , można łatwo tworzyć obiekty zawsze wtedy, gdy są potrzebne, takie jak SpriteVisual i CompositionColorBrush:

var visual = _compositor.CreateSpriteVisual();
visual.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));

Chociaż jest to tylko kilka wierszy kodu, demonstruje zaawansowaną koncepcję: Obiekty SpriteVisual są sercem systemu efektów. SpriteVisual zapewnia dużą elastyczność i współdziałanie w tworzeniu kolorów, obrazów i efektów. SpriteVisual to pojedynczy typ wizualizacji, który może wypełnić prostokąt 2D pędzlem, w tym przypadku stałym kolorem.

Przycinanie elementu wizualnego

Kompositor może również służyć do tworzenia klipów w wizualizacji. Poniżej przedstawiono przykład użycia narzędzia InsetClip do przycinania każdej strony wizualizacji:

var clip = _compositor.CreateInsetClip();
clip.LeftInset = 1.0f;
clip.RightInset = 1.0f;
clip.TopInset = 1.0f;
clip.BottomInset = 1.0f;
_currentVisual.Clip = clip;

Podobnie jak w przypadku innych obiektów w interfejsie API, element InsetClip może mieć zastosowane animacje do jego właściwości.

Obracanie klipu

Wizualizację można przekształcić za pomocą obrotu. Należy pamiętać, że element RotationAngle obsługuje zarówno radiany, jak i stopnie. Domyślnie ustawienia to radiany, ale łatwo jest określić wartość w stopniach, jak pokazano w następującym fragmencie kodu:

child.RotationAngleInDegrees = 45.0f;

Rotacja to tylko jeden przykład zestawu składników transformacji udostępnianych przez interfejs API, aby ułatwić wykonywanie tych zadań. Inne obejmują przesunięcie, skalowanie, orientację, rotację I 4x4 TransformMatrix.

Ustawianie nieprzezroczystości

Ustawienie przezroczystości obrazu jest prostą operacją przy użyciu wartości typu float. Na przykład w przykładzie wszystkie kwadraty zaczynają się od .8 nieprzezroczystości:

visual.Opacity = 0.8f;

Podobnie jak rotacja, właściwość Nieprzezroczystość może być animowana.

Zmiana położenia obiektu wizualnego w kolekcji

Interfejs API kompozycji umożliwia zmianę pozycji wizualizacji w VisualCollection na różne sposoby. Można go umieścić nad innym Visual za pomocą InsertAbove, umieścić poniżej za pomocą InsertBelow, przenieść na górę za pomocą InsertAtTop lub na dół za pomocą InsertAtBottom.

W przykładzie kliknięta wizualizacja jest sortowana u góry:

parent.Children.InsertAtTop(_currentVisual);

Pełny przykład

W pełnym przykładzie WinUI wszystkie powyższe pojęcia są używane razem do konstruowania i przechodzenia przez proste drzewo obiektów Visual w celu zmiany nieprzezroczystości bez niestandardowego renderowania DirectX. W przykładzie użyto interfejsu WinUI Grid dla danych wejściowych wskaźnika i hostuje drzewo kompozycji z elementem ElementCompositionPreview.

<Page
    x:Class="CompositionVisualSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid
        x:Name="MyHost"
        Background="Transparent"
        PointerPressed="OnPointerPressed"
        PointerMoved="OnPointerMoved"
        PointerReleased="OnPointerReleased" />
</Page>
using System;
using System.Numerics;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Input;
using Windows.Foundation;
using Windows.UI;

namespace CompositionVisualSample
{
    public sealed partial class MainPage : Page
    {
        private readonly Random _random = new();
        private Compositor _compositor;
        private ContainerVisual _root;
        private ContainerVisual _currentVisual;
        private Vector2 _offsetBias;
        private bool _dragging;

        public MainPage()
        {
            InitializeComponent();
            Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            if (_root != null)
            {
                return;
            }

            _compositor = ElementCompositionPreview.GetElementVisual(MyHost).Compositor;
            _root = _compositor.CreateContainerVisual();
            ElementCompositionPreview.SetElementChildVisual(MyHost, _root);

            for (int index = 0; index < 20; index++)
            {
                _root.Children.InsertAtTop(CreateChildElement());
            }
        }

        private ContainerVisual CreateChildElement()
        {
            var element = _compositor.CreateContainerVisual();
            element.Size = new Vector2(100.0f, 100.0f);

            var maxX = (float)Math.Max(MyHost.ActualWidth - element.Size.X, 0.0);
            var maxY = (float)Math.Max(MyHost.ActualHeight - element.Size.Y, 0.0);
            element.Offset = new Vector3(
                (float)(_random.NextDouble() * maxX),
                (float)(_random.NextDouble() * maxY),
                0.0f);

            var outer = _compositor.CreateSpriteVisual();
            outer.Size = new Vector2(100.0f, 100.0f);
            outer.Brush = _compositor.CreateColorBrush(Colors.White);
            element.Children.InsertAtTop(outer);

            var inner = _compositor.CreateSpriteVisual();
            inner.Offset = new Vector3(3.0f, 3.0f, 0.0f);
            inner.Size = new Vector2(94.0f, 94.0f);

            byte red = (byte)(0xFF * (0.2f + (_random.NextDouble() / 0.8f)));
            byte green = (byte)(0xFF * (0.2f + (_random.NextDouble() / 0.8f)));
            byte blue = (byte)(0xFF * (0.2f + (_random.NextDouble() / 0.8f)));
            inner.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, red, green, blue));
            outer.Children.InsertAtTop(inner);

            element.Opacity = 0.8f;
            return element;
        }

        private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
        {
            Point position = e.GetCurrentPoint(MyHost).Position;
            _currentVisual = null;

            foreach (var child in _root.Children)
            {
                Vector3 offset = child.Offset;
                Vector2 size = child.Size;

                if ((position.X >= offset.X) &&
                    (position.X < offset.X + size.X) &&
                    (position.Y >= offset.Y) &&
                    (position.Y < offset.Y + size.Y))
                {
                    _currentVisual = child as ContainerVisual;
                    _offsetBias = new Vector2(
                        (float)(offset.X - position.X),
                        (float)(offset.Y - position.Y));
                }
            }

            if (_currentVisual != null)
            {
                ContainerVisual parent = (ContainerVisual)_currentVisual.Parent;
                parent.Children.Remove(_currentVisual);
                parent.Children.InsertAtTop(_currentVisual);
            }
        }

        private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
        {
            if (_currentVisual == null)
            {
                return;
            }

            if (!_dragging)
            {
                _currentVisual.Opacity = 1.0f;

                foreach (var child in _currentVisual.Children)
                {
                    child.RotationAngleInDegrees = 45.0f;
                    child.CenterPoint = new Vector3(_currentVisual.Size.X / 2, _currentVisual.Size.Y / 2, 0.0f);
                    break;
                }

                var clip = _compositor.CreateInsetClip();
                clip.LeftInset = 1.0f;
                clip.RightInset = 1.0f;
                clip.TopInset = 1.0f;
                clip.BottomInset = 1.0f;
                _currentVisual.Clip = clip;

                _dragging = true;
            }

            Point position = e.GetCurrentPoint(MyHost).Position;
            _currentVisual.Offset = new Vector3(
                (float)(position.X + _offsetBias.X),
                (float)(position.Y + _offsetBias.Y),
                0.0f);
        }

        private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
        {
            if (_currentVisual == null)
            {
                return;
            }

            if (_dragging)
            {
                foreach (var child in _currentVisual.Children)
                {
                    child.RotationAngle = 0.0f;
                    child.CenterPoint = new Vector3(0.0f, 0.0f, 0.0f);
                    break;
                }

                _currentVisual.Opacity = 0.8f;
                _currentVisual.Clip = null;
                _dragging = false;
            }

            _currentVisual = null;
        }
    }
}

Jeśli musisz zainicjować kompozytor przed utworzeniem elementu hosta XAML, użyj funkcji CompositionTarget.GetCompositorForCurrentThread(), a następnie przypnij wizualizacje do interfejsu użytkownika, gdy element hosta stanie się dostępny.