编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

深入理解 ArkUI - 组件上下树(ar组件下载)

wxchong 2025-01-24 14:16:18 开源技术 53 ℃ 0 评论

深入理解 ArkUI - 组件上下树

组件上下树

Arkui有一颗组件树,同时维护一颗framenode树与Rs渲染树对应。

IF

If分支的上下树发生在分支切换过程中,消失的分支节点下树,新分支走创建流程上树。以一个例子为例:

if (this.isShow) {
    Button()
    .width('100%')
    .height('100%')
}

编译后生成ts代码:

this.observeComponentCreation2((elmtId, isInitialRender) => {
    If.create();
    if (this.isShow) {
        this.ifElseBranchUpdateFunction(0, () => {
            this.observeComponentCreation2((elmtId, isInitialRender) => {
                Button.createWithLabel();
                Button.width('100%');
                Button.height('100%');
            }, Button);
            Button.pop();
        });
    }
    else {
        this.ifElseBranchUpdateFunction(1, () => {
        });
    }
}, If);
If.pop();

前端If对应后端IfElseNode类型实例,其对应关系可以参考之前文章。ifElseBranchUpdateFunction函数时分支切换执行函数,源码如下:

frameworks/bridge/declarative_frontend/engine/stateMgmt.js
ifElseBranchUpdateFunction(branchId, branchfunc) {
    ...
    let removedChildElmtIds = new Array();
    // 关键点1:调用branchId分支切换
    If.branchId(branchId, removedChildElmtIds);
    //un-registers the removed child elementIDs using proxy
    // 关键点2:获取到移除的节点,清理对应的状态变量
    UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
    // purging these elmtIds from state mgmt will make sure no more update function on any deleted child wi;ll be executed
    this.purgeDeletedElmtIds();
    // 关键点3:触发子节点创建
    branchfunc();
    (_b = PUV2ViewBase.arkThemeScopeManager) === null || _b === void 0 ? void 0 : _b.onIfElseBranchUpdateExit(removedChildElmtIds);
}

IF上下树小结:
关键点1:调用
branchId分支切换下树
关键点2:获取到移除的节点,清理对应的状态变量
关键点3:branchfunc触发子节点创建上树


下树(branchId分支切换)

分支切换会执行IfElseNodeSetBranchId函数:

void IfElseNode::SetBranchId(int32_t value, std::list<int32_t>& removedElmtId)
{
    branchIdChanged_ = (branchId_ != value);
    TAG_LOGD(AceLogTag::ACE_IF, "IfElse(%{public}d).SetBranchId branchIdChanged_: %{public}d",
        GetId(), branchIdChanged_);
    if (branchIdChanged_) {
        // collect elmtIds of all children and their children up to CustomNode object
        // these will be removed, but possibly with a delay if their is an animation
        // list of elmtIds is sent back to calling TS ViewPU.ifElseBranchUpdateFunction()
        Clean(false, true, branchId_);
        CollectRemovedChildren(GetChildren(), removedElmtId, true);
        branchId_ = value;
    }
}

Clean函数首先会遍历子组件OnRemoveFromParent下树,后MarkNeedSyncRenderTree同步下渲染树

void UINode::Clean(bool cleanDirectly, bool allowTransition, int32_t branchId)
{
    bool needSyncRenderTree = false;
    int32_t index = 0;

    auto children = GetChildren();
    // 递归遍历子节点下树
    for (const auto& child : children) {
        ...
        if (child->OnRemoveFromParent(allowTransition)) {
            // OnRemoveFromParent returns true means the child can be removed from tree immediately.
            RemoveDisappearingChild(child);
            needSyncRenderTree = true;
        } else {
            // else move child into disappearing children, skip syncing render tree
            AddDisappearingChild(child, index, branchId);
        }
        ++index;
    }

    if (tag_ != V2::JS_IF_ELSE_ETS_TAG) {
        children_.clear();
    }
    // 下渲染树
    MarkNeedSyncRenderTree(true);
}

上树(子节点创建)

branchfunc是前端注册过来的组件创建闭包函数,执行子组件的创建。

this.observeComponentCreation2((elmtId, isInitialRender) => {
    Button.createWithLabel();
    Button.width('100%');
    Button.height('100%');
}, Button);
Button.pop();

