图层系统
一、为什么需要图层系统
Canvas API 是命令式的:调用 drawRect,立刻画一个矩形;下一帧如果还想要这个矩形,必须再调用一次。这没有任何问题——直到你需要处理以下场景:
- 持久化对象:一个按钮存在于整个会话中,不需要每帧重新描述它
- 局部更新:只有一个图标在动,其他内容静止,不应该全量重绘
- 交互:点击测试、坐标转换、子元素查找
- 复杂效果:背景模糊、遮罩、阴影样式需要"知道自己在哪个位置"
命令式 API 在这些场景下会让调用方承担大量状态管理的负担。图层系统的出发点就是解决这个问题:提供一个声明式的场景描述层,你只需告诉系统"场景是什么",渲染系统自动决定"怎么画、画哪部分"。
你的代码描述的是:
root
├─ 背景图层(ImageLayer)
├─ 卡片容器(Layer)
│ ├─ 阴影形状(ShapeLayer)
│ └─ 内容(TextLayer)
└─ 按钮(ShapeLayer)
DisplayList 负责:
- 遍历树结构
- 计算哪些图层发生了变化
- 只重绘变化的部分
- 处理遮罩、效果、混合模式
二、Layer 树结构与类型
Layer 类型总览
| 类型 | 职责 |
|---|---|
Layer | 纯容器,自身不绘制内容,用于组织层次 |
ImageLayer | 渲染一张图片 |
ShapeLayer | 渲染矢量形状,支持多填充 / 多描边叠加 |
SolidLayer | 纯色矩形,最轻量 |
TextLayer | 文字排版与渲染 |
MeshLayer | 自定义顶点网格 |
VectorLayer | 类 AE Shape Layer 的矢量动画系统(见第七节) |
DisplayList 使用流程
auto displayList = std::make_shared<DisplayList>();
auto root = displayList->root();
// 背景
auto bg = SolidLayer::Make();
bg->setColor(Color::FromRGBA(240, 240, 245, 255));
root->addChild(bg);
// 卡片
auto card = ShapeLayer::Make();
Path path;
path.addRoundRect(Rect::MakeXYWH(20, 20, 360, 200), 16, 16);
card->setShape(Shape::MakeFrom(std::move(path)));
card->setFillStyles({ShapeStyle::Make(Color::White())});
root->addChild(card);
// 渲染
displayList->render(surface);
父子关系 API
parent->addChild(child);
parent->addChildAt(child, 0); // 插入到最底层
parent->replaceChild(oldChild, newChild);
child->removeFromParent();
parent->setChildren({a, b, c}); // 批量替换所有子层
child->setName("avatar");
auto found = parent->getChildByName("avatar");
三、核心属性
图层的位置、透明度、可见性通过以下属性控制。变换有三套方案,它们操作的是同一个底层字段 matrix3D,最后一次调用生效:
setPosition(仅修改平移分量)
setMatrix(用 2D 仿射矩阵替换整个 matrix3D)
setMatrix3D(直接替换整个 matrix3D)
透明度与混合模式
layer->setAlpha(0.8f); // 80% opacity
layer->setBlendMode(BlendMode::Multiply); // multiply blend
layer->setVisible(false); // hidden, not rendered
alpha 的行为取决于 allowsGroupOpacity 属性(默认 false)。默认情况下,alpha 分别应用到图层自身和各子层上;当设置 allowsGroupOpacity(true) 后,图层及其子层先渲染到离屏纹理,再整体应用 alpha——产生"组合透明度"效果。

变换

// 只需要移动位置
layer->setPosition(Point::Make(100, 200));
// 需要旋转或缩放
Matrix m = Matrix::MakeRotate(45, cx, cy);
layer->setMatrix(m);
// 需要 3D 变换
Matrix3D m3d = Matrix3D::MakeRotate({0, 1, 0}, 30); // rotate 30 degrees around Y axis
layer->setMatrix3D(m3d);
裁剪区域(scrollRect)
// 只显示图层内 (10,10)~(310,210) 区域,常用于滚动容器
layer->setScrollRect(Rect::MakeXYWH(10, 10, 300, 200));

四、ShapeLayer 多样式
Canvas API 每次绘制只能用一个 Paint,想给同一个形状叠加「渐变填充 + 纯色描边 + 外发光」需要画三次,还得处理遮挡顺序。ShapeLayer 解决了这个问题——它支持 fills 列表和 strokes 列表,多个样式按顺序叠加,类似 Figma 里对一个形状加多个填充层:
auto shapeLayer = ShapeLayer::Make();
shapeLayer->setShape(Shape::MakeFrom(path));
// bottom gradient fill
auto fill1 = ShapeStyle::Make(gradientShader);
// top semi-transparent overlay
auto fill2 = ShapeStyle::Make(Color::FromRGBA(255, 255, 255, 40), BlendMode::Screen);
shapeLayer->setFillStyles({fill1, fill2});
// inside stroke (does not affect outer bounds)
auto stroke = ShapeStyle::Make(Color::FromRGBA(0, 0, 0, 60));
shapeLayer->setLineWidth(2.0f);
shapeLayer->setStrokeAlign(StrokeAlign::Inside);
shapeLayer->addStrokeStyle(stroke);
StrokeAlign:描边位置控制

