合成视觉对象
合成视觉对象组成了合成 API 的所有其他功能均可使用和基于的可视化树结构。 该 API 允许开发人员定义并创建一个或多个可视化对象,其中每个对象表示可视化树中的单个节点。
视觉对象
可视树结构的组成部分包括以下多种视觉类型,以及影响视觉对象内容的多个子类的基本画笔类:
- Visual – 基对象,大部分属性均位于此处且继承自其他视觉对象。
- ContainerVisual – 派生自视觉对象,并添加了创建子视觉对象的功能。
- SpriteVisual – 派生自 ContainerVisual。 添加了关联画笔的功能,以便视觉对象可以呈现像素,包括图像、效果或纯色。
- LayerVisual – 派生自 ContainerVisual。 视觉对象的子元素将平展为单个层。
(Windows 10 版本 1607 SDK 14393 中引入的。) - ShapeVisual – 派生自 ContainerVisual。 一个可视化树节点,它是 CompositionShape 的根。
(Windows 10 版本 1803 SDK 17134 中引入的。) - RedirectVisual – 派生自 ContainerVisual。 视觉对象从另一个视觉对象获取其内容。
(Windows 10 版本 1809 SDK 17763 中引入的。) - SceneVisual – 派生自 ContainerVisual。 适用于 3D 场景的节点的容器视觉对象。
(Windows 10 版本 1903 SDK 18362 中引入的。)
可以使用 CompositionBrush 及其子类(包括 CompositionColorBrush、CompositionSurfaceBrush 和 CompositionEffectBrush)将内容和效果应用于 SpriteVisual。 若要了解有关画笔的详细信息,请参阅 CompositionBrush 概述。
CompositionVisual 示例
在此,我们将看一些演示前面列出的三种不同视觉类型的示例代码。 尽管此示例不介绍诸如动画或更复杂的效果等概念,但它包含所有这些系统需要使用的构成要素。 (本文的结尾列出了完整的示例代码)。
在该示例中,提供大量可以在屏幕上单击和拖动的纯色正方形。 在单击某一正方形后它将凸显, 将其旋转 45 度,然后在拖动该正方形时它将变为不透明。
此处显示了大量关于使用 API 的基本概念,其中包括:
- 创建合成器
- 使用 CompositionColorBrush 创建 SpriteVisual
- 剪裁可视对象
- 旋转可视对象
- 设置不透明度
- 更改集合中视觉对象的位置。
创建合成器
创建 Compositor 并存储它以用作某一变量中的工厂对象是一项简单的任务。 以下代码段演示了如何创建新的 Compositor:
_compositor = new Compositor();
创建 SpriteVisual 和 ColorBrush
使用 Compositor 可随时根据需要轻松创建对象,如 SpriteVisual 和 CompositionColorBrush:
var visual = _compositor.CreateSpriteVisual();
visual.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
虽然该代码段只有几行代码,但它演示了一个强大的概念:即 SpriteVisual 对象,此类对象是效果系统的核心。 在创建颜色、图形和效果方面,SpriteVisual 具有出色的灵活性和互动性。 SpriteVisual 是单一视觉对象类型,该对象可以使用画笔填充 2D 矩形;在本示例中为纯色。
剪裁可视对象
Compositor 还可以用于创建 Visual 的剪裁。 下面的例子来自于使用 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 也可以将动画应用到其属性。
旋转剪裁
Visual 可以通过旋转进行转换。 请注意,RotationAngle 同时支持弧度和度数。 其默认采用弧度为单位,不过也可以轻松指定以度数为单位,如以下代码段中所示:
child.RotationAngleInDegrees = 45.0f;
Rotation 仅仅是 API 提供一组转换组件以供简化这些任务的示例之一。 此外,还包括 Offset、Scale、Orientation、RotationAxis 和 4x4 TransformMatrix。
设置不透明度
使用浮点值可轻松设置视觉对象的不透明度。 例如,在该示例中所有正方形的不透明度一开始都为 .8:
visual.Opacity = 0.8f;
与 Rotation 一样,Opacity 属性也进行动画处理。
更改集合中视觉对象的位置
借助合成 API,可以采用多种方式更改 VisualCollection 中视觉对象的位置。 使用 InsertAbove 可将该对象放置在其他视觉对象的上方、使用 InsertBelow 可放置在下方、使用 InsertAtTop 可移动到顶部,而使用InsertAtBottom 可移动到底部。
在示例中,如果在 Visual 对象上单击,该对象将排列到顶部:
parent.Children.InsertAtTop(_currentVisual);
完整示例
在完整示例中,上述所有概念可一起用于构造和浏览 Visual 对象的一个简单树,以便可以在不使用 XAML、WWA 或 DirectX 的情况下更改不透明度。 此示例显示了如何创建和添加子 Visual 对象,以及如何更改属性。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Core;
namespace compositionvisual
{
class VisualProperties : IFrameworkView
{
//------------------------------------------------------------------------------
//
// VisualProperties.Initialize
//
// This method is called during startup to associate the IFrameworkView with the
// CoreApplicationView.
//
//------------------------------------------------------------------------------
void IFrameworkView.Initialize(CoreApplicationView view)
{
_view = view;
_random = new Random();
}
//------------------------------------------------------------------------------
//
// VisualProperties.SetWindow
//
// This method is called when the CoreApplication has created a new CoreWindow,
// allowing the application to configure the window and start producing content
// to display.
//
//------------------------------------------------------------------------------
void IFrameworkView.SetWindow(CoreWindow window)
{
_window = window;
InitNewComposition();
_window.PointerPressed += OnPointerPressed;
_window.PointerMoved += OnPointerMoved;
_window.PointerReleased += OnPointerReleased;
}
//------------------------------------------------------------------------------
//
// VisualProperties.OnPointerPressed
//
// This method is called when the user touches the screen, taps it with a stylus
// or clicks the mouse.
//
//------------------------------------------------------------------------------
void OnPointerPressed(CoreWindow window, PointerEventArgs args)
{
Point position = args.CurrentPoint.Position;
//
// Walk our list of visuals to determine who, if anybody, was selected
//
foreach (var child in _root.Children)
{
//
// Did we hit this child?
//
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))
{
//
// This child was hit. Since the children are stored back to front,
// the last one hit is the front-most one so it wins
//
_currentVisual = child as ContainerVisual;
_offsetBias = new Vector2((float)(offset.X - position.X),
(float)(offset.Y - position.Y));
}
}
//
// If a visual was hit, bring it to the front of the Z order
//
if (_currentVisual != null)
{
ContainerVisual parent = _currentVisual.Parent as ContainerVisual;
parent.Children.Remove(_currentVisual);
parent.Children.InsertAtTop(_currentVisual);
}
}
//------------------------------------------------------------------------------
//
// VisualProperties.OnPointerMoved
//
// This method is called when the user moves their finger, stylus or mouse with
// a button pressed over the screen.
//
//------------------------------------------------------------------------------
void OnPointerMoved(CoreWindow window, PointerEventArgs args)
{
//
// If a visual is selected, drag it with the pointer position and
// make it opaque while we drag it
//
if (_currentVisual != null)
{
//
// Set up the properties of the visual the first time it is
// dragged. This will last for the duration of the drag
//
if (!_dragging)
{
_currentVisual.Opacity = 1.0f;
//
// Transform the first child of the current visual so that
// the image is rotated
//
foreach (var child in _currentVisual.Children)
{
child.RotationAngleInDegrees = 45.0f;
child.CenterPoint = new Vector3(_currentVisual.Size.X / 2, _currentVisual.Size.Y / 2, 0);
break;
}
//
// Clip the visual to its original layout rect by using an inset
// clip with a one-pixel margin all around
//
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 = args.CurrentPoint.Position;
_currentVisual.Offset = new Vector3((float)(position.X + _offsetBias.X),
(float)(position.Y + _offsetBias.Y),
0.0f);
}
}
//------------------------------------------------------------------------------
//
// VisualProperties.OnPointerReleased
//
// This method is called when the user lifts their finger or stylus from the
// screen, or lifts the mouse button.
//
//------------------------------------------------------------------------------
void OnPointerReleased(CoreWindow window, PointerEventArgs args)
{
//
// If a visual was selected, make it transparent again when it is
// released and restore the transform and clip
//
if (_currentVisual != null)
{
if (_dragging)
{
//
// Remove the transform from the first child
//
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;
}
}
//------------------------------------------------------------------------------
//
// VisualProperties.Load
//
// This method is called when a specific page is being loaded in the
// application. It is not used for this application.
//
//------------------------------------------------------------------------------
void IFrameworkView.Load(string unused)
{
}
//------------------------------------------------------------------------------
//
// VisualProperties.Run
//
// This method is called by CoreApplication.Run() to actually run the
// dispatcher's message pump.
//
//------------------------------------------------------------------------------
void IFrameworkView.Run()
{
_window.Activate();
_window.Dispatcher.ProcessEvents(CoreProcessEventsOption.ProcessUntilQuit);
}
//------------------------------------------------------------------------------
//
// VisualProperties.Uninitialize
//
// This method is called during shutdown to disconnect the CoreApplicationView,
// and CoreWindow from the IFrameworkView.
//
//------------------------------------------------------------------------------
void IFrameworkView.Uninitialize()
{
_window = null;
_view = null;
}
//------------------------------------------------------------------------------
//
// VisualProperties.InitNewComposition
//
// This method is called by SetWindow(), where we initialize Composition after
// the CoreWindow has been created.
//
//------------------------------------------------------------------------------
void InitNewComposition()
{
//
// Set up Windows.UI.Composition Compositor, root ContainerVisual, and associate with
// the CoreWindow.
//
_compositor = new Compositor();
_root = _compositor.CreateContainerVisual();
_compositionTarget = _compositor.CreateTargetForCurrentView();
_compositionTarget.Root = _root;
//
// Create a few visuals for our window
//
for (int index = 0; index < 20; index++)
{
_root.Children.InsertAtTop(CreateChildElement());
}
}
//------------------------------------------------------------------------------
//
// VisualProperties.CreateChildElement
//
// Creates a small sub-tree to represent a visible element in our application.
//
//------------------------------------------------------------------------------
Visual CreateChildElement()
{
//
// Each element consists of three visuals, which produce the appearance
// of a framed rectangle
//
var element = _compositor.CreateContainerVisual();
element.Size = new Vector2(100.0f, 100.0f);
//
// Position this visual randomly within our window
//
element.Offset = new Vector3((float)(_random.NextDouble() * 400), (float)(_random.NextDouble() * 400), 0.0f);
//
// The outer rectangle is always white
//
//Note to preview API users - SpriteVisual and Color Brush replace SolidColorVisual
//for example instead of doing
//var visual = _compositor.CreateSolidColorVisual() and
//visual.Color = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
//we now use the below
var visual = _compositor.CreateSpriteVisual();
element.Children.InsertAtTop(visual);
visual.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
visual.Size = new Vector2(100.0f, 100.0f);
//
// The inner rectangle is inset from the outer by three pixels all around
//
var child = _compositor.CreateSpriteVisual();
visual.Children.InsertAtTop(child);
child.Offset = new Vector3(3.0f, 3.0f, 0.0f);
child.Size = new Vector2(94.0f, 94.0f);
//
// Pick a random color for every rectangle
//
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)));
child.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, red, green, blue));
//
// Make the subtree root visual partially transparent. This will cause each visual in the subtree
// to render partially transparent, since a visual's opacity is multiplied with its parent's
// opacity
//
element.Opacity = 0.8f;
return element;
}
// CoreWindow / CoreApplicationView
private CoreWindow _window;
private CoreApplicationView _view;
// Windows.UI.Composition
private Compositor _compositor;
private CompositionTarget _compositionTarget;
private ContainerVisual _root;
private ContainerVisual _currentVisual;
private Vector2 _offsetBias;
private bool _dragging;
// Helpers
private Random _random;
}
public sealed class VisualPropertiesFactory : IFrameworkViewSource
{
//------------------------------------------------------------------------------
//
// VisualPropertiesFactory.CreateView
//
// This method is called by CoreApplication to provide a new IFrameworkView for
// a CoreWindow that is being created.
//
//------------------------------------------------------------------------------
IFrameworkView IFrameworkViewSource.CreateView()
{
return new VisualProperties();
}
//------------------------------------------------------------------------------
//
// main
//
//------------------------------------------------------------------------------
static int Main(string[] args)
{
CoreApplication.Run(new VisualPropertiesFactory());
return 0;
}
}
}