in litho-core/src/main/java/com/facebook/litho/LayoutState.java [531:932]
private static void collectResults(
final ComponentContext parentContext,
final LithoLayoutResult result,
final LithoNode node,
final LayoutState layoutState,
@Nullable RenderTreeNode parent,
final @Nullable DiffNode parentDiffNode,
final @Nullable DebugHierarchy.Node parentHierarchy) {
final LayoutStateContext layoutStateContext = layoutState.getLayoutStateContext();
if (layoutStateContext.isLayoutReleased()) {
return;
}
final Component component = node.getTailComponent();
final boolean isTracing = ComponentsSystrace.isTracing();
final DebugHierarchy.Node hierarchy = getDebugHierarchy(parentHierarchy, node);
// Early return if collecting results of a node holding a nested tree.
if (result instanceof NestedTreeHolderResult) {
// If the nested tree is defined, it has been resolved during a measure call during
// layout calculation.
if (isTracing) {
ComponentsSystrace.beginSectionWithArgs("resolveNestedTree:" + component.getSimpleName())
.arg("widthSpec", "EXACTLY " + result.getWidth())
.arg("heightSpec", "EXACTLY " + result.getHeight())
.arg("rootComponentId", node.getTailComponent().getId())
.flush();
}
final int size = node.getComponentCount();
final ComponentContext immediateParentContext;
if (size == 1) {
immediateParentContext = parentContext;
} else {
immediateParentContext = node.getComponentContextAt(1);
}
LithoLayoutResult nestedTree =
Layout.create(
layoutStateContext,
Preconditions.checkNotNull(immediateParentContext),
(NestedTreeHolderResult) result,
SizeSpec.makeSizeSpec(result.getWidth(), EXACTLY),
SizeSpec.makeSizeSpec(result.getHeight(), EXACTLY));
if (isTracing) {
ComponentsSystrace.endSection();
}
if (nestedTree == null) {
return;
}
// Account for position of the holder node.
layoutState.mCurrentX += result.getX();
layoutState.mCurrentY += result.getY();
collectResults(
parentContext,
nestedTree,
nestedTree.getNode(),
layoutState,
parent,
parentDiffNode,
hierarchy);
layoutState.mCurrentX -= result.getX();
layoutState.mCurrentY -= result.getY();
return;
}
final ScopedComponentInfo tail = node.getTailScopedComponentInfo();
final ComponentContext context = tail.getContext();
final boolean shouldGenerateDiffTree = layoutState.mShouldGenerateDiffTree;
final DiffNode diffNode;
if (shouldGenerateDiffTree) {
diffNode = createDiffNode(tail, parentDiffNode);
if (parentDiffNode == null) {
layoutState.mDiffTreeRoot = diffNode;
}
} else {
diffNode = null;
}
// The last measured specs, and measurements need to be explicitly
// set on the LayoutResult if measure was not called for it. This
// will ensure layout diffing works in subsequent layouts for these
// results.
if (ComponentsConfiguration.alwaysWriteDiffNodes && !result.wasMeasured()) {
final int widthSpec = exactly(result.getWidth());
final int heightSpec = exactly(result.getHeight());
result.setLastWidthSpec(widthSpec);
result.setLastHeightSpec(heightSpec);
result.setLastMeasuredWidth(result.getWidth());
result.setLastMeasuredHeight(result.getHeight());
}
final @Nullable LithoRenderUnit hostRenderUnit = result.getHostRenderUnit();
final boolean needsHostView = hostRenderUnit != null;
final long currentHostMarker = layoutState.mCurrentHostMarker;
final int currentHostOutputPosition = layoutState.mCurrentHostOutputPosition;
final TransitionId currentTransitionId = layoutState.mCurrentTransitionId;
final OutputUnitsAffinityGroup<AnimatableItem> currentLayoutOutputAffinityGroup =
layoutState.mCurrentLayoutOutputAffinityGroup;
layoutState.mCurrentTransitionId = getTransitionIdForNode(node);
layoutState.mCurrentLayoutOutputAffinityGroup =
layoutState.mCurrentTransitionId != null
? new OutputUnitsAffinityGroup<AnimatableItem>()
: null;
// 1. Insert a host LayoutOutput if we have some interactive content to be attached to.
if (hostRenderUnit != null) {
final int hostLayoutPosition =
addHostRenderTreeNode(
hostRenderUnit, parent, result, node, layoutState, diffNode, hierarchy);
addCurrentAffinityGroupToTransitionMapping(layoutState);
parent = layoutState.mMountableOutputs.get(hostLayoutPosition);
layoutState.mCurrentLevel++;
layoutState.mCurrentHostMarker = parent.getRenderUnit().getId();
layoutState.mCurrentHostOutputPosition = hostLayoutPosition;
}
// We need to take into account flattening when setting duplicate parent state. The parent after
// flattening may no longer exist. Therefore the value of duplicate parent state should only be
// true if the path between us (inclusive) and our inner/root host (exclusive) all are
// duplicate parent state.
final boolean shouldDuplicateParentState = layoutState.mShouldDuplicateParentState;
layoutState.mShouldDuplicateParentState =
needsHostView
|| layoutState.isLayoutRoot(result)
|| (shouldDuplicateParentState && node.isDuplicateParentStateEnabled());
// 2. Add background if defined.
if (!layoutState.mShouldDisableDrawableOutputs) {
final LithoRenderUnit backgroundRenderUnit = result.getBackgroundRenderUnit();
if (backgroundRenderUnit != null) {
final RenderTreeNode backgroundRenderTreeNode =
addDrawableRenderTreeNode(
backgroundRenderUnit,
parent,
result,
layoutState,
hierarchy,
OutputUnitType.BACKGROUND,
needsHostView);
if (diffNode != null) {
diffNode.setBackgroundOutput((LithoRenderUnit) backgroundRenderTreeNode.getRenderUnit());
}
}
}
// Generate the RenderTreeNode for the given node.
final @Nullable RenderTreeNode contentRenderTreeNode =
createContentRenderTreeNode(result, node, layoutState, parent);
// 3. Now add the MountSpec (either View or Drawable) to the outputs.
if (contentRenderTreeNode != null) {
final LithoRenderUnit contentRenderUnit =
(LithoRenderUnit) contentRenderTreeNode.getRenderUnit();
final LayoutOutput contentLayoutOutput = contentRenderUnit.getLayoutOutput();
final LithoLayoutData layoutData =
(LithoLayoutData) Preconditions.checkNotNull(contentRenderTreeNode.getLayoutData());
// Notify component about its final size.
if (isTracing) {
ComponentsSystrace.beginSection("onBoundsDefined:" + component.getSimpleName());
}
try {
if (isMountSpec(component) && !isMountable(component)) {
component.onBoundsDefined(
context, result, (InterStagePropsContainer) layoutData.mLayoutData);
}
} catch (Exception e) {
ComponentUtils.handleWithHierarchy(context, component, e);
} finally {
if (isTracing) {
ComponentsSystrace.endSection();
}
}
addRenderTreeNode(
layoutState,
contentRenderTreeNode,
contentRenderUnit,
contentLayoutOutput,
OutputUnitType.CONTENT,
!needsHostView ? layoutState.mCurrentTransitionId : null,
parent);
if (diffNode != null) {
diffNode.setContentOutput(contentRenderUnit);
}
if (hierarchy != null) {
contentLayoutOutput.setHierarchy(hierarchy.mutateType(OutputUnitType.CONTENT));
}
}
// 4. Extract the Transitions.
if (Layout.areTransitionsEnabled(context)) {
final ArrayList<Transition> transitions = node.getTransitions();
if (transitions != null) {
for (int i = 0, size = transitions.size(); i < size; i++) {
final Transition transition = transitions.get(i);
if (layoutState.mTransitions == null) {
layoutState.mTransitions = new ArrayList<>();
}
TransitionUtils.addTransitions(
transition, layoutState.mTransitions, layoutState.mRootComponentName);
}
}
final @Nullable Map<String, ScopedComponentInfo>
scopedComponentInfosNeedingPreviousRenderData =
node.getScopedComponentInfosNeedingPreviousRenderData();
if (scopedComponentInfosNeedingPreviousRenderData != null) {
if (layoutState.mScopedComponentInfosNeedingPreviousRenderData == null) {
layoutState.mScopedComponentInfosNeedingPreviousRenderData = new ArrayList<>();
}
for (Map.Entry<String, ScopedComponentInfo> entry :
scopedComponentInfosNeedingPreviousRenderData.entrySet()) {
layoutState.mScopedComponentInfosNeedingPreviousRenderData.add(entry.getValue());
}
}
}
layoutState.mCurrentX += result.getX();
layoutState.mCurrentY += result.getY();
// We must process the nodes in order so that the layout state output order is correct.
for (int i = 0, size = result.getChildCount(); i < size; i++) {
final LithoLayoutResult child = result.getChildAt(i);
collectResults(context, child, child.getNode(), layoutState, parent, diffNode, hierarchy);
}
layoutState.mCurrentX -= result.getX();
layoutState.mCurrentY -= result.getY();
// 5. Add border color if defined.
final LithoRenderUnit borderRenderUnit = result.getBorderRenderUnit();
if (borderRenderUnit != null) {
final RenderTreeNode borderRenderTreeNode =
addDrawableRenderTreeNode(
borderRenderUnit,
parent,
result,
layoutState,
hierarchy,
OutputUnitType.BORDER,
needsHostView);
if (diffNode != null) {
diffNode.setBorderOutput((LithoRenderUnit) borderRenderTreeNode.getRenderUnit());
}
}
// 6. Add foreground if defined.
if (!layoutState.mShouldDisableDrawableOutputs) {
final @Nullable LithoRenderUnit foregroundRenderUnit = result.getForegroundRenderUnit();
if (foregroundRenderUnit != null) {
final RenderTreeNode foregroundRenderTreeNode =
addDrawableRenderTreeNode(
foregroundRenderUnit,
parent,
result,
layoutState,
hierarchy,
OutputUnitType.FOREGROUND,
needsHostView);
if (diffNode != null) {
diffNode.setForegroundOutput((LithoRenderUnit) foregroundRenderTreeNode.getRenderUnit());
}
}
}
if (diffNode != null) {
diffNode.setLastWidthSpec(result.getLastWidthSpec());
diffNode.setLastHeightSpec(result.getLastHeightSpec());
diffNode.setLastMeasuredWidth(result.getLastMeasuredWidth());
diffNode.setLastMeasuredHeight(result.getLastMeasuredHeight());
diffNode.setLayoutData(result.getLayoutData());
diffNode.setMountable(result.getNode().getMountable());
}
// 7. Add VisibilityOutputs if any visibility-related event handlers are present.
if (node.hasVisibilityHandlers()) {
final VisibilityOutput visibilityOutput =
createVisibilityOutput(
result,
node,
layoutState,
contentRenderTreeNode != null
? contentRenderTreeNode
: needsHostView ? parent : null);
layoutState.mVisibilityOutputs.add(visibilityOutput);
if (diffNode != null) {
diffNode.setVisibilityOutput(visibilityOutput);
}
}
// 8. If we're in a testing environment, maintain an additional data structure with
// information about nodes that we can query later.
if (layoutState.mTestOutputs != null && !TextUtils.isEmpty(node.getTestKey())) {
final TestOutput testOutput =
createTestOutput(
result,
node,
layoutState,
contentRenderTreeNode != null
? (LithoRenderUnit) contentRenderTreeNode.getRenderUnit()
: null);
layoutState.mTestOutputs.add(testOutput);
}
// 9. Extract the Working Range registrations.
List<WorkingRangeContainer.Registration> registrations = node.getWorkingRangeRegistrations();
if (registrations != null && !registrations.isEmpty()) {
if (layoutState.mWorkingRangeContainer == null) {
layoutState.mWorkingRangeContainer = new WorkingRangeContainer();
}
for (WorkingRangeContainer.Registration registration : registrations) {
layoutState.mWorkingRangeContainer.registerWorkingRange(
registration.mName, registration.mWorkingRange, registration.mScopedComponentInfo);
}
}
final List<Attachable> attachables = result.getNode().getAttachables();
if (attachables != null) {
if (layoutState.mAttachables == null) {
layoutState.mAttachables = new ArrayList<>();
}
layoutState.mAttachables.addAll(attachables);
}
final Rect rect;
if (contentRenderTreeNode != null) {
rect = contentRenderTreeNode.getAbsoluteBounds(new Rect());
} else {
rect = new Rect();
rect.left = layoutState.mCurrentX + result.getX();
rect.top = layoutState.mCurrentY + result.getY();
rect.right = rect.left + result.getWidth();
rect.bottom = rect.top + result.getHeight();
}
for (int i = 0, size = node.getComponentCount(); i < size; i++) {
final Component delegate = node.getComponentAt(i);
final String delegateKey = node.getGlobalKeyAt(i);
// Keep a list of the components we created during this layout calculation. If the layout is
// valid, the ComponentTree will update the event handlers that have been created in the
// previous ComponentTree with the new component dispatched, otherwise Section children
// might not be accessing the correct props and state on the event handlers. The null
// checkers cover tests, the scope and tree should not be null at this point of the layout
// calculation.
final ComponentContext delegateScopedContext = node.getComponentContextAt(i);
if (delegateScopedContext != null && delegateScopedContext.getComponentTree() != null) {
if (layoutState.mScopedComponentInfos != null) {
layoutState.mScopedComponentInfos.add(delegateScopedContext.getScopedComponentInfo());
}
}
if (delegateKey != null || delegate.hasHandle()) {
Rect copyRect = new Rect(rect);
if (delegateKey != null) {
layoutState.mComponentKeyToBounds.put(delegateKey, copyRect);
}
if (delegate.hasHandle()) {
layoutState.mComponentHandleToBounds.put(delegate.getHandle(), copyRect);
}
}
}
// All children for the given host have been added, restore the previous
// host, level, and duplicate parent state value in the recursive queue.
if (layoutState.mCurrentHostMarker != currentHostMarker) {
layoutState.mCurrentHostMarker = currentHostMarker;
layoutState.mCurrentHostOutputPosition = currentHostOutputPosition;
layoutState.mCurrentLevel--;
}
layoutState.mShouldDuplicateParentState = shouldDuplicateParentState;
addCurrentAffinityGroupToTransitionMapping(layoutState);
layoutState.mCurrentTransitionId = currentTransitionId;
layoutState.mCurrentLayoutOutputAffinityGroup = currentLayoutOutputAffinityGroup;
}