Skip to content

变换(Transforms) ​

Translate、Scale 和 Rotate 会在视觉上改变一个元素及其内部的一切, 而布局仍为它保留原来的空间。它们适用于盒子、文本、图片、控件和自定义 绘制。指针跟随实际绘制的内容,包括嵌套的变换与裁剪:

go
ui.Column(c).Size(180, 110).Padding(12).Gap(8).
	Scale(1.1, 0.9).Rotate(12).Translate(40, 0).
	Children(func() {
		ui.Text(c, "Transformed controls")
		ui.TextInput(c, &app.name)
	})

方法链中的变换按调用顺序生效:本例先缩放,再旋转,最后平移。父元素的 变换在子元素的变换之后生效。每个缩放和旋转默认以元素边框盒的中心为 原点。TransformOrigin(0, 0) 使用其左上角;(1, 1) 使用其右下角。 也允许其他小数,包括盒子之外的值。负缩放会翻转内容;零缩放使元素 不可见,并且不再有指针命中区域。变换的值必须是有限的。

对于可复用的组合,构建一个 Transform 并用 Transform 设置它:

go
pose := ui.Scaling(1.2, 1.2).Then(ui.Rotation(15)).Then(ui.Translation(30, 0))
ui.Box(c).Transform(pose).TransformOrigin(0, 0)

零值 Transform 是恒等变换。设置 Transform 会替换元素先前的变换; 便捷方法则在其后追加。在视图中、帧布局之前设置变换。对图标使用 Rotate 保持其现有行为,即围绕中心旋转 SVG 图形。要旋转图标的整个 边框盒,使用 Transform(ui.Rotation(degrees))。

斜切与 3D ​

Skew(x, y) 沿每个轴按度数倾斜一个元素,如同 CSS 的 skew()。 RotateX 和 RotateY 让它围绕经过原点的水平轴和垂直轴在 3D 中转动, 在它们之后调用 Perspective(distance) 就像从前方该数量的 DIP 处观看: 距离越短,透视越强烈;800 左右看起来自然。没有 Perspective 时, 转动后的元素只会显得更窄或更矮:

go
// 一张翻转的卡片:它的背面再多转半圈,在超过 90 度后显示出来,
// 每个面在转离视线时都会隐藏。
card := ui.Box(c).Size(160, 100)
turn := float32(0)
if app.flipped {
	turn = 180
}
turn = card.AnimateTo("turn", turn, ui.Snappy)
card.Children(func() {
	front := ui.Box(c).Background(t.Surface)
	back := ui.Box(c).Background(t.Accent)
	front.Absolute().Fill().RotateY(turn).Perspective(800).BackfaceHidden()
	back.Absolute().Fill().RotateY(turn + 180).Perspective(800).BackfaceHidden()
})

Transform 值以同样的方式组合,使用 Skewing、RotationX、 RotationY 和 Perspective。每个元素都会把其 3D 变换产生的效果压平到 父元素的平面上,如同 CSS 默认的 transform-style: flat:父元素在 3D 中转动时,会带着子元素的绘制一起转动。当元素自身的 3D 变换把背面转向 观看者时,BackfaceHidden 会隐藏该元素,并使其不再接收指针。

指针、裁剪、文本输入和无障碍对 3D 变换的跟随方式与 2D 变换相同, 渲染器以透视方式绘制它们,包括平滑的边缘。

坐标、裁剪与覆盖层 ​

Bounds() 是上一帧经过变换的包围矩形,以窗口的 DIP 表示。 LayoutBounds() 是变换之前的盒子。LocalToWindow 和 WindowToLocal 转换相对于该盒子的点;当变换不存在逆时,WindowToLocal 返回 false。 PointerPosition、Dragged 和 HandleInput 报告本地位置和向量。 即使指针移出变换后的盒子,已捕获的指针输入仍保持本地坐标。

Draw 和 DrawOver 和以前一样接收原始布局坐标:画笔把它们绘制的 一切应用变换,包括路径、文本、图片、阴影和特效。裁剪保留其入栈时所在 的坐标系,并通过嵌套元素组合。输入法候选窗口和无障碍接收光标与元素 变换后的包围矩形。弹出面板在覆盖层中保持竖直,并附着到其锚点变换后的 边界,布局过渡期间也是如此。

变换的动画 ​

变换既接受固定值,也接受动画值:保持速度的弹簧、曲线、关键帧,或 跟随指针的值(参见动效):

go
tile := ui.Box(c).Key("tile").Size(80, 50)
target := float32(0)
if app.expanded {
	target = 120
}
tile.Translate(tile.AnimateTo("x", target, ui.Snappy), 0)

过渡对进场的元素进行缩放和旋转,并让一个元素从 另一个元素先前显示的位置出现(Match),同样使用变换。

渲染 ​

Metal、Direct3D 11 和 OpenGL 在 GPU 上把通用变换应用于它们的实例化 几何。填充、边框、阴影、图片、文本和特效保持本地坐标;背景读取会映射 回窗口坐标。Metal 在变换下保留广色域颜色。

嵌套的圆角或变换裁剪使用保留的覆盖掩码。在静止裁剪下移动的内容复用 该掩码,无需 CPU 逐像素工作。与内容共享坐标系的硬裁剪(包括变换后的 文本输入)直接在片元着色器中求值,并复用其祖先的掩码。修改复杂裁剪会 重新计算其覆盖;调整其大小会就地替换其纹理,而不是累积缓存图像。

对于没有 GPU 的机器和静止时的小幅更新,CPU 合成器仍是渲染器。在有 GPU 可用时,活跃的变换动画使用 GPU。文本位图在变换时以双线性方式 采样,变换后的文本使用灰度覆盖。GPU 抗锯齿在设备像素坐标上遵循 CPU 渲染器的行为。要清晰地缩放整个窗口,请使用内容的缩放 (Context.SetZoom,参见窗口),而不是变换。

示例画廊的 Motion 页面展示嵌套变换的可编辑控件、锚定的弹出面板、 重定目标与被抛出的弹簧、关键帧序列和共享元素。