Java tutorial
/* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ package com.facebook.react.uimanager; import androidx.annotation.Nullable; import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableType; import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactPropGroup; import com.facebook.yoga.YogaAlign; import com.facebook.yoga.YogaConstants; import com.facebook.yoga.YogaDisplay; import com.facebook.yoga.YogaFlexDirection; import com.facebook.yoga.YogaJustify; import com.facebook.yoga.YogaOverflow; import com.facebook.yoga.YogaPositionType; import com.facebook.yoga.YogaUnit; import com.facebook.yoga.YogaWrap; /** * Supply setters for base view layout properties such as width, height, flex properties, borders, * etc. * * <p>Checking for isVirtual everywhere is a hack to get around the fact that some virtual nodes * still have layout properties set on them in JS: for example, a component that returns a <Text> * may or may not be embedded in a parent text. There are better solutions that should probably be * explored, namely using the VirtualText class in JS and setting the correct set of validAttributes */ public class LayoutShadowNode extends ReactShadowNodeImpl { /** A Mutable version of com.facebook.yoga.YogaValue */ private static class MutableYogaValue { float value; YogaUnit unit; private MutableYogaValue() { } private MutableYogaValue(MutableYogaValue mutableYogaValue) { this.value = mutableYogaValue.value; this.unit = mutableYogaValue.unit; } void setFromDynamic(Dynamic dynamic) { if (dynamic.isNull()) { unit = YogaUnit.UNDEFINED; value = YogaConstants.UNDEFINED; } else if (dynamic.getType() == ReadableType.String) { final String s = dynamic.asString(); if (s.equals("auto")) { unit = YogaUnit.AUTO; value = YogaConstants.UNDEFINED; } else if (s.endsWith("%")) { unit = YogaUnit.PERCENT; value = Float.parseFloat(s.substring(0, s.length() - 1)); } else { throw new IllegalArgumentException("Unknown value: " + s); } } else { unit = YogaUnit.POINT; value = PixelUtil.toPixelFromDIP(dynamic.asDouble()); } } } private final MutableYogaValue mTempYogaValue; public LayoutShadowNode() { mTempYogaValue = new MutableYogaValue(); } @ReactProp(name = ViewProps.WIDTH) public void setWidth(Dynamic width) { if (isVirtual()) { return; } mTempYogaValue.setFromDynamic(width); switch (mTempYogaValue.unit) { case POINT: case UNDEFINED: setStyleWidth(mTempYogaValue.value); break; case AUTO: setStyleWidthAuto(); break; case PERCENT: setStyleWidthPercent(mTempYogaValue.value); break; } width.recycle(); } @ReactProp(name = ViewProps.MIN_WIDTH) public void setMinWidth(Dynamic minWidth) { if (isVirtual()) { return; } mTempYogaValue.setFromDynamic(minWidth); switch (mTempYogaValue.unit) { case POINT: case UNDEFINED: setStyleMinWidth(mTempYogaValue.value); break; case PERCENT: setStyleMinWidthPercent(mTempYogaValue.value); break; } minWidth.recycle(); } boolean mCollapsable; @ReactProp(name = "collapsable") public void setCollapsable(boolean collapsable) { mCollapsable = collapsable; } @ReactProp(name = ViewProps.MAX_WIDTH) public void setMaxWidth(Dynamic maxWidth) { if (isVirtual()) { return; } mTempYogaValue.setFromDynamic(maxWidth); switch (mTempYogaValue.unit) { case POINT: case UNDEFINED: setStyleMaxWidth(mTempYogaValue.value); break; case PERCENT: setStyleMaxWidthPercent(mTempYogaValue.value); break; } maxWidth.recycle(); } @ReactProp(name = ViewProps.HEIGHT) public void setHeight(Dynamic height) { if (isVirtual()) { return; } mTempYogaValue.setFromDynamic(height); switch (mTempYogaValue.unit) { case POINT: case UNDEFINED: setStyleHeight(mTempYogaValue.value); break; case AUTO: setStyleHeightAuto(); break; case PERCENT: setStyleHeightPercent(mTempYogaValue.value); break; } height.recycle(); } @ReactProp(name = ViewProps.MIN_HEIGHT) public void setMinHeight(Dynamic minHeight) { if (isVirtual()) { return; } mTempYogaValue.setFromDynamic(minHeight); switch (mTempYogaValue.unit) { case POINT: case UNDEFINED: setStyleMinHeight(mTempYogaValue.value); break; case PERCENT: setStyleMinHeightPercent(mTempYogaValue.value); break; } minHeight.recycle(); } @ReactProp(name = ViewProps.MAX_HEIGHT) public void setMaxHeight(Dynamic maxHeight) { if (isVirtual()) { return; } mTempYogaValue.setFromDynamic(maxHeight); switch (mTempYogaValue.unit) { case POINT: case UNDEFINED: setStyleMaxHeight(mTempYogaValue.value); break; case PERCENT: setStyleMaxHeightPercent(mTempYogaValue.value); break; } maxHeight.recycle(); } @ReactProp(name = ViewProps.FLEX, defaultFloat = 0f) public void setFlex(float flex) { if (isVirtual()) { return; } super.setFlex(flex); } @ReactProp(name = ViewProps.FLEX_GROW, defaultFloat = 0f) public void setFlexGrow(float flexGrow) { if (isVirtual()) { return; } super.setFlexGrow(flexGrow); } @ReactProp(name = ViewProps.FLEX_SHRINK, defaultFloat = 0f) public void setFlexShrink(float flexShrink) { if (isVirtual()) { return; } super.setFlexShrink(flexShrink); } @ReactProp(name = ViewProps.FLEX_BASIS) public void setFlexBasis(Dynamic flexBasis) { if (isVirtual()) { return; } mTempYogaValue.setFromDynamic(flexBasis); switch (mTempYogaValue.unit) { case POINT: case UNDEFINED: setFlexBasis(mTempYogaValue.value); break; case AUTO: setFlexBasisAuto(); break; case PERCENT: setFlexBasisPercent(mTempYogaValue.value); break; } flexBasis.recycle(); } @ReactProp(name = ViewProps.ASPECT_RATIO, defaultFloat = YogaConstants.UNDEFINED) public void setAspectRatio(float aspectRatio) { setStyleAspectRatio(aspectRatio); } @ReactProp(name = ViewProps.FLEX_DIRECTION) public void setFlexDirection(@Nullable String flexDirection) { if (isVirtual()) { return; } if (flexDirection == null) { setFlexDirection(YogaFlexDirection.COLUMN); return; } switch (flexDirection) { case "column": { setFlexDirection(YogaFlexDirection.COLUMN); break; } case "column-reverse": { setFlexDirection(YogaFlexDirection.COLUMN_REVERSE); break; } case "row": { setFlexDirection(YogaFlexDirection.ROW); break; } case "row-reverse": { setFlexDirection(YogaFlexDirection.ROW_REVERSE); break; } default: { throw new JSApplicationIllegalArgumentException("invalid value for flexDirection: " + flexDirection); } } } @ReactProp(name = ViewProps.FLEX_WRAP) public void setFlexWrap(@Nullable String flexWrap) { if (isVirtual()) { return; } if (flexWrap == null) { setFlexWrap(YogaWrap.NO_WRAP); return; } switch (flexWrap) { case "nowrap": { setFlexWrap(YogaWrap.NO_WRAP); break; } case "wrap": { setFlexWrap(YogaWrap.WRAP); break; } case "wrap-reverse": { setFlexWrap(YogaWrap.WRAP_REVERSE); break; } default: { throw new JSApplicationIllegalArgumentException("invalid value for flexWrap: " + flexWrap); } } } @ReactProp(name = ViewProps.ALIGN_SELF) public void setAlignSelf(@Nullable String alignSelf) { if (isVirtual()) { return; } if (alignSelf == null) { setAlignSelf(YogaAlign.AUTO); return; } switch (alignSelf) { case "auto": { setAlignSelf(YogaAlign.AUTO); return; } case "flex-start": { setAlignSelf(YogaAlign.FLEX_START); return; } case "center": { setAlignSelf(YogaAlign.CENTER); return; } case "flex-end": { setAlignSelf(YogaAlign.FLEX_END); return; } case "stretch": { setAlignSelf(YogaAlign.STRETCH); return; } case "baseline": { setAlignSelf(YogaAlign.BASELINE); return; } case "space-between": { setAlignSelf(YogaAlign.SPACE_BETWEEN); return; } case "space-around": { setAlignSelf(YogaAlign.SPACE_AROUND); return; } default: { throw new JSApplicationIllegalArgumentException("invalid value for alignSelf: " + alignSelf); } } } @ReactProp(name = ViewProps.ALIGN_ITEMS) public void setAlignItems(@Nullable String alignItems) { if (isVirtual()) { return; } if (alignItems == null) { setAlignItems(YogaAlign.STRETCH); return; } switch (alignItems) { case "auto": { setAlignItems(YogaAlign.AUTO); return; } case "flex-start": { setAlignItems(YogaAlign.FLEX_START); return; } case "center": { setAlignItems(YogaAlign.CENTER); return; } case "flex-end": { setAlignItems(YogaAlign.FLEX_END); return; } case "stretch": { setAlignItems(YogaAlign.STRETCH); return; } case "baseline": { setAlignItems(YogaAlign.BASELINE); return; } case "space-between": { setAlignItems(YogaAlign.SPACE_BETWEEN); return; } case "space-around": { setAlignItems(YogaAlign.SPACE_AROUND); return; } default: { throw new JSApplicationIllegalArgumentException("invalid value for alignItems: " + alignItems); } } } @ReactProp(name = ViewProps.ALIGN_CONTENT) public void setAlignContent(@Nullable String alignContent) { if (isVirtual()) { return; } if (alignContent == null) { setAlignContent(YogaAlign.FLEX_START); return; } switch (alignContent) { case "auto": { setAlignContent(YogaAlign.AUTO); return; } case "flex-start": { setAlignContent(YogaAlign.FLEX_START); return; } case "center": { setAlignContent(YogaAlign.CENTER); return; } case "flex-end": { setAlignContent(YogaAlign.FLEX_END); return; } case "stretch": { setAlignContent(YogaAlign.STRETCH); return; } case "baseline": { setAlignContent(YogaAlign.BASELINE); return; } case "space-between": { setAlignContent(YogaAlign.SPACE_BETWEEN); return; } case "space-around": { setAlignContent(YogaAlign.SPACE_AROUND); return; } default: { throw new JSApplicationIllegalArgumentException("invalid value for alignContent: " + alignContent); } } } @ReactProp(name = ViewProps.JUSTIFY_CONTENT) public void setJustifyContent(@Nullable String justifyContent) { if (isVirtual()) { return; } if (justifyContent == null) { setJustifyContent(YogaJustify.FLEX_START); return; } switch (justifyContent) { case "flex-start": { setJustifyContent(YogaJustify.FLEX_START); break; } case "center": { setJustifyContent(YogaJustify.CENTER); break; } case "flex-end": { setJustifyContent(YogaJustify.FLEX_END); break; } case "space-between": { setJustifyContent(YogaJustify.SPACE_BETWEEN); break; } case "space-around": { setJustifyContent(YogaJustify.SPACE_AROUND); break; } case "space-evenly": { setJustifyContent(YogaJustify.SPACE_EVENLY); break; } default: { throw new JSApplicationIllegalArgumentException("invalid value for justifyContent: " + justifyContent); } } } @ReactProp(name = ViewProps.OVERFLOW) public void setOverflow(@Nullable String overflow) { if (isVirtual()) { return; } if (overflow == null) { setOverflow(YogaOverflow.VISIBLE); return; } switch (overflow) { case "visible": { setOverflow(YogaOverflow.VISIBLE); break; } case "hidden": { setOverflow(YogaOverflow.HIDDEN); break; } case "scroll": { setOverflow(YogaOverflow.SCROLL); break; } default: { throw new JSApplicationIllegalArgumentException("invalid value for overflow: " + overflow); } } } @ReactProp(name = ViewProps.DISPLAY) public void setDisplay(@Nullable String display) { if (isVirtual()) { return; } if (display == null) { setDisplay(YogaDisplay.FLEX); return; } switch (display) { case "flex": { setDisplay(YogaDisplay.FLEX); break; } case "none": { setDisplay(YogaDisplay.NONE); break; } default: { throw new JSApplicationIllegalArgumentException("invalid value for display: " + display); } } } @ReactPropGroup(names = { ViewProps.MARGIN, ViewProps.MARGIN_VERTICAL, ViewProps.MARGIN_HORIZONTAL, ViewProps.MARGIN_START, ViewProps.MARGIN_END, ViewProps.MARGIN_TOP, ViewProps.MARGIN_BOTTOM, ViewProps.MARGIN_LEFT, ViewProps.MARGIN_RIGHT, }) public void setMargins(int index, Dynamic margin) { if (isVirtual()) { return; } int spacingType = maybeTransformLeftRightToStartEnd(ViewProps.PADDING_MARGIN_SPACING_TYPES[index]); mTempYogaValue.setFromDynamic(margin); switch (mTempYogaValue.unit) { case POINT: case UNDEFINED: setMargin(spacingType, mTempYogaValue.value); break; case AUTO: setMarginAuto(spacingType); break; case PERCENT: setMarginPercent(spacingType, mTempYogaValue.value); break; } margin.recycle(); } @ReactPropGroup(names = { ViewProps.PADDING, ViewProps.PADDING_VERTICAL, ViewProps.PADDING_HORIZONTAL, ViewProps.PADDING_START, ViewProps.PADDING_END, ViewProps.PADDING_TOP, ViewProps.PADDING_BOTTOM, ViewProps.PADDING_LEFT, ViewProps.PADDING_RIGHT, }) public void setPaddings(int index, Dynamic padding) { if (isVirtual()) { return; } int spacingType = maybeTransformLeftRightToStartEnd(ViewProps.PADDING_MARGIN_SPACING_TYPES[index]); mTempYogaValue.setFromDynamic(padding); switch (mTempYogaValue.unit) { case POINT: case UNDEFINED: setPadding(spacingType, mTempYogaValue.value); break; case PERCENT: setPaddingPercent(spacingType, mTempYogaValue.value); break; } padding.recycle(); } @ReactPropGroup(names = { ViewProps.BORDER_WIDTH, ViewProps.BORDER_START_WIDTH, ViewProps.BORDER_END_WIDTH, ViewProps.BORDER_TOP_WIDTH, ViewProps.BORDER_BOTTOM_WIDTH, ViewProps.BORDER_LEFT_WIDTH, ViewProps.BORDER_RIGHT_WIDTH, }, defaultFloat = YogaConstants.UNDEFINED) public void setBorderWidths(int index, float borderWidth) { if (isVirtual()) { return; } int spacingType = maybeTransformLeftRightToStartEnd(ViewProps.BORDER_SPACING_TYPES[index]); setBorder(spacingType, PixelUtil.toPixelFromDIP(borderWidth)); } @ReactPropGroup(names = { ViewProps.START, ViewProps.END, ViewProps.LEFT, ViewProps.RIGHT, ViewProps.TOP, ViewProps.BOTTOM, }) public void setPositionValues(int index, Dynamic position) { if (isVirtual()) { return; } final int[] POSITION_SPACING_TYPES = { Spacing.START, Spacing.END, Spacing.LEFT, Spacing.RIGHT, Spacing.TOP, Spacing.BOTTOM }; int spacingType = maybeTransformLeftRightToStartEnd(POSITION_SPACING_TYPES[index]); mTempYogaValue.setFromDynamic(position); switch (mTempYogaValue.unit) { case POINT: case UNDEFINED: setPosition(spacingType, mTempYogaValue.value); break; case PERCENT: setPositionPercent(spacingType, mTempYogaValue.value); break; } position.recycle(); } private int maybeTransformLeftRightToStartEnd(int spacingType) { if (!I18nUtil.getInstance().doLeftAndRightSwapInRTL(getThemedContext())) { return spacingType; } switch (spacingType) { case Spacing.LEFT: return Spacing.START; case Spacing.RIGHT: return Spacing.END; default: return spacingType; } } @ReactProp(name = ViewProps.POSITION) public void setPosition(@Nullable String position) { if (isVirtual()) { return; } if (position == null) { setPositionType(YogaPositionType.RELATIVE); return; } switch (position) { case "relative": { setPositionType(YogaPositionType.RELATIVE); break; } case "absolute": { setPositionType(YogaPositionType.ABSOLUTE); break; } default: { throw new JSApplicationIllegalArgumentException("invalid value for position: " + position); } } } @Override @ReactProp(name = "onLayout") public void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) { super.setShouldNotifyOnLayout(shouldNotifyOnLayout); } }