/** * Copyright (c) 2015-present, Facebook, Inc. All rights reserved. * *
This source code is licensed under the BSD-style license found in the LICENSE file in the root * directory of this source tree. An additional grant of patent rights can be found in the PATENTS * file in the same directory. */ package com.facebook.react.uimanager; import com.facebook.infer.annotation.Assertions; import com.facebook.react.uimanager.annotations.ReactPropertyHolder; import com.facebook.yoga.YogaAlign; import com.facebook.yoga.YogaBaselineFunction; import com.facebook.yoga.YogaConfig; import com.facebook.yoga.YogaConstants; import com.facebook.yoga.YogaDirection; import com.facebook.yoga.YogaDisplay; import com.facebook.yoga.YogaEdge; import com.facebook.yoga.YogaFlexDirection; import com.facebook.yoga.YogaJustify; import com.facebook.yoga.YogaMeasureFunction; import com.facebook.yoga.YogaNode; import com.facebook.yoga.YogaOverflow; import com.facebook.yoga.YogaPositionType; import com.facebook.yoga.YogaValue; import com.facebook.yoga.YogaWrap; import java.util.ArrayList; import java.util.Arrays; import javax.annotation.Nullable; /** * Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily for * layouting therefore it extends {@link YogaNode} to allow that. They also help with handling * Common base subclass of {@link YogaNode} for all layout nodes for react-based view. It extends * {@link YogaNode} by adding additional capabilities. * *
Instances of this class receive property updates from JS via @{link UIManagerModule}. * Subclasses may use {@link #updateShadowNode} to persist some of the updated fields in the node * instance that corresponds to a particular view type. * *
Subclasses of {@link ReactShadowNodeImpl} should be created only from {@link ViewManager} that * corresponds to a certain type of native view. They will be updated and accessed only from JS * thread. Subclasses of {@link ViewManager} may choose to use base class {@link * ReactShadowNodeImpl} or custom subclass of it if necessary. * *
The primary use-case for {@link ReactShadowNodeImpl} nodes is to calculate layouting. Although * this might be extended. For some examples please refer to ARTGroupYogaNode or ReactTextYogaNode. * *
This class allows for the native view hierarchy to not be an exact copy of the hierarchy
* received from JS by keeping track of both JS children (e.g. {@link #getChildCount()} and
* separately native children (e.g. {@link #getNativeChildCount()}). See {@link
* NativeViewHierarchyOptimizer} for more information.
*/
@ReactPropertyHolder
public class ReactShadowNodeImpl implements ReactShadowNode Basically, a view might have children that have been optimized away by {@link
* NativeViewHierarchyOptimizer}. Since those children will then add their native children to this
* view, we now have ranges of native children that correspond to single unoptimized children. The
* purpose of this method is to return the index within the native children that corresponds to
* the **start** of the native children that belong to the given child. Also, note that all of the
* children of a view might be optimized away, so this could return the same value for multiple
* different children.
*
* Example. Native children are represented by (N) where N is the no-opt child they came from.
* If no children are optimized away it'd look like this: (0) (1) (2) (3) ... (n)
*
* In case some children are optimized away, it might look like this: (0) (1) (1) (1) (3) (3)
* (4)
*
* In that case: getNativeOffsetForChild(Node 0) => 0 getNativeOffsetForChild(Node 1) => 1
* getNativeOffsetForChild(Node 2) => 4 getNativeOffsetForChild(Node 3) => 4
*
* getNativeOffsetForChild(Node 4) => 6
*/
@Override
public final int getNativeOffsetForChild(ReactShadowNodeImpl child) {
int index = 0;
boolean found = false;
for (int i = 0; i < getChildCount(); i++) {
ReactShadowNodeImpl current = getChildAt(i);
if (child == current) {
found = true;
break;
}
index += (current.isLayoutOnly() ? current.getTotalNativeChildren() : 1);
}
if (!found) {
throw new RuntimeException(
"Child " + child.getReactTag() + " was not a child of " + mReactTag);
}
return index;
}
@Override
public final float getLayoutX() {
return mYogaNode.getLayoutX();
}
@Override
public final float getLayoutY() {
return mYogaNode.getLayoutY();
}
@Override
public final float getLayoutWidth() {
return mYogaNode.getLayoutWidth();
}
@Override
public final float getLayoutHeight() {
return mYogaNode.getLayoutHeight();
}
/** @return the x position of the corresponding view on the screen, rounded to pixels */
@Override
public int getScreenX() {
return mScreenX;
}
/** @return the y position of the corresponding view on the screen, rounded to pixels */
@Override
public int getScreenY() {
return mScreenY;
}
/** @return width corrected for rounding to pixels. */
@Override
public int getScreenWidth() {
return mScreenWidth;
}
/** @return height corrected for rounding to pixels. */
@Override
public int getScreenHeight() {
return mScreenHeight;
}
@Override
public final YogaDirection getLayoutDirection() {
return mYogaNode.getLayoutDirection();
}
@Override
public void setLayoutDirection(YogaDirection direction) {
mYogaNode.setDirection(direction);
}
@Override
public final YogaValue getStyleWidth() {
return mYogaNode.getWidth();
}
@Override
public void setStyleWidth(float widthPx) {
mYogaNode.setWidth(widthPx);
}
@Override
public void setStyleWidthPercent(float percent) {
mYogaNode.setWidthPercent(percent);
}
@Override
public void setStyleWidthAuto() {
mYogaNode.setWidthAuto();
}
@Override
public void setStyleMinWidth(float widthPx) {
mYogaNode.setMinWidth(widthPx);
}
@Override
public void setStyleMinWidthPercent(float percent) {
mYogaNode.setMinWidthPercent(percent);
}
@Override
public void setStyleMaxWidth(float widthPx) {
mYogaNode.setMaxWidth(widthPx);
}
@Override
public void setStyleMaxWidthPercent(float percent) {
mYogaNode.setMaxWidthPercent(percent);
}
@Override
public final YogaValue getStyleHeight() {
return mYogaNode.getHeight();
}
@Override
public void setStyleHeight(float heightPx) {
mYogaNode.setHeight(heightPx);
}
@Override
public void setStyleHeightPercent(float percent) {
mYogaNode.setHeightPercent(percent);
}
@Override
public void setStyleHeightAuto() {
mYogaNode.setHeightAuto();
}
@Override
public void setStyleMinHeight(float widthPx) {
mYogaNode.setMinHeight(widthPx);
}
@Override
public void setStyleMinHeightPercent(float percent) {
mYogaNode.setMinHeightPercent(percent);
}
@Override
public void setStyleMaxHeight(float widthPx) {
mYogaNode.setMaxHeight(widthPx);
}
@Override
public void setStyleMaxHeightPercent(float percent) {
mYogaNode.setMaxHeightPercent(percent);
}
@Override
public void setFlex(float flex) {
mYogaNode.setFlex(flex);
}
@Override
public void setFlexGrow(float flexGrow) {
mYogaNode.setFlexGrow(flexGrow);
}
@Override
public void setFlexShrink(float flexShrink) {
mYogaNode.setFlexShrink(flexShrink);
}
@Override
public void setFlexBasis(float flexBasis) {
mYogaNode.setFlexBasis(flexBasis);
}
@Override
public void setFlexBasisAuto() {
mYogaNode.setFlexBasisAuto();
}
@Override
public void setFlexBasisPercent(float percent) {
mYogaNode.setFlexBasisPercent(percent);
}
@Override
public void setStyleAspectRatio(float aspectRatio) {
mYogaNode.setAspectRatio(aspectRatio);
}
@Override
public void setFlexDirection(YogaFlexDirection flexDirection) {
mYogaNode.setFlexDirection(flexDirection);
}
@Override
public void setFlexWrap(YogaWrap wrap) {
mYogaNode.setWrap(wrap);
}
@Override
public void setAlignSelf(YogaAlign alignSelf) {
mYogaNode.setAlignSelf(alignSelf);
}
@Override
public void setAlignItems(YogaAlign alignItems) {
mYogaNode.setAlignItems(alignItems);
}
@Override
public void setAlignContent(YogaAlign alignContent) {
mYogaNode.setAlignContent(alignContent);
}
@Override
public void setJustifyContent(YogaJustify justifyContent) {
mYogaNode.setJustifyContent(justifyContent);
}
@Override
public void setOverflow(YogaOverflow overflow) {
mYogaNode.setOverflow(overflow);
}
@Override
public void setDisplay(YogaDisplay display) {
mYogaNode.setDisplay(display);
}
@Override
public void setMargin(int spacingType, float margin) {
mYogaNode.setMargin(YogaEdge.fromInt(spacingType), margin);
}
@Override
public void setMarginPercent(int spacingType, float percent) {
mYogaNode.setMarginPercent(YogaEdge.fromInt(spacingType), percent);
}
@Override
public void setMarginAuto(int spacingType) {
mYogaNode.setMarginAuto(YogaEdge.fromInt(spacingType));
}
@Override
public final float getPadding(int spacingType) {
return mYogaNode.getLayoutPadding(YogaEdge.fromInt(spacingType));
}
@Override
public final YogaValue getStylePadding(int spacingType) {
return mYogaNode.getPadding(YogaEdge.fromInt(spacingType));
}
@Override
public void setDefaultPadding(int spacingType, float padding) {
mDefaultPadding.set(spacingType, padding);
updatePadding();
}
@Override
public void setPadding(int spacingType, float padding) {
mPadding[spacingType] = padding;
mPaddingIsPercent[spacingType] = false;
updatePadding();
}
@Override
public void setPaddingPercent(int spacingType, float percent) {
mPadding[spacingType] = percent;
mPaddingIsPercent[spacingType] = !YogaConstants.isUndefined(percent);
updatePadding();
}
private void updatePadding() {
for (int spacingType = Spacing.LEFT; spacingType <= Spacing.ALL; spacingType++) {
if (spacingType == Spacing.LEFT
|| spacingType == Spacing.RIGHT
|| spacingType == Spacing.START
|| spacingType == Spacing.END) {
if (YogaConstants.isUndefined(mPadding[spacingType])
&& YogaConstants.isUndefined(mPadding[Spacing.HORIZONTAL])
&& YogaConstants.isUndefined(mPadding[Spacing.ALL])) {
mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType));
continue;
}
} else if (spacingType == Spacing.TOP || spacingType == Spacing.BOTTOM) {
if (YogaConstants.isUndefined(mPadding[spacingType])
&& YogaConstants.isUndefined(mPadding[Spacing.VERTICAL])
&& YogaConstants.isUndefined(mPadding[Spacing.ALL])) {
mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType));
continue;
}
} else {
if (YogaConstants.isUndefined(mPadding[spacingType])) {
mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType));
continue;
}
}
if (mPaddingIsPercent[spacingType]) {
mYogaNode.setPaddingPercent(YogaEdge.fromInt(spacingType), mPadding[spacingType]);
} else {
mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mPadding[spacingType]);
}
}
}
@Override
public void setBorder(int spacingType, float borderWidth) {
mYogaNode.setBorder(YogaEdge.fromInt(spacingType), borderWidth);
}
@Override
public void setPosition(int spacingType, float position) {
mYogaNode.setPosition(YogaEdge.fromInt(spacingType), position);
}
@Override
public void setPositionPercent(int spacingType, float percent) {
mYogaNode.setPositionPercent(YogaEdge.fromInt(spacingType), percent);
}
@Override
public void setPositionType(YogaPositionType positionType) {
mYogaNode.setPositionType(positionType);
}
@Override
public void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) {
mShouldNotifyOnLayout = shouldNotifyOnLayout;
}
@Override
public void setBaselineFunction(YogaBaselineFunction baselineFunction) {
mYogaNode.setBaselineFunction(baselineFunction);
}
@Override
public void setMeasureFunction(YogaMeasureFunction measureFunction) {
if ((measureFunction == null ^ mYogaNode.isMeasureDefined()) && getChildCount() != 0) {
throw new RuntimeException(
"Since a node with a measure function does not add any native yoga children, it's "
+ "not safe to transition to/from having a measure function unless a node has no children");
}
mYogaNode.setMeasureFunction(measureFunction);
}
@Override
public boolean isMeasureDefined() {
return mYogaNode.isMeasureDefined();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
toStringWithIndentation(sb, 0);
return sb.toString();
}
private void toStringWithIndentation(StringBuilder result, int level) {
// Spaces and tabs are dropped by IntelliJ logcat integration, so rely on __ instead.
for (int i = 0; i < level; ++i) {
result.append("__");
}
result.append(getClass().getSimpleName()).append(" ");
if (mYogaNode != null) {
result.append(getLayoutWidth()).append(",").append(getLayoutHeight());
} else {
result.append("(virtual node)");
}
result.append("\n");
if (getChildCount() == 0) {
return;
}
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).toStringWithIndentation(result, level + 1);
}
}
@Override
public void dispose() {
if (mYogaNode != null) {
mYogaNode.reset();
YogaNodePool.get().release(mYogaNode);
}
}
}