网站首页 > 开源技术 正文
深入理解 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分支切换)
分支切换会执行IfElseNode的SetBranchId函数:
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::Push,Button.pop会执行ViewStackProcessor::Pop,Pop函数的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,会存储在后端的framenode的layout_property,不会下组件树,只会在None时下渲染树。
- 1. 当None设置为非None状态或者非None设置为None状态,需要重新触发Measure和Layout。
- 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是在Grid的FrameNode对象创建的时候关联,用于处理滚动事件、滚动条、边缘效果等,保存了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,除了保存了布局相关的通用信息,如宽、高等,还包含rowsTemplate、ColumnsTemplate、GridDirection等信息。
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时创建的绘制方法,用于滚动条、边缘阴影效果等绘制。
主要的布局算法包含GridLayoutAlgorithm、GridAdaptiveLayoutAlgorithm、GridIrregularLayoutAlgorithm、GridScrollLayoutAlgorithm和GridScrollWithOptionsLayoutAlgorithm。
何时触发LazyForEach上下树?
当容器触发滚动(scrollTo、scrollBy、scrollPage等),会将滚动偏移量设置给GridLayoutInfo的currentOffset_,然后对节点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,创建布局算法执行对应标脏节点的Measure和Layout方法。
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);
}
Measure和Layout会更新GridLayoutInfo、GridLayoutProperty等信息用于重新渲染。
上树: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上下树
猜你喜欢
- 2025-01-24 10个免费的JavaScript工具来创建交互式地图
- 2025-01-24 鸿蒙应用签名实操及机制探究(鸿蒙签名不一致解决怎么强制安装)
- 2025-01-24 暗物质vs.修改引力理论,终极对决已经打响?
- 2025-01-24 关于JavaScript必学的基础知识(上)
- 2025-01-24 B站跨年晚会节目单官宣,二次元浓度超高,童年回忆满满
- 2025-01-24 缅军22师卡玛亚205营官兵轮奸女LO性,在发泄兽欲后杀害母女二人
- 2025-01-24 WebAPI详细解说【思维导图】(webapi使用方法)
- 2025-01-24 「风采」兰州特警全力以赴 筑起文博会安全屏障
- 2025-01-24 B站2024跨年晚会全阵容官宣!(b站2021跨年晚会时间)
- 2025-01-24 Vue.js 实现树形结构管理系统的前端设计与实现
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- jdk (81)
- putty (66)
- rufus (78)
- 内网穿透 (89)
- okhttp (70)
- powertoys (74)
- windowsterminal (81)
- netcat (65)
- ghostscript (65)
- veracrypt (65)
- asp.netcore (70)
- wrk (67)
- aspose.words (80)
- itk (80)
- ajaxfileupload.js (66)
- sqlhelper (67)
- express.js (67)
- phpmailer (67)
- xjar (70)
- redisclient (78)
- wakeonlan (66)
- tinygo (85)
- startbbs (72)
- webftp (82)
- vsvim (79)
本文暂时没有评论,来添加一个吧(●'◡'●)