合成视觉对象构成了Microsoft.UI.Composition API 使用和构建的所有其他功能的可视化树结构。 API 允许开发人员定义和创建一个或多个视觉对象,每个对象表示可视化树中的单个节点。
视觉效果
有多种可视化类型组成可视化树结构,以及具有多个子类的基画笔类,这些子类会影响视觉对象的内容:
- 视觉 对象 – 基本对象,大多数属性都在此处,并由其他视觉对象继承。
-
ContainerVisual – 派生自 Visual,并增加了创建子级的功能。
- SpriteVisual – 派生自 ContainerVisual。 能够关联画笔,以便视觉对象可以呈现像素,包括图像、效果或纯色。
- LayerVisual – 派生自 ContainerVisual。 视觉元素的子级合并为单个层。
- ShapeVisual – 派生自 ContainerVisual。 作为 CompositionShape 根节点的视觉树节点。
- RedirectVisual – 派生自 ContainerVisual。 视觉对象从另一个视觉对象获取其内容。
- SceneVisual – 派生自 ContainerVisual。 3D 场景节点的容器视觉对象。
可以使用 CompositionBrush 及其子类(包括 CompositionColorBrush、CompositionSurfaceBrush 和 CompositionEffectBrush)将内容和效果应用于 SpriteVisuals。 若要了解有关画笔的详细信息,请参阅 CompositionBrush 概述。
CompositionVisual 示例
在这里,我们将介绍一些 WinUI 3 示例代码,该代码演示了前面列出的三种不同的视觉类型。 虽然此示例不包含动画或更复杂的效果等概念,但它包含所有这些系统使用的构建基块。 (本文末尾列出了完整的示例代码。
在这个示例中,屏幕上有多个纯色方块,可以单击并拖动。 当点击方块时,它会显示在最前面,旋转 45 度,并在被拖动时变得不透明。
这显示了使用 API 的一些基本概念,包括:
- 创建图像合成器
- 使用 CompositionColorBrush 创建 SpriteVisual
- 剪裁视觉对象
- 旋转视觉对象
- 设置不透明度
- 更改视觉对象在集合中的位置。
创建合成器
创建 Compositor 并将其存储在用作工厂的变量中是一项简单的任务。 在 WinUI 应用中,通常从已连接到可视化树的 XAML 元素检索合成器:
Compositor compositor = ElementCompositionPreview.GetElementVisual(MyHost).Compositor;
如果需要合成器并且没有可用的 UIElement,则可以改用 CompositionTarget.GetCompositorForCurrentThread() 。
创建 SpriteVisual 和 ColorBrush
使用 Compositor ,只要需要对象(例如 SpriteVisual 和 CompositionColorBrush),即可轻松创建对象:
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(),并在主机元素可用时将视觉对象附加到您的用户界面。