ArkUI节点模型和渲染机制

ArkUI节点模型和渲染机制

HarmonyOS
2024-05-26 16:01:51
浏览
收藏 1
回答 1
待解决
回答 1
按赞同
/
按时间
mafast

节点模型

当前模型是三棵树模型,分别为Component、Element和Render树,其中Component是作为前端UI组件的映射,一个UI组件对应一个Component,从Component生成对应的Element,来维护组件的树形结构和更新,Render被保存在Component和Element中,主要存储节点的属性信息,驱动绘制和更新。

渲染机制

布局

布局的过程就是通过各类布局的算法计算出每个RenderNode在相对空间上的真实大小和位置。

当某个节点的内容发生变化时,就会标记自己为needLayout,并一直向上标记到重绘边界(RepaintBoundary),从重绘边界开始,父节点将LayoutParam传给子节点,子节点据此计算尺寸大小和位置。

render_node.h

public: 
    // Called by parent to perform layout. 
    void Layout(const LayoutParam& layoutParam) 
    { 
        auto pipeline = context_.Upgrade(); 
        if (!pipeline) { 
            LOGE("pipeline is null when layout"); 
            return; 
        } 
  
        bool dipScaleChange = !NearEqual(pipeline->GetDipScale(), dipScale_); 
        dipScale_ = pipeline->GetDipScale(); 
        if (dipScaleChange || layoutParam_ != layoutParam) { 
            layoutParam_ = layoutParam; 
            layoutParamChanged_ = true; 
            SetNeedLayout(true); 
        } 
  
        if (onChangeCallback_) { 
            onChangeCallback_(); 
        } 
        OnLayout(); 
    } 
  
    virtual bool IsRepaintBoundary() const 
    { 
        return IsHeadRenderNode(); 
    } 
  
    // JSview boundary, all nodes in [head, tail] share the same RSNode 
    bool IsHeadRenderNode() const 
    { 
#ifdef ENABLE_ROSEN_BACKEND 
        return SystemProperties::GetRosenBackendEnabled() ? isHeadRenderNode_ : false; 
#else 
        return false; 
#endif 
    }

render_node.cpp

void RenderNode::OnLayout() 
{ 
    auto parent = parent_.Upgrade(); 
    if (parent) { 
        Size parentViewPort = parent->GetChildViewPort(); 
        if (viewPort_ != parentViewPort) { 
            viewPort_ = parentViewPort; 
            needLayout_ = true; 
        } 
    } 
    if (NeedLayout()) { 
        PrepareLayout(); 
        PerformLayout(); 
        layoutParamChanged_ = false; 
        SetNeedLayout(false); 
        pendingDispatchLayoutReady_ = true; 
        MarkNeedRender(); 
    } 
} 
  
void RenderNode::MarkNeedRender(bool overlay) 
{ 
    if (!needRender_) { 
        SetNeedRender(true); 
        if (IsRepaintBoundary()) { 
            auto pipelineContext = context_.Upgrade(); 
            if (pipelineContext) { 
                pipelineContext->AddDirtyRenderNode(AceType::Claim(this), overlay); 
            } 
        } else { 
            auto parent = parent_.Upgrade(); 
            if (parent) { 
                parent->MarkNeedRender(); 
            } 
        } 
    } 
}

render_button.cpp

void RenderButton::PerformLayout() 
{ 
    if (!buttonComponent_) { 
        LOGE("Fail to perform layout due to buttonComponent is null"); 
        return; 
    } 
    minWidth_ = buttonComponent_->GetMinWidth(); 
    type_ = buttonComponent_->GetType(); 
    widthDefined_ = GreatOrEqual(buttonComponent_->GetWidth().Value(), 0.0); 
    heightDefined_ = GreatOrEqual(buttonComponent_->GetHeight().Value(), 0.0); 
    if (type_ == ButtonType::ARC) { 
        width_ = ARC_BUTTON_WIDTH; 
        height_ = ARC_BUTTON_HEIGHT; 
    } 
    buttonSize_ = Size(NormalizePercentToPx(width_, false), NormalizePercentToPx(height_, true)); 
    rrectRadius_ = NormalizeToPx(buttonComponent_->GetRectRadius()); 
    layoutSize_ = Measure(); 
    SetChildrenLayoutSize(); 
    SetLayoutSize(CalculateLayoutSize()); 
    SetChildrenAlignment(); 
    buttonSize_ = GetLayoutSize() - Size(widthDelta_, widthDelta_); 
    if (type_ == ButtonType::CAPSULE) { 
        rrectRadius_ = buttonSize_.Height() / 2; 
    } 
}

绘制

同布局一样,绘制也是一个深度遍历的过程,遍历调用每个RenderNode的Paint方法,此时的绘制只是根据布局算出来的大小和位置,在当前绘制的上下文记录每个节点的绘制命令。

对每个节点,绘制分为四个步骤:

① 如果当前节点需要分层,那么需要创建一个新的绘制上下文,并提供可以记录绘制命令的画布;

② 在当前的画布上记录背景的绘制命令;

③ 递归调用子节点的绘制方法,记录子节点的绘制命令;

④ 在当前的画布上记录前景的绘制命令。

render_node.cpp