ViewStackProcessor是一个单例,栈顶是新创建节点的父节点。
Button.createWithLabel会执行ViewStackProcessor::PushButton.pop会执行ViewStackProcessor::PopPop函数的MountToParent完成子组件的挂树。

frameworks/core/components_ng/base/view_stack_processor.cpp
void ViewStackProcessor::Push(const RefPtr<UINode>& element, bool /*isCustomView*/)
{
    if (ShouldPopImmediately()) {
        Pop();
    }
    element->SetRemoveSilently(false);
    elementsStack_.push(element);
}


void ViewStackProcessor::Pop()
{
    if (elementsStack_.empty() || elementsStack_.size() == 1) {
        return;
    }
    ...
    currentNode->MountToParent(
        parent, DEFAULT_NODE_SLOT, AceType::InstanceOf<ForEachNode>(parent), AceType::InstanceOf<IfElseNode>(parent));
    auto currentFrameNode = AceType::DynamicCast<FrameNode>(currentNode);
    if (currentFrameNode) {
        currentFrameNode->OnMountToParentDone();
    }
}

完整流程:

Visibility.None

当组件设置为Visibility.None,会存储在后端的framenodelayout_property,不会下组件树,只会在None时下渲染树。

  1. 1. 当None设置为非None状态或者非None设置为None状态,需要重新触发MeasureLayout
  2. 2. 非Node场景仅仅需要更新组件树即可。
void LayoutProperty::OnVisibilityUpdate(VisibleType visible, bool allowTransition)
{
    ...
    // if visible is not changed to/from VisibleType::Gone, only need to update render tree.
    if (preVisibility.value_or(VisibleType::VISIBLE) != VisibleType::GONE && visible != VisibleType::GONE) {
        parent->MarkNeedSyncRenderTree();
        parent->RebuildRenderContextTree();
        return;
    }
    UpdatePropertyChangeFlag(PROPERTY_UPDATE_MEASURE);
    parent->MarkNeedSyncRenderTree();
    parent->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
}

滚动组件(list、grid、swiper等)

滚动类组件大多会与LazyForEach或者Repeat组件配合使用,组件上下树管理也托管给LazyForEach或者Repeat。这里只介绍如何触发LazyForEach下子节点上、下树。

Grid组件

Grid组件模块示意图:

GridPattern

GridPattern是在GridFrameNode对象创建的时候关联,用于处理滚动事件、滚动条、边缘效果等,保存了Grid的布局信息在GridLayoutInfo中。

void GridModelNG::Create(const RefPtr<ScrollControllerBase>& positionController, const RefPtr<ScrollProxy>& scrollProxy)
{
    auto* stack = ViewStackProcessor::GetInstance();
    auto nodeId = stack->ClaimNodeId();
    ACE_LAYOUT_SCOPED_TRACE("Create[%s][self:%d]", V2::GRID_ETS_TAG, nodeId);
    auto frameNode =
        FrameNode::GetOrCreateFrameNode(V2::GRID_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr<GridPattern>(); });
    stack->Push(frameNode);
    ...
}

GridLayoutInfo说明gridMatrix_Grid缓存,保存所有Item的索引号currentRect_显示第一行上边缘与显示界面的差值,向上为负,向下为正startMainLineIndex_可视区域第一行的行号endMainLineIndex_可视区域最后一行的行号startIndex_可视区域第一个Item的索引endIndex_可视区域最后一个Item的索引

GridLayoutProperty

继承自LayoutProperty,除了保存了布局相关的通用信息,如宽、高等,还包含rowsTemplateColumnsTemplateGridDirection等信息。

GestureEventHub

保存了Grid的事件方法和拖拽、滚动等相关事件处理函数,持有GestureEventHub对象,用于处理TouchEvent等手势事件。

Algorithm

Measure过程中会创建布局算法,当前会根据不同的行列模板创建不同的布局算法,布局完成后保存布局信息。

// This will call child and self measure process.
void FrameNode::Measure(const std::optional<LayoutConstraintF>& parentConstraint)
{
    ...
    GetLayoutAlgorithm(true);
    ...
}

