通过


合成视觉元素

合成视觉对象构成了Microsoft.UI.Composition API 使用和构建的所有其他功能的可视化树结构。 API 允许开发人员定义和创建一个或多个视觉对象,每个对象表示可视化树中的单个节点。

视觉效果

有多种可视化类型组成可视化树结构,以及具有多个子类的基画笔类,这些子类会影响视觉对象的内容:

可以使用 CompositionBrush 及其子类(包括 CompositionColorBrushCompositionSurfaceBrushCompositionEffectBrush)将内容和效果应用于 SpriteVisuals。 若要了解有关画笔的详细信息,请参阅 CompositionBrush 概述

CompositionVisual 示例

在这里,我们将介绍一些 WinUI 3 示例代码,该代码演示了前面列出的三种不同的视觉类型。 虽然此示例不包含动画或更复杂的效果等概念,但它包含所有这些系统使用的构建基块。 (本文末尾列出了完整的示例代码。

在这个示例中,屏幕上有多个纯色方块,可以单击并拖动。 当点击方块时,它会显示在最前面,旋转 45 度,并在被拖动时变得不透明。

这显示了使用 API 的一些基本概念,包括:

  • 创建图像合成器
  • 使用 CompositionColorBrush 创建 SpriteVisual
  • 剪裁视觉对象
  • 旋转视觉对象
  • 设置不透明度
  • 更改视觉对象在集合中的位置。

创建合成器

创建 Compositor 并将其存储在用作工厂的变量中是一项简单的任务。 在 WinUI 应用中,通常从已连接到可视化树的 XAML 元素检索合成器:

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

如果需要合成器并且没有可用的 UIElement,则可以改用 CompositionTarget.GetCompositorForCurrentThread()

创建 SpriteVisual 和 ColorBrush

使用 Compositor ,只要需要对象(例如 SpriteVisualCompositionColorBrush),即可轻松创建对象:

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

虽然这只是几行代码,但它演示了一个强大的概念: SpriteVisual 对象是效果系统的核心。 SpriteVisual 允许在颜色、图像和效果创建方面具有极大的灵活性和交互性。 SpriteVisual 是一种视觉对象类型,可以使用画笔填充 2D 矩形,在本例中为纯色。

剪裁视觉对象

Compositor 还可用于为视觉对象创建剪辑。 下面是使用 InsetClip 剪裁视觉对象每一侧的示例:

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

与 API 中的其他对象一样, InsetClip 可以对其属性应用动画。

旋转剪辑

可以使用旋转来转换 视觉对象 。 请注意,RotationAngle 支持以弧度和度为单位。 它默认使用弧度,但可以轻松指定为角度,如下代码示例所示:

child.RotationAngleInDegrees = 45.0f;

轮换只是 API 提供的一组转换组件的一个示例,可简化这些任务。 其他包括 Offset、Scale、Orientation、RotationAxis 和 4x4 TransformMatrix。

设置不透明度

使用浮点值设置视觉对象的不透明度是一个简单的操作。 例如,在示例中,所有方块的透明度初始值为0.8。

visual.Opacity = 0.8f;

与旋转一样,可对 不透明度 属性进行动画处理。

更改视觉对象在集合中的位置

合成 API 允许以多种方式更改 VisualCollection 中的视觉 对象位置。 它可以放置在另一个具有 InsertAbove 的视觉对象上方,放置于 InsertBelow 下,使用 InsertAtTop 移动到顶部,或使用 InsertAtBottom 移动到底部。

在该示例中,被单击的 Visual 被排序到顶部。

parent.Children.InsertAtTop(_currentVisual);

完整示例

在完整的 WinUI 示例中,以上所有概念均用于构建和遍历 Visual 对象的简单树,从而在不进行自定义 DirectX 渲染的情况下更改不透明度。 该示例使用 WinUI Grid 来进行指针输入,并使用 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;
        }
    }
}

如果需要在拥有 XAML 主机元素之前初始化合成器,可以使用CompositionTarget.GetCompositorForCurrentThread(),并在主机元素可用时将视觉对象附加到您的用户界面。