| 模式 | 效果 | 适用场景 |
|---|---|---|
Center(默认) | 描边以路径为中心,内外各一半 | 通用 |
Inside | 描边全在路径内侧,不影响外边界 | 边界贴合布局、设计稿"内描边" |
Outside | 描边全在路径外侧,不缩小内容区域 | 发光效果、外边框 |
五、TextLayer 排版
auto text = TextLayer::Make();
text->setText("Hello\nTGFX");
text->setFont(Font(typeface, 28));
text->setTextColor(Color::Black());
text->setTextAlign(TextAlign::Center);
// 设置排版区域
text->setLayoutWidth(200);
text->setLayoutHeight(100);
text->setAutoWrap(true); // 超出 width 自动换行
autoWrap = true + layoutWidth 配合使用,超出宽度时自动折行;layoutHeight 控制最大高度,超出部分裁剪(不会自动缩小字号)。
六、遮罩与图层效果
遮罩(mask)
让一个图层的形状控制另一个图层的可见范围:
auto maskLayer = ShapeLayer::Make();
// ... 设置遮罩形状 ...
auto content = ImageLayer::Make();
content->setImage(photo);
content->setMask(maskLayer);
content->setMaskType(LayerMaskType::Alpha);
三种 maskType 的行为差别很大,选错了效果就不对:

| maskType | 控制方式 | 典型用途 |
|---|---|---|
Alpha | 遮罩层的 alpha 通道控制可见性 | 渐变消隐、边缘羽化 |
Luminance | 遮罩层的亮度控制可见性 | 照片级光照蒙版 |
Contour | 遮罩层的轮廓做硬边裁切 | 圆角头像、形状裁切 |
实践建议:头像圆角裁切用 Contour;需要渐渐消失的边缘用 Alpha;Luminance 是高级玩法,大多数场景用不到。
LayerFilter vs LayerStyle
这两个都能给图层添加视觉效果,但机制完全不同:

LayerFilter(滤镜):把图层内容绘制到离屏纹理,滤镜处理后替换原内容。
LayerStyle(样式):在图层内容的上方或下方额外叠加视觉元素,不改变原内容。
// LayerFilter:整个图层变模糊
auto blurFilter = BlurFilter::Make(8.0f, 8.0f);
layer->setFilters({blurFilter});
// LayerStyle:在图层下方绘制阴影,原内容不变
auto shadow = DropShadowStyle::Make(10, 10, 15, 15, Color::FromRGBA(150, 150, 170, 255));
layer->setLayerStyles({shadow});
LayerFilter 可用类型:
| 类型 | 工厂方法 |
|---|---|
| 模糊 | BlurFilter::Make(blurX, blurY, tileMode) |
| 投影阴影 | DropShadowFilter::Make(dx, dy, blurX, blurY, color, shadowOnly) |
| 内阴影 | InnerShadowFilter::Make(dx, dy, blurX, blurY, color, shadowOnly) |
| 颜色矩阵 | ColorMatrixFilter::Make(matrix) |
| 混合叠加 | BlendFilter::Make(color, mode) |
LayerFilter 是 LayerProperty(可变属性),可以在渲染后动态修改参数而无需重建:
// 动态修改模糊半径,自动触发重绘
blurFilter->setBlurrinessX(20.0f);
选型原则:需要改变图层内容本身的效果 → LayerFilter;需要在图层外围添加视觉装饰 → LayerStyle。
七、VectorLayer:类 AE 矢量动画系统
Canvas + ShapeLayer 可以画静态矢量图形,但对于"星形伸缩动画"、"路径描绘动画"、"重复元素动画"这类在 After Effects 里常见的矢量动画,用 ShapeLayer 的 fills/strokes 列表表达起来非常繁琐,而且每帧都要重新构建形状。
VectorLayer 提供了一套可动画的矢量元素系统,对应 AE 的 Shape Layer。元素树从几何描述到最终渲染分四个层次:
几何元素(形状/路径/文字)
↓
修饰器(TrimPath / RoundCorner / MergePath / Repeater)
↓
样式(FillStyle / StrokeStyle)
↓
分组(VectorGroup,带独立变换)

