ACE Engine FrameNode 节点

1 概述

FrameNode 是 ArkUI NG 中 "组件构建的 UI 节点,是组件树里的"实体节点"(有布局、有渲染、有事件),继承自:

  • AceTypeReferencedUINodeFrameNode
  • 同时继承 LayoutWrapper(让它可以直接参与 NG layout pipeline)

可以理解为:

  • UINode:只管"树结构 + 生命周期"(谁是父、谁是子、挂到哪棵树上)
  • FrameNode:在此基础上,加上 布局、渲染、事件、可见性、Inspector 等完整 UI 能力
  • 源码位置~/frameworks/core/components_ng/base/frame_node.h
  • 典型实例Text, Button, Column 等组件的节点,最终都落在不同类型的 FrameNode

实质上, 我们所有的前端目前所有的组件都会反馈在这里, 不论是arkui、 cangjieui 或者是腾讯Kuikly(KMP -> CAPI)。

2 类图

3 核心功能

3.1 节点核心行为(Pattern、 GeometryNode、LayoutProperty、 PaintProperty、RenderContext)

scss 复制代码
FrameNode::FrameNode(
    const std::string& tag, int32_t nodeId, const RefPtr<Pattern>& pattern, bool isRoot, bool isLayoutNode)
    : UINode(tag, nodeId, isRoot), LayoutWrapper(WeakClaim(this)), pattern_(pattern)
{
    isLayoutNode_ = isLayoutNode;
    frameProxy_ = std::make_unique<FrameProxy>(this);
    if (IsFree()) {
        renderContext_->SetIsFree(IsFree());
        renderContext_->SetHostNode(WeakClaim(this));
    }
    if (tag == V2::XCOMPONENT_ETS_TAG) {
        renderContext_->InitContext(IsRootNode(), pattern_->GetContextParam(), isLayoutNode);
    } else {
        renderContext_->InitContext(IsRootNode(), pattern_->GetContextParam(), isLayoutNode, this);
    }
    paintProperty_ = pattern->CreatePaintProperty();
    layoutProperty_ = pattern->CreateLayoutProperty();
    // first create make layout property dirty.
    layoutProperty_->UpdatePropertyChangeFlag(PROPERTY_UPDATE_MEASURE);
    layoutProperty_->SetHost(WeakClaim(this));
    layoutSeperately_ = true;
    paintProperty_->SetHost(WeakClaim(this));
    // ...
}

Pattern 是最核心的部分,它决定了 FrameNode 持有什么类型的 LayoutProperty、PaintProperty、EventHub。不同组件(Text、Button、Column)只需要提供不同的 Pattern 子类,重写 CreateLayoutProperty() / CreatePaintProperty() 等工厂方法,就能让同一个 FrameNode 骨架承载不同的组件行为。

3.1.1 Pattern(行为逻辑层)
  • 变量定义 : RefPtr<Pattern> pattern_

  • 职责:

    • 定义组件的行为逻辑:如何响应属性变化、事件、生命周期回调等
    • 创建 LayoutAlgorithm、PaintMethod、EventHub 等
    • 类似 Flutter 的 RenderObject / React 的组件逻辑层
    • 关键方法:

      csharp 复制代码
      // 从 Pattern 获取各种组件
      virtual RefPtr<LayoutProperty> CreateLayoutProperty();
      virtual RefPtr<PaintProperty> CreatePaintProperty();
      virtual RefPtr<LayoutAlgorithm> CreateLayoutAlgorithm();
      virtual RefPtr<NodePaintMethod> CreateNodePaintMethod();
      virtual RefPtr<EventHub> CreateEventHub();
      ​
      // 生命周期回调
      virtual void OnModifyDone();
      virtual void OnAttachToFrameNode();
      virtual void OnDetachFromFrameNode();
    • 使用示例:

      ini 复制代码
      // 获取 Pattern 指针(带类型检查)
      auto* textPattern = frameNode->GetPatternPtr<TextPattern>();
      ​
      // 获取 Pattern 的 RefPtr
      auto textPatternRef = frameNode->GetPattern<TextPattern>();