const RefPtr<LayoutAlgorithmWrapper>& FrameNode::GetLayoutAlgorithm(bool needReset)
{
    if ((!layoutAlgorithm_ || (needReset && layoutAlgorithm_->IsExpire())) && pattern_) {
        layoutAlgorithm_ = MakeRefPtr<LayoutAlgorithmWrapper>(pattern_->CreateLayoutAlgorithm());
    }
    if (needReset) {
        layoutAlgorithm_->SetNeedMeasure();
    }
    return layoutAlgorithm_;
}

GridPaintMethod

在需要生成RenderTask时创建的绘制方法,用于滚动条、边缘阴影效果等绘制。
主要的布局算法包含
GridLayoutAlgorithmGridAdaptiveLayoutAlgorithmGridIrregularLayoutAlgorithmGridScrollLayoutAlgorithmGridScrollWithOptionsLayoutAlgorithm

何时触发LazyForEach上下树?

当容器触发滚动(scrollTo、scrollBy、scrollPage等),会将滚动偏移量设置给GridLayoutInfocurrentOffset_,然后对节点MarkDirtyNode标脏触发Measure.
触底和触顶会重新计算currentOffset_,其他情况减掉偏移。更新操作通过UpdateCurrentOffset完成。

bool GridPattern::UpdateCurrentOffset(float offset, int32_t source)
{
    if (!isConfigScrollable_ || !scrollable_) {
        return true;
    }

    auto host = GetHost();
    CHECK_NULL_RETURN(host, false);

    // check edgeEffect is not springEffect
    if (!HandleEdgeEffect(offset, source, GetContentSize())) {
        if (IsOutOfBoundary(true)) {
            host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
        }
        return false;
    }
    SetScrollSource(source);
    FireAndCleanScrollingListener();
    if (info_.synced_) {
        info_.prevOffset_ = info_.currentOffset_;
        info_.synced_ = false;
    }
    // When finger moves down, offset is positive.
    // When finger moves up, offset is negative.
    bool irregular = UseIrregularLayout();
    float mainGap = GetMainGap();
    auto itemsHeight = info_.GetTotalHeightOfItemsInView(mainGap, irregular);
    if (info_.offsetEnd_) {
        if (source == SCROLL_FROM_UPDATE) {
            float overScroll = 0.0f;
            if (irregular) {
                overScroll = info_.GetDistanceToBottom(GetMainContentSize(), itemsHeight, mainGap);
            } else {
                overScroll = info_.currentOffset_ - (GetMainContentSize() - itemsHeight);
            }
            auto friction = CalculateFriction(std::abs(overScroll) / GetMainContentSize());
            offset *= friction;
        }
        auto userOffset = FireOnWillScroll(-offset);
        info_.currentOffset_ -= userOffset;

        host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);

        if (GreatNotEqual(info_.currentOffset_, GetMainContentSize() - itemsHeight)) {
            info_.offsetEnd_ = false;
            info_.reachEnd_ = false;
        }

        return true;
    }
    if (info_.reachStart_) {
        if (source == SCROLL_FROM_UPDATE) {
            auto friction = CalculateFriction(std::abs(info_.currentOffset_) / GetMainContentSize());
            offset *= friction;
        }
        auto userOffset = FireOnWillScroll(-offset);
        info_.currentOffset_ -= userOffset;

        host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);

        if (LessNotEqual(info_.currentOffset_, 0.0)) {
            info_.reachStart_ = false;
        }
        return true;
    }
    auto userOffset = FireOnWillScroll(-offset);
    info_.currentOffset_ -= userOffset;
    host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
    return true;
}

当下个Vysnc信号到来,渲染管线会根据组件的Pattern,创建布局算法执行对应标脏节点的MeasureLayout方法。

void GridIrregularLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
{
    if (info_.childrenCount_ <= 0) {
        return;
    }
    wrapper_ = layoutWrapper;
    auto props = DynamicCast<GridLayoutProperty>(wrapper_->GetLayoutProperty());

    float mainSize = MeasureSelf(props);
    Init(props);

    if (info_.targetIndex_) {
        MeasureToTarget();
        info_.targetIndex_.reset();
    }

    if (info_.jumpIndex_ != EMPTY_JUMP_INDEX) {
        MeasureOnJump(mainSize);
    } else {
        MeasureOnOffset(mainSize);
    }

    if (props->GetAlignItems().value_or(GridItemAlignment::DEFAULT) == GridItemAlignment::STRETCH) {
        GridLayoutBaseAlgorithm::AdjustChildrenHeight(layoutWrapper);
    }

    UpdateLayoutInfo();
    const int32_t cacheCnt = props->GetCachedCountValue(info_.defCachedCount_) * info_.crossCount_;
    if (props->GetShowCachedItemsValue(false)) {
        SyncPreloadItems(cacheCnt);
    } else {
        PreloadItems(cacheCnt);
    }
}

void GridIrregularLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
{
    const auto& info = info_;
    if (info.childrenCount_ <= 0) {
        return;
    }
    wrapper_ = layoutWrapper;
    auto props = DynamicCast<GridLayoutProperty>(wrapper_->GetLayoutProperty());
    CHECK_NULL_VOID(props);

    const int32_t cacheCount = props->GetCachedCountValue(info.defCachedCount_);
    if (!props->HasCachedCount()) {
        info_.UpdateDefaultCachedCount();
    }
    LayoutChildren(info.currentOffset_, cacheCount);

    const int32_t cacheCnt = cacheCount * info.crossCount_;
    wrapper_->SetActiveChildRange(std::min(info.startIndex_, info.endIndex_), info.endIndex_, cacheCnt, cacheCnt,
        props->GetShowCachedItemsValue(false));
    wrapper_->SetCacheCount(cacheCnt);
}


MeasureLayout会更新GridLayoutInfoGridLayoutProperty等信息用于重新渲染。
上树:Measure过程如果有新的Item创建,GetOrCreateChildByIndex回调前端js的keyGenFunc_触发上树。

std::pair<float, LayoutConstraintF> GridIrregularFiller::MeasureItem(
    const FillParameters& params, int32_t itemIdx, int32_t col, int32_t row, bool isCache)
{
    auto props = AceType::DynamicCast<GridLayoutProperty>(wrapper_->GetLayoutProperty());
    auto constraint = props->CreateChildConstraint();
    auto child = wrapper_->GetOrCreateChildByIndex(itemIdx, !isCache, isCache);
    CHECK_NULL_RETURN(child, {});

    const auto itemSize = GridLayoutUtils::GetItemSize(info_, wrapper_, itemIdx);
    float crossLen = 0.0f;
    for (int32_t i = 0; i < itemSize.columns; ++i) {
        crossLen += params.crossLens[i + col];
    }
    crossLen += params.crossGap * (itemSize.columns - 1);
    constraint.percentReference.SetCrossSize(crossLen, info_->axis_);
    if (info_->axis_ == Axis::VERTICAL) {
        constraint.maxSize = SizeF { crossLen, Infinity<float>() };
        constraint.parentIdealSize = OptionalSizeF(crossLen, std::nullopt);
    } else {
        constraint.maxSize = SizeF { Infinity<float>(), crossLen };
        constraint.parentIdealSize = OptionalSizeF(std::nullopt, crossLen);
    }

    child->Measure(constraint);
    SetItemInfo(itemIdx, row, col, itemSize);

    float childHeight = child->GetGeometryNode()->GetMarginFrameSize().MainSize(info_->axis_);
    // spread height to each row.
    float heightPerRow = (childHeight - (params.mainGap * (itemSize.rows - 1))) / itemSize.rows;
    for (int32_t i = 0; i < itemSize.rows; ++i) {
        info_->lineHeightMap_[row + i] = std::max(info_->lineHeightMap_[row + i], heightPerRow);
    }
    return { childHeight, constraint };
}

下树:Layout过程wrapper_->SetActiveChildRange设置可视区域的Item,通过cache的大小,超出部分下树。

void LazyForEachNode::DoSetActiveChildRange(
    int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd, bool showCache)
{
    if (!builder_) {
        return;
    }
    if (showCache) {
        start -= cacheStart;
        end += cacheEnd;
        builder_->SetShowCached(cacheStart, cacheEnd);
    }
    if (builder_->SetActiveChildRange(start, end)) {
        tempChildren_.clear();
        tempChildren_.swap(children_);
        MarkNeedSyncRenderTree();
        PostIdleTask();
    }
}

LazyForEach上下树

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表