几何元素
// 矩形
auto rect = Rectangle::Make();
rect->setSize(Size::Make(200, 100));
rect->setRoundness(12.0f); // 圆角
// 椭圆
auto ellipse = Ellipse::Make();
ellipse->setSize(Size::Make(100, 100));
// 多角星 / 多边形
auto star = Polystar::Make();
star->setPoints(5);
star->setOuterRadius(80.0f);
star->setInnerRadius(32.0f); // 0 = 多边形,> 0 = 星形
// 任意路径
auto shapePath = ShapePath::Make();
shapePath->setPath(myPath);
修饰器
修饰器作用于同一 VectorGroup 内在它之前定义的所有几何元素:
// 路径裁剪动画:end 从 0 到 1,路径逐渐出现
auto trim = TrimPath::Make();
trim->setStart(0.0f);
trim->setEnd(animProgress); // 0 ~ 1,由动画驱动
trim->setTrimType(TrimPathType::Separate); // 每条路径单独裁剪
// 圆角化
auto roundCorner = RoundCorner::Make();
roundCorner->setRadius(8.0f);
// 重复器:把前面的元素复制 N 次,每份应用递增变换
auto repeater = Repeater::Make();
repeater->setCopies(6);
repeater->setRotation(60.0f); // rotate 60 degrees per copy
样式
// 渐变填充
auto gradientSource = Gradient::MakeLinear(
Point::Make(0, 0), Point::Make(200, 0),
{Color::Blue(), Color::Purple()}, {});
auto fill = FillStyle::Make(gradientSource);
fill->setOpacity(0.9f);
fill->setFillRule(FillRule::EvenOdd);
// 虚线描边
auto stroke = StrokeStyle::Make(SolidColor::Make(Color::White()));
stroke->setStrokeWidth(3.0f);
stroke->setDashOffset(0.0f);
stroke->setDashes({12.0f, 6.0f});
VectorGroup 与变换
auto group = VectorGroup::Make();
group->setAnchor(Point::Make(100, 50));
group->setPosition(Point::Make(200, 150));
group->setRotation(45.0f);
group->setScale({1.2f, 1.2f});
group->setElements({rect, fill, trim});
auto vLayer = VectorLayer::Make();
vLayer->setContents({group});
root->addChild(vLayer);
完整示例:路径描绘动画
auto vLayer = VectorLayer::Make();
// 圆形路径
auto ellipse = Ellipse::Make();
ellipse->setSize(Size::Make(200, 200));
// 描边
auto stroke = StrokeStyle::Make(SolidColor::Make(Color::White()));
stroke->setStrokeWidth(4.0f);
// 路径裁剪(end 由外部动画驱动)
auto trim = TrimPath::Make();
trim->setStart(0.0f);
trim->setEnd(progress); // 0 → 1
vLayer->setContents({ellipse, stroke, trim});
// 下一帧更新 progress,VectorLayer 自动重绘变化部分
trim->setEnd(progress + 0.016f);

八、点击测试与坐标转换
// 精确点击测试(true = 按路径形状,false = 按 bounds 快速判断)
bool hit = layer->hitTestPoint(touchX, touchY, true);
// 获取某坐标下所有图层(从顶到底)
auto layers = displayList->root()->getLayersUnderPoint(x, y);
// 坐标转换(root 为全局坐标系)
Point local = layer->globalToLocal(Point::Make(globalX, globalY));
Point global = layer->localToGlobal(Point::Make(localX, localY));
九、3D 图层
默认情况下,即使子图层有 3D 变换,父图层渲染时仍然按文档顺序叠加,不按 3D 深度排序。两张卡片旋转时,后加入的永远在最上面,而不是按真实的 3D 位置决定前后关系。
在父容器开启 preserve3D,建立三维渲染上下文后,子图层会按实际 Z 深度排序合成:
auto container = Layer::Make();
container->setPreserve3D(true); // 建立 3D 渲染上下文
auto card1 = ImageLayer::Make();
card1->setMatrix3D(Matrix3D::MakeTranslate(0, 0, 50)); // Z=50,离观察者更近
auto card2 = ImageLayer::Make();
card2->setMatrix3D(Matrix3D::MakeTranslate(0, 0, -50)); // Z=-50,离观察者更远
container->addChild(card2); // 文档顺序在前
container->addChild(card1); // 文档顺序在后,但 Z 更大,渲染时覆盖 card2
卡片翻转动画:
float angle = animProgress * M_PI; // 0 → π
card->setMatrix3D(Matrix3D::MakeRotate({0, 1, 0}, angle));
// 翻转过半时切换正反面
front->setVisible(angle < M_PI / 2);
back->setVisible(angle >= M_PI / 2);
限制条件:开启了 layerStyles、filters 或 mask 的图层无法参与 3D 合成——这些功能需要独立的离屏纹理,与 3D 深度排序在架构上不兼容。
选型速查
| 场景 | 推荐 |
|---|---|
| 一次性绘制,不需要持久化 | Canvas API |
| 持久化对象、交互、增量更新 | Layer 系统 |
| AE 数据驱动的矢量动画 | VectorLayer |
| 静态矢量形状 + 多填充叠加 | ShapeLayer |
| 描边需要贴合内边界 | StrokeAlign::Inside |
| 背景毛玻璃 | BackgroundBlurStyle |
| 软边缘遮罩 | maskType = Alpha |
| 硬边形状裁切 | maskType = Contour |
| 动态调整效果参数 | LayerFilter(LayerProperty,可变) |
| 3D 卡片翻转 | preserve3D + matrix3D |