3.1.2 GeometryNode(几何信息层)
  • 变量定义 : RefPtr<GeometryNode> geometryNode_(通过继承 LayoutWrapper 获得)

  • 职责:

    • 存储节点的几何信息:大小、位置、命中区域等
    • 提供 frame rect 和 content rect 的访问接口
  • 核心数据:

    kotlin 复制代码
    class GeometryNode {
    private:
        SizeF frameSize_;           // 节点整体大小
        OffsetF frameOffset_;       // 节点在父容器中的位置
        SizeF contentSize_;         // 内容区域大小(去除 padding)
        OffsetF contentOffset_;     // 内容区域相对 frame 的偏移
        RectF hitTestRect_;         // 命中测试区域
    };
  • 使用场景:

    scss 复制代码
    // 获取节点大小
    auto size = frameNode->GetGeometryNode()->GetFrameSize();
    ​
    // 获取节点位置
    auto offset = frameNode->GetGeometryNode()->GetFrameOffset();
    ​
    // 获取内容区域
    auto contentRect = frameNode->GetGeometryNode()->GetContentRect();
3.1.3 LayoutProperty(布局属性层)
  • 变量定义 : RefPtr<LayoutProperty> layoutProperty_

  • 职责:

    • 管理布局相关属性:margin/padding/flex/align 等
    • 提供属性变更标记(PropertyChangeFlag)
    • 存储 LayoutConstraint
  • 核心属性:

    arduino 复制代码
    class LayoutProperty : public Property {
    private:
        std::optional<CalcSize> calcSize_;
        std::optional<CalcLength> calcLayoutConstraint_;
        std::optional<PaddingProperty> padding_;
        std::optional<MarginProperty> margin_;
        std::optional<FlexProperty> flexProperty_;
        std::optional<AlignDirection> align_;
        std::optional<VisibleType> visibility_;
        // ... 更多布局属性
    };
  • 使用示例:

    scss 复制代码
    // 更新布局属性
    auto layoutProp = frameNode->GetLayoutPropertyPtr<LayoutProperty>();
    layoutProp->UpdateMargin(MarginProperty { .left = 10.0f });
    ​
    // 触发重新测量
    frameNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
3.1.4 PaintProperty(绘制属性层)
  • 变量定义 : RefPtr<PaintProperty> paintProperty_

  • 职责:

    • 管理绘制相关属性:颜色、边框、阴影、透明度等
    • 通过 RenderContext 间接使用
  • 核心属性:

    arduino 复制代码
    class PaintProperty : public Property {
    private:
        std::optional<Color> backgroundColor_;
        std::optional<BorderWidthProperty> borderWidth_;
        std::optional<BorderColorProperty> borderColor_;
        std::optional<BorderRadiusProperty> borderRadius_;
        std::optional<Shadow> shadow_;
        std::optional<float> opacity_;
        // ... 更多绘制属性
    };
3.1.5 RenderContext(渲染上下文层)
  • 变量定义 : RefPtr<RenderContext> renderContext_

  • 职责:

    • 封装 RSNode(RenderService Node)
    • 管理绘制命令和渲染状态
    • 控制真正的绘制节点
  • 核心功能:

    arduino 复制代码
    class RenderContext {
    public:
        // 初始化 RSNode
        void InitContext(bool isRoot, std::optional<ContextParam> param);
        
        // 同步几何属性到 RSNode
        void SyncGeometryProperties(const RectF& rect, const RectF& contentRect);
        
        // 刷新绘制内容
        void FlushContentModifier(const RefPtr<PaintWrapper>& wrapper);
        
        // 管理 RSNode 树
        void AddChild(const RefPtr<RenderContext>& child, int32_t slot = -1);
        void RemoveChild(const RefPtr<RenderContext>& child);
    };
  • 使用示例:

    scss 复制代码
    // 获取 RenderContext
    auto renderContext = frameNode->GetRenderContext();
    ​
    // 设置背景色
    renderContext->UpdateBackgroundColor(Color::RED);
    ​
    // 设置透明度
    renderContext->UpdateOpacity(0.5f);

3.2 树结构与子节点(UINode进行了扩展)

UINodechildren_ 基础上,FrameNode 额外维护了一套 按 ZIndex 排序的 Frame 子节点集合

UINode::children_(结构树/逻辑树):

  • 类型:std::list<RefPtr>
  • 按 添加顺序 排列
  • 包含 所有节点类型:FrameNode、SyntaxNode(ForEach、If 等语法节点)、CustomNode 等
  • 用途:维护 DSL 声明的逻辑结构,做 diff、更新、遍历

FrameNode::frameChildren_(渲染树):

  • 类型:std::multiset<WeakPtr, ZIndexComparator>
  • 按 ZIndex 排序
  • 只包含 FrameNode(过滤掉了所有语法节点)
  • 用途:渲染绘制、命中测试

这个顺序不同会影响两个核心问题:

  • 渲染顺序

    RebuildRenderContextTree 按 frameChildren_ 的顺序重建 RSNode 树

    scss 复制代码
    void FrameNode::RebuildRenderContextTree()
    {
        // This function has a mirror function (XxxMultiThread) and needs to be modified synchronously.
        FREE_NODE_CHECK(this, RebuildRenderContextTree);
        if (!needSyncRenderTree_) {
            return;
        }
        auto pipeline = GetContextRefPtr();
        if (pipeline && !pipeline->CheckThreadSafe()) {
            LOGW("RebuildRenderContextTree doesn't run on UI thread!");
        }
        auto oldFrameChildren = std::move(frameChildren_);
        frameChildren_.clear();
        std::list<RefPtr<FrameNode>> children;
        // generate full children list, including disappear children.
        GenerateOneDepthVisibleFrameWithTransition(children);
        if (overlayNode_) {
            auto property = overlayNode_->GetLayoutProperty();
            if (property && property->GetVisibilityValue(VisibleType::VISIBLE) == VisibleType::VISIBLE) {
                children.push_back(overlayNode_);
            }
        }
        if (accessibilityFocusPaintNode_) {
            children.push_back(accessibilityFocusPaintNode_);
        }
        for (const auto& child : children) {
            frameChildren_.emplace(child);
        }
    ​
        // Notify AttachToRenderTree / DetachFromRenderTree.
        ProcessRenderTreeDiff(children, oldFrameChildren);
        oldFrameChildren.clear();
    ​
        renderContext_->RebuildFrame(this, children);
    }    
  • 命中测试顺序

    • TouchTest 按 frameChildren_ 反向遍历(ZIndex 从高到低),确保上层元素优先响应触摸

      less 复制代码
          for (auto iter = frameChildren_.rbegin(); iter != frameChildren_.rend(); ++iter) {
              if (GetHitTestMode() == HitTestMode::HTMBLOCK || GetHitTestMode() == HitTestMode::HTMBLOCK_DESCENDANTS) {
                  break;
              }

3.3 标脏

脏标记系统是 FrameNode 驱动 NG Pipeline 的核心机制。当属性发生变化时,FrameNode 通过 MarkDirtyNode 将自身注册到 PipelineContext 的脏节点列表中,Pipeline 在下一帧统一处理这些脏节点,执行布局或渲染。

  • 需要标记的内容

    PropertyChangeFlag 是一组位标志,每个 bit 代表一种变更类型。按功能可分为三类:

    测量类(影响节点尺寸,需要重新 Measure):

    布局类(不影响尺寸,只影响位置):

    渲染类(不影响布局,只影响绘制):

    特殊标志:延迟标脏

  • 标脏

    scss 复制代码
    void FrameNode::MarkDirtyNode(PropertyChangeFlag extraFlag)
    {
        // 分支1:冻结状态 → 只存 flag,不触发 Pipeline
        if (IsFreeze()) {
            layoutProperty_->UpdatePropertyChangeFlag(extraFlag);
            paintProperty_->UpdatePropertyChangeFlag(extraFlag);
            return;
        }
        // 分支2:PROPERTY_UPDATE_DIFF → 延迟标脏,加入 DirtyPropertyNode 列表
        if (CheckNeedMakePropertyDiff(extraFlag)) {
            if (isPropertyDiffMarked_) return;
            context->AddDirtyPropertyNode(Claim(this));
            isPropertyDiffMarked_ = true;
            return;
        }
        // 分支3:正常标脏 → 进入核心层
        MarkDirtyNode(IsMeasureBoundary(), IsRenderBoundary(), extraFlag);
    }
    scss 复制代码
    // frame_node.cpp:3107
    void FrameNode::MarkDirtyNode(bool isMeasureBoundary, bool isRenderBoundary, PropertyChangeFlag extraFlag)
    {
        // 1. 将 flag 累积到 layoutProperty_ 和 paintProperty_ 上
        layoutProperty_->UpdatePropertyChangeFlag(extraFlag);
        paintProperty_->UpdatePropertyChangeFlag(extraFlag);
    ​
        auto layoutFlag = layoutProperty_->GetPropertyChangeFlag();
        auto paintFlag = paintProperty_->GetPropertyChangeFlag();
        if (CheckNoChanged(layoutFlag | paintFlag)) return;
    ​
        // 2. 优先判断是否需要布局(布局优先级 > 渲染)
        if (CheckNeedRequestMeasureAndLayout(layoutFlag)) {
            // 2a. 非边界节点 → 向上传播给父节点
            if (!isMeasureBoundary && IsNeedRequestParentMeasure()) {
                if (RequestParentDirty()) return;  // 父节点接管,自己不入队
            }
            // 2b. 到达边界 或 无父节点 → 自己入队
            if (isLayoutDirtyMarked_) return;  // 防重复
            isLayoutDirtyMarked_ = true;
            context->AddDirtyLayoutNode(Claim(this));
            return;
        }
    ​
        // 3. 不需要布局,只需要渲染
        layoutProperty_->CleanDirty();
        MarkNeedRender(isRenderBoundary);
    }
  • 什么时候中断:

    • Scroll、List 等容器组件天然是测量边界 Scroll、List 等容器组件天然是测量边界
    • 拥有独立渲染层的组件
    • 具有固定尺寸的节点------子节点尺寸变化不影响它
相关推荐
郑鱼咚5 小时前
现在的AI热潮,恰恰证明了这个世界就是个草台班子
前端·人工智能·程序员
Striver_5 小时前
elpis总结——基于koa的elpis-core
前端
阿慧勇闯大前端5 小时前
在AI时代,再去了解react19新特性还有用吗? 最近总有朋友问我:“现在AI写代码这么厉害了,我写个需求丢给ChatGPT,几秒钟就生成一堆组件,还学新特
前端·react.js
秋水无痕5 小时前
从零搭建个人博客系统:Spring Boot 多模块实践详解
前端·javascript·后端
陆枫Larry6 小时前
图片预览前先 filter 掉空地址:一个容易忽略的细节
前端
我叫蒙奇6 小时前
rem 适配全过程
前端
陆枫Larry6 小时前
小程序中按固定宽高比展示图片并去除黑边的实现思路
前端
HelloReader6 小时前
Tauri 2.1 新特性自定义 HTTP Headers 配置详解
前端
一点一一6 小时前
从输入URL到页面加载:浏览器多进程/线程协同的完整逻辑
前端·面试