void RenderNode::RenderWithContext(RenderContext& context, const Offset& offset) 
{ 
    MarkNeedWindowBlur(false); 
    if (onLayoutReady_ && pendingDispatchLayoutReady_) { 
        onLayoutReady_(std::string("\"layoutReady\",null,null")); 
    } 
    pendingDispatchLayoutReady_ = false; 
    if (GetHasSubWindow() || !GetNeedClip()) { 
        if (context.GetNeedRestoreHole()) { 
            context.Restore(); 
            context.SetNeedRestoreHole(false); 
            context.SetClipHole(Rect()); 
        } 
    } else { 
        context.SetClipHole(context_.Upgrade()->GetTransparentHole()); 
    } 
    Paint(context, offset); 
    for (const auto& item : SortChildrenByZIndex(disappearingNodes_)) { 
        PaintChild(item, context, offset); 
    } 
    auto hasOnAreaChangeCallback = eventExtensions_ ? eventExtensions_->HasOnAreaChangeExtension() : false; 
    if (needUpdateAccessibility_ || hasOnAreaChangeCallback) { 
        auto pipelineContext = context_.Upgrade(); 
        if (pipelineContext != nullptr) { 
            pipelineContext->AddNeedRenderFinishNode(AceType::Claim(this)); 
        } 
    } 
    CheckIfNeedUpdateTouchRect(); 
    SetNeedRender(false); 
} 
  
void RenderNode::Paint(RenderContext& context, const Offset& offset) 
{ 
    const auto& children = GetChildren(); 
    for (const auto& item : SortChildrenByZIndex(children)) { 
        PaintChild(item, context, offset); 
    } 
} 
  
void RenderNode::PaintChild(const RefPtr<RenderNode>& child, RenderContext& context, const Offset& offset) 
{ 
    if (child && child->GetVisible()) { 
        context.PaintChild(child, offset); 
    } 
}

rosen_render_context.cpp

void RosenRenderContext::Repaint(const RefPtr<RenderNode>& node) 
{ 
    if (!ShouldPaint(node) || !node->NeedRender() || node->GetRSNode() == nullptr) { 
        return; 
    } 
  
    auto rsNode = node->GetRSNode(); 
    auto offset = 
        node->GetTransitionPaintRect().GetOffset() - 
        Offset(rsNode->GetStagingProperties().GetFrame().x_, rsNode->GetStagingProperties().GetFrame().y_); 
  
    std::string name = AceType::TypeName(node); 
    if (name != "RosenRenderForm" && name != "RosenRenderPlugin") { 
        InitContext(rsNode, node->GetRectWithShadow(), offset); 
        node->RenderWithContext(*this, offset); 
        UpdateChildren(rsNode); 
    } else { 
        node->RenderWithContext(*this, offset); 
    } 
    StopRecordingIfNeeded(); 
} 
  
void RosenRenderContext::PaintChild(const RefPtr<RenderNode>& child, const Offset& offset) 
{ 
    if (!ShouldPaint(child)) { 
        return; 
    } 
    auto pipelineContext = child->GetContext().Upgrade(); 
    if (!pipelineContext) { 
        LOGE("pipelineContext is null."); 
        return; 
    } 
    bool canChildOverflow = pipelineContext->GetMinPlatformVersion() >= OVERFLOW_PLATFORM_VERSION; 
    Rect rect = child->GetTransitionPaintRect() + offset; 
    if (!(child->IsPaintOutOfParent() || canChildOverflow) && !estimatedRect_.IsIntersectWith(rect)) { 
#if defined(PREVIEW) 
        child->ClearAccessibilityRect(); 
#endif 
        return; 
    } 
  
    auto childRSNode = child->GetRSNode(); 
    if (childRSNode && childRSNode != rsNode_) { 
        childNodes_.emplace_back(childRSNode); 
        std::string name = AceType::TypeName(child); 
        if (name != "RosenRenderForm" && name != "RosenRenderPlugin") { 
            if (child->NeedRender()) { 
                RosenRenderContext context; 
                auto transparentHole = pipelineContext->GetTransparentHole(); 
                if (transparentHole.IsValid() && child->GetNeedClip()) { 
                    Offset childOffset = rect.GetOffset(); 
                    Rect hole = transparentHole - childOffset; 
                    context.SetClipHole(hole); 
                } 
                context.Repaint(child); 
            } else { 
                // No need to repaint, notify to update AccessibilityNode info. 
                child->NotifyPaintFinish(); 
            } 
        } 
        Offset pos = rect.GetOffset(); 
        if (name == "RosenRenderPlugin") { 
            auto renderPlugin = AceType::DynamicCast<RenderSubContainer>(child); 
            if (!renderPlugin) { 
                return; 
            } 
            auto pluginContext = renderPlugin->GetSubPipelineContext(); 
            if (!pluginContext) { 
                return; 
            } 
            auto density = pipelineContext->GetDensity(); 
            Offset pluginOffset = {pos.GetX() / density, pos.GetY() / density}; 
            pluginContext->SetPluginEventOffset(child->GetGlobalOffset()); 
        } 
    } else { 
        child->RenderWithContext(*this, rect.GetOffset()); 
    } 
}
分享
微博
QQ
微信
回复
2024-05-27 21:13:14
相关问题
WebView进程模型渲染机制是什么
805浏览 • 1回复 待解决
组件状态修改与build渲染机制
361浏览 • 1回复 待解决
在Stage模型下如何主动重新渲染UI
1647浏览 • 1回复 已解决
FA模型Stage模型StartAbility使用差异
675浏览 • 1回复 待解决
Stage模型FA模型的区别
1456浏览 • 1回复 待解决
TaskPoolWorker中任务调度机制
724浏览 • 1回复 待解决
鸿蒙插件化热更新机制
1320浏览 • 1回复 待解决
合理选择条件渲染显隐控制
240浏览 • 1回复 待解决
宕机的 Slave 节点如何恢复?
2144浏览 • 1回复 待解决
宕机的 Master 节点如何恢复?
1863浏览 • 1回复 待解决
ArkTS异步机制与执行顺序
296浏览 • 1回复 待解决
鸿蒙是否有预授权机制
2172浏览 • 1回复 待解决
如何实现键盘避让机制
1159浏览 • 1回复 待解决
PolarDB访问时是否可指定节点?
1107浏览 • 1回复 待解决