diff --git a/RNTester/js/TextExample.android.js b/RNTester/js/TextExample.android.js
index d299c825635360..1eeeda49cc6bcd 100644
--- a/RNTester/js/TextExample.android.js
+++ b/RNTester/js/TextExample.android.js
@@ -325,6 +325,12 @@ class TextExample extends React.Component<{}> {
right right right right right right right right right right right
right right
+
+ justify (works when api level >= 26 otherwise fallbacks to "left"):
+ this text component{"'"}s contents are laid out with "textAlign:
+ justify" and as you can see all of the lines except the last one
+ span the available width of the parent container.
+
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java
index ec00cd5b30662e..5b9d5b21df52fd 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java
@@ -265,6 +265,9 @@ private static int parseNumericFontWeight(String fontWeightString) {
protected int mTextAlign = Gravity.NO_GRAVITY;
protected int mTextBreakStrategy =
(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY;
+ protected int mJustificationMode =
+ (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE;
+ protected TextTransform mTextTransform = TextTransform.UNSET;
protected float mTextShadowOffsetDx = 0;
protected float mTextShadowOffsetDy = 0;
@@ -357,19 +360,28 @@ public void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
@ReactProp(name = ViewProps.TEXT_ALIGN)
public void setTextAlign(@Nullable String textAlign) {
- if (textAlign == null || "auto".equals(textAlign)) {
- mTextAlign = Gravity.NO_GRAVITY;
- } else if ("left".equals(textAlign)) {
- mTextAlign = Gravity.LEFT;
- } else if ("right".equals(textAlign)) {
- mTextAlign = Gravity.RIGHT;
- } else if ("center".equals(textAlign)) {
- mTextAlign = Gravity.CENTER_HORIZONTAL;
- } else if ("justify".equals(textAlign)) {
- // Fallback gracefully for cross-platform compat instead of error
+ if ("justify".equals(textAlign)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mJustificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD;
+ }
mTextAlign = Gravity.LEFT;
} else {
- throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
+ }
+
+ if (textAlign == null || "auto".equals(textAlign)) {
+ mTextAlign = Gravity.NO_GRAVITY;
+ } else if ("left".equals(textAlign)) {
+ mTextAlign = Gravity.LEFT;
+ } else if ("right".equals(textAlign)) {
+ mTextAlign = Gravity.RIGHT;
+ } else if ("center".equals(textAlign)) {
+ mTextAlign = Gravity.CENTER_HORIZONTAL;
+ } else {
+ throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
+ }
+
}
markUpdated();
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
index eaef5690304b33..5cf16db80f0fd4 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
@@ -99,14 +99,18 @@ public long measure(
new StaticLayout(
text, textPaint, hintWidth, alignment, 1.f, 0.f, mIncludeFontPadding);
} else {
- layout =
+ StaticLayout.Builder builder =
StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, hintWidth)
- .setAlignment(alignment)
- .setLineSpacing(0.f, 1.f)
- .setIncludePad(mIncludeFontPadding)
- .setBreakStrategy(mTextBreakStrategy)
- .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
- .build();
+ .setAlignment(alignment)
+ .setLineSpacing(0.f, 1.f)
+ .setIncludePad(mIncludeFontPadding)
+ .setBreakStrategy(mTextBreakStrategy)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ builder.setJustificationMode(mJustificationMode);
+ }
+ layout = builder.build();
}
} else if (boring != null && (unconstrainedWidth || boring.width <= width)) {
@@ -217,7 +221,8 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
getPadding(Spacing.END),
getPadding(Spacing.BOTTOM),
getTextAlign(),
- mTextBreakStrategy);
+ mTextBreakStrategy,
+ mJustificationMode);
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
}
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java
index c31bb3c55ea52d..fd1344f0fb714d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java
@@ -26,6 +26,7 @@ public class ReactTextUpdate {
private final float mPaddingBottom;
private final int mTextAlign;
private final int mTextBreakStrategy;
+ private final int mJustificationMode;
/**
* @deprecated Use a non-deprecated constructor for ReactTextUpdate instead. This one remains
@@ -49,7 +50,8 @@ public ReactTextUpdate(
paddingEnd,
paddingBottom,
textAlign,
- Layout.BREAK_STRATEGY_HIGH_QUALITY);
+ Layout.BREAK_STRATEGY_HIGH_QUALITY,
+ Layout.JUSTIFICATION_MODE_NONE);
}
public ReactTextUpdate(
@@ -61,7 +63,8 @@ public ReactTextUpdate(
float paddingEnd,
float paddingBottom,
int textAlign,
- int textBreakStrategy) {
+ int textBreakStrategy,
+ int justificationMode) {
mText = text;
mJsEventCounter = jsEventCounter;
mContainsImages = containsImages;
@@ -71,6 +74,7 @@ public ReactTextUpdate(
mPaddingBottom = paddingBottom;
mTextAlign = textAlign;
mTextBreakStrategy = textBreakStrategy;
+ mJustificationMode = justificationMode;
}
public Spannable getText() {
@@ -108,4 +112,8 @@ public int getTextAlign() {
public int getTextBreakStrategy() {
return mTextBreakStrategy;
}
+
+ public int getJustificationMode() {
+ return mJustificationMode;
+ }
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
index 7e99cb1429d8df..a804a46a94a6d5 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
@@ -72,6 +72,11 @@ public void setText(ReactTextUpdate update) {
setBreakStrategy(update.getTextBreakStrategy());
}
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ if (getJustificationMode() != update.getJustificationMode()) {
+ setJustificationMode(update.getJustificationMode());
+ }
+ }
}
@Override
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java
index 226776b7dd6d0d..81323b335272d0 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java
@@ -82,8 +82,10 @@ public Object updateLocalData(ReactTextView view, ReactStylesDiffMap props, Reac
// TODO add textBreakStrategy prop into local Data
int textBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
- return
- new ReactTextUpdate(
+ // TODO add justificationMode prop into local Data
+ int justificationMode = Layout.JUSTIFICATION_MODE_NONE;
+
+ return new ReactTextUpdate(
spanned,
-1, // TODO add this into local Data?
false, // TODO add this into local Data
@@ -92,7 +94,8 @@ public Object updateLocalData(ReactTextView view, ReactStylesDiffMap props, Reac
textViewProps.getEndPadding(),
textViewProps.getBottomPadding(),
textViewProps.getTextAlign(),
- textBreakStrategy
+ textBreakStrategy,
+ justificationMode
);
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java
index cbf49e34dc0e84..e23961d75d8b44 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java
@@ -50,6 +50,8 @@ public class TextAttributeProps {
protected int mTextAlign = Gravity.NO_GRAVITY;
protected int mTextBreakStrategy =
(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY;
+ protected int mJustificationMode =
+ (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE;
protected TextTransform mTextTransform = TextTransform.UNSET;
protected float mTextShadowOffsetDx = 0;
@@ -204,19 +206,28 @@ public void setAllowFontScaling(boolean allowFontScaling) {
}
public void setTextAlign(@Nullable String textAlign) {
- if (textAlign == null || "auto".equals(textAlign)) {
- mTextAlign = Gravity.NO_GRAVITY;
- } else if ("left".equals(textAlign)) {
- mTextAlign = Gravity.LEFT;
- } else if ("right".equals(textAlign)) {
- mTextAlign = Gravity.RIGHT;
- } else if ("center".equals(textAlign)) {
- mTextAlign = Gravity.CENTER_HORIZONTAL;
- } else if ("justify".equals(textAlign)) {
- // Fallback gracefully for cross-platform compat instead of error
+ if ("justify".equals(textAlign)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mJustificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD;
+ }
mTextAlign = Gravity.LEFT;
} else {
- throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
+ }
+
+ if (textAlign == null || "auto".equals(textAlign)) {
+ mTextAlign = Gravity.NO_GRAVITY;
+ } else if ("left".equals(textAlign)) {
+ mTextAlign = Gravity.LEFT;
+ } else if ("right".equals(textAlign)) {
+ mTextAlign = Gravity.RIGHT;
+ } else if ("center".equals(textAlign)) {
+ mTextAlign = Gravity.CENTER_HORIZONTAL;
+ } else {
+ throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
+ }
+
}
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
index 71e56882be88a9..4ba54a7f6c6661 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
@@ -14,6 +14,7 @@
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
+import android.text.Layout;
import android.text.Spannable;
import android.text.TextWatcher;
import android.util.TypedValue;
@@ -435,19 +436,28 @@ public void setUnderlineColor(ReactEditText view, @Nullable Integer underlineCol
@ReactProp(name = ViewProps.TEXT_ALIGN)
public void setTextAlign(ReactEditText view, @Nullable String textAlign) {
- if (textAlign == null || "auto".equals(textAlign)) {
- view.setGravityHorizontal(Gravity.NO_GRAVITY);
- } else if ("left".equals(textAlign)) {
- view.setGravityHorizontal(Gravity.LEFT);
- } else if ("right".equals(textAlign)) {
- view.setGravityHorizontal(Gravity.RIGHT);
- } else if ("center".equals(textAlign)) {
- view.setGravityHorizontal(Gravity.CENTER_HORIZONTAL);
- } else if ("justify".equals(textAlign)) {
- // Fallback gracefully for cross-platform compat instead of error
+ if ("justify".equals(textAlign)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ view.setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD);
+ }
view.setGravityHorizontal(Gravity.LEFT);
} else {
- throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ view.setJustificationMode(Layout.JUSTIFICATION_MODE_NONE);
+ }
+
+ if (textAlign == null || "auto".equals(textAlign)) {
+ view.setGravityHorizontal(Gravity.NO_GRAVITY);
+ } else if ("left".equals(textAlign)) {
+ view.setGravityHorizontal(Gravity.LEFT);
+ } else if ("right".equals(textAlign)) {
+ view.setGravityHorizontal(Gravity.RIGHT);
+ } else if ("center".equals(textAlign)) {
+ view.setGravityHorizontal(Gravity.CENTER_HORIZONTAL);
+ } else {
+ throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
+ }
+
}
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java
index 53efe02e06e46d..f40a05dde07cd9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java
@@ -204,7 +204,8 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
getPadding(Spacing.RIGHT),
getPadding(Spacing.BOTTOM),
mTextAlign,
- mTextBreakStrategy);
+ mTextBreakStrategy,
+ mJustificationMode);
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
}
}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java
index 7938bbffd2625f..05af680b549ab0 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java
@@ -15,6 +15,7 @@
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.text.Layout;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
@@ -419,6 +420,21 @@ public void testMaxLinesApplied() {
assertThat(textView.getEllipsize()).isEqualTo(TextUtils.TruncateAt.END);
}
+ @TargetApi(Build.VERSION_CODES.O)
+ @Test
+ public void testTextAlignJustifyApplied() {
+ UIManagerModule uiManager = getUIManagerModule();
+
+ ReactRootView rootView = createText(
+ uiManager,
+ JavaOnlyMap.of("textAlign", "justify"),
+ JavaOnlyMap.of(ReactRawTextShadowNode.PROP_TEXT, "test text"));
+
+ TextView textView = (TextView) rootView.getChildAt(0);
+ assertThat(textView.getText().toString()).isEqualTo("test text");
+ assertThat(textView.getJustificationMode()).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD);
+ }
+
/**
* Make sure TextView has exactly one span and that span has given type.
*/
diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java
index 50fc92e218d3ed..3bf3865b6e298d 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java
@@ -9,8 +9,10 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.os.Build;
import android.text.InputType;
import android.text.InputFilter;
+import android.text.Layout;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.inputmethod.EditorInfo;
@@ -344,6 +346,10 @@ public void testTextAlign() {
assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.CENTER_HORIZONTAL);
mManager.updateProperties(view, buildStyles("textAlign", null));
assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(defaultHorizontalGravity);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mManager.updateProperties(view, buildStyles("textAlign", "justify"));
+ assertThat(view.getJustificationMode()).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD);
+ }
// TextAlignVertical
mManager.updateProperties(view, buildStyles("textAlignVertical", "top"));