Skip to content

Commit d2153fc

Browse files
sunnylqmfacebook-github-bot
authored andcommitted
Text: Implement textAlign justify for android O+ (#22477)
Summary: Add textAlign justify for android O+(api level >=26) Resolves #22475 <img src="https://user-images.githubusercontent.com/615282/49341207-35e3b980-f685-11e8-91ab-dbc19c1ee4d0.gif" width="400" /> Changelog: ---------- [Android] [Added] - Implement textAlign justify for android O+ Pull Request resolved: #22477 Differential Revision: D13512004 Pulled By: cpojer fbshipit-source-id: e20f4976bfd957a5faeae0bbed2ff27c03023bb1
1 parent af52693 commit d2153fc

File tree

11 files changed

+130
-45
lines changed

11 files changed

+130
-45
lines changed

Diff for: RNTester/js/TextExample.android.js

+6
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,12 @@ class TextExample extends React.Component<{}> {
325325
right right right right right right right right right right right
326326
right right
327327
</Text>
328+
<Text style={{textAlign: 'justify'}}>
329+
justify (works when api level >= 26 otherwise fallbacks to "left"):
330+
this text component{"'"}s contents are laid out with "textAlign:
331+
justify" and as you can see all of the lines except the last one
332+
span the available width of the parent container.
333+
</Text>
328334
</RNTesterBlock>
329335
<RNTesterBlock title="Unicode">
330336
<View>

Diff for: ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java

+23-11
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ private static int parseNumericFontWeight(String fontWeightString) {
265265
protected int mTextAlign = Gravity.NO_GRAVITY;
266266
protected int mTextBreakStrategy =
267267
(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY;
268+
protected int mJustificationMode =
269+
(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE;
270+
protected TextTransform mTextTransform = TextTransform.UNSET;
268271

269272
protected float mTextShadowOffsetDx = 0;
270273
protected float mTextShadowOffsetDy = 0;
@@ -357,19 +360,28 @@ public void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
357360

358361
@ReactProp(name = ViewProps.TEXT_ALIGN)
359362
public void setTextAlign(@Nullable String textAlign) {
360-
if (textAlign == null || "auto".equals(textAlign)) {
361-
mTextAlign = Gravity.NO_GRAVITY;
362-
} else if ("left".equals(textAlign)) {
363-
mTextAlign = Gravity.LEFT;
364-
} else if ("right".equals(textAlign)) {
365-
mTextAlign = Gravity.RIGHT;
366-
} else if ("center".equals(textAlign)) {
367-
mTextAlign = Gravity.CENTER_HORIZONTAL;
368-
} else if ("justify".equals(textAlign)) {
369-
// Fallback gracefully for cross-platform compat instead of error
363+
if ("justify".equals(textAlign)) {
364+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
365+
mJustificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD;
366+
}
370367
mTextAlign = Gravity.LEFT;
371368
} else {
372-
throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
369+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
370+
mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
371+
}
372+
373+
if (textAlign == null || "auto".equals(textAlign)) {
374+
mTextAlign = Gravity.NO_GRAVITY;
375+
} else if ("left".equals(textAlign)) {
376+
mTextAlign = Gravity.LEFT;
377+
} else if ("right".equals(textAlign)) {
378+
mTextAlign = Gravity.RIGHT;
379+
} else if ("center".equals(textAlign)) {
380+
mTextAlign = Gravity.CENTER_HORIZONTAL;
381+
} else {
382+
throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
383+
}
384+
373385
}
374386
markUpdated();
375387
}

Diff for: ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java

+13-8
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,18 @@ public long measure(
9999
new StaticLayout(
100100
text, textPaint, hintWidth, alignment, 1.f, 0.f, mIncludeFontPadding);
101101
} else {
102-
layout =
102+
StaticLayout.Builder builder =
103103
StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, hintWidth)
104-
.setAlignment(alignment)
105-
.setLineSpacing(0.f, 1.f)
106-
.setIncludePad(mIncludeFontPadding)
107-
.setBreakStrategy(mTextBreakStrategy)
108-
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
109-
.build();
104+
.setAlignment(alignment)
105+
.setLineSpacing(0.f, 1.f)
106+
.setIncludePad(mIncludeFontPadding)
107+
.setBreakStrategy(mTextBreakStrategy)
108+
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
109+
110+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
111+
builder.setJustificationMode(mJustificationMode);
112+
}
113+
layout = builder.build();
110114
}
111115

112116
} else if (boring != null && (unconstrainedWidth || boring.width <= width)) {
@@ -217,7 +221,8 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
217221
getPadding(Spacing.END),
218222
getPadding(Spacing.BOTTOM),
219223
getTextAlign(),
220-
mTextBreakStrategy);
224+
mTextBreakStrategy,
225+
mJustificationMode);
221226
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
222227
}
223228
}

Diff for: ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class ReactTextUpdate {
2626
private final float mPaddingBottom;
2727
private final int mTextAlign;
2828
private final int mTextBreakStrategy;
29+
private final int mJustificationMode;
2930

3031
/**
3132
* @deprecated Use a non-deprecated constructor for ReactTextUpdate instead. This one remains
@@ -49,7 +50,8 @@ public ReactTextUpdate(
4950
paddingEnd,
5051
paddingBottom,
5152
textAlign,
52-
Layout.BREAK_STRATEGY_HIGH_QUALITY);
53+
Layout.BREAK_STRATEGY_HIGH_QUALITY,
54+
Layout.JUSTIFICATION_MODE_NONE);
5355
}
5456

5557
public ReactTextUpdate(
@@ -61,7 +63,8 @@ public ReactTextUpdate(
6163
float paddingEnd,
6264
float paddingBottom,
6365
int textAlign,
64-
int textBreakStrategy) {
66+
int textBreakStrategy,
67+
int justificationMode) {
6568
mText = text;
6669
mJsEventCounter = jsEventCounter;
6770
mContainsImages = containsImages;
@@ -71,6 +74,7 @@ public ReactTextUpdate(
7174
mPaddingBottom = paddingBottom;
7275
mTextAlign = textAlign;
7376
mTextBreakStrategy = textBreakStrategy;
77+
mJustificationMode = justificationMode;
7478
}
7579

7680
public Spannable getText() {
@@ -108,4 +112,8 @@ public int getTextAlign() {
108112
public int getTextBreakStrategy() {
109113
return mTextBreakStrategy;
110114
}
115+
116+
public int getJustificationMode() {
117+
return mJustificationMode;
118+
}
111119
}

Diff for: ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java

+5
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ public void setText(ReactTextUpdate update) {
7272
setBreakStrategy(update.getTextBreakStrategy());
7373
}
7474
}
75+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
76+
if (getJustificationMode() != update.getJustificationMode()) {
77+
setJustificationMode(update.getJustificationMode());
78+
}
79+
}
7580
}
7681

7782
@Override

Diff for: ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ public Object updateLocalData(
7979
// TODO add textBreakStrategy prop into local Data
8080
int textBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
8181

82+
// TODO add justificationMode prop into local Data
83+
int justificationMode = Layout.JUSTIFICATION_MODE_NONE;
84+
8285
return new ReactTextUpdate(
8386
spanned,
8487
-1, // TODO add this into local Data?
@@ -88,7 +91,9 @@ public Object updateLocalData(
8891
textViewProps.getEndPadding(),
8992
textViewProps.getBottomPadding(),
9093
textViewProps.getTextAlign(),
91-
textBreakStrategy);
94+
textBreakStrategy,
95+
justificationMode
96+
);
9297
}
9398

9499
@Override

Diff for: ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java

+22-11
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public class TextAttributeProps {
5050
protected int mTextAlign = Gravity.NO_GRAVITY;
5151
protected int mTextBreakStrategy =
5252
(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY;
53+
protected int mJustificationMode =
54+
(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE;
5355
protected TextTransform mTextTransform = TextTransform.UNSET;
5456

5557
protected float mTextShadowOffsetDx = 0;
@@ -204,19 +206,28 @@ public void setAllowFontScaling(boolean allowFontScaling) {
204206
}
205207

206208
public void setTextAlign(@Nullable String textAlign) {
207-
if (textAlign == null || "auto".equals(textAlign)) {
208-
mTextAlign = Gravity.NO_GRAVITY;
209-
} else if ("left".equals(textAlign)) {
210-
mTextAlign = Gravity.LEFT;
211-
} else if ("right".equals(textAlign)) {
212-
mTextAlign = Gravity.RIGHT;
213-
} else if ("center".equals(textAlign)) {
214-
mTextAlign = Gravity.CENTER_HORIZONTAL;
215-
} else if ("justify".equals(textAlign)) {
216-
// Fallback gracefully for cross-platform compat instead of error
209+
if ("justify".equals(textAlign)) {
210+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
211+
mJustificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD;
212+
}
217213
mTextAlign = Gravity.LEFT;
218214
} else {
219-
throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
215+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
216+
mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
217+
}
218+
219+
if (textAlign == null || "auto".equals(textAlign)) {
220+
mTextAlign = Gravity.NO_GRAVITY;
221+
} else if ("left".equals(textAlign)) {
222+
mTextAlign = Gravity.LEFT;
223+
} else if ("right".equals(textAlign)) {
224+
mTextAlign = Gravity.RIGHT;
225+
} else if ("center".equals(textAlign)) {
226+
mTextAlign = Gravity.CENTER_HORIZONTAL;
227+
} else {
228+
throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
229+
}
230+
220231
}
221232
}
222233

Diff for: ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java

+21-11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.text.Editable;
1616
import android.text.InputFilter;
1717
import android.text.InputType;
18+
import android.text.Layout;
1819
import android.text.Spannable;
1920
import android.text.TextWatcher;
2021
import android.util.TypedValue;
@@ -460,19 +461,28 @@ public void setUnderlineColor(ReactEditText view, @Nullable Integer underlineCol
460461

461462
@ReactProp(name = ViewProps.TEXT_ALIGN)
462463
public void setTextAlign(ReactEditText view, @Nullable String textAlign) {
463-
if (textAlign == null || "auto".equals(textAlign)) {
464-
view.setGravityHorizontal(Gravity.NO_GRAVITY);
465-
} else if ("left".equals(textAlign)) {
466-
view.setGravityHorizontal(Gravity.LEFT);
467-
} else if ("right".equals(textAlign)) {
468-
view.setGravityHorizontal(Gravity.RIGHT);
469-
} else if ("center".equals(textAlign)) {
470-
view.setGravityHorizontal(Gravity.CENTER_HORIZONTAL);
471-
} else if ("justify".equals(textAlign)) {
472-
// Fallback gracefully for cross-platform compat instead of error
464+
if ("justify".equals(textAlign)) {
465+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
466+
view.setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD);
467+
}
473468
view.setGravityHorizontal(Gravity.LEFT);
474469
} else {
475-
throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
470+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
471+
view.setJustificationMode(Layout.JUSTIFICATION_MODE_NONE);
472+
}
473+
474+
if (textAlign == null || "auto".equals(textAlign)) {
475+
view.setGravityHorizontal(Gravity.NO_GRAVITY);
476+
} else if ("left".equals(textAlign)) {
477+
view.setGravityHorizontal(Gravity.LEFT);
478+
} else if ("right".equals(textAlign)) {
479+
view.setGravityHorizontal(Gravity.RIGHT);
480+
} else if ("center".equals(textAlign)) {
481+
view.setGravityHorizontal(Gravity.CENTER_HORIZONTAL);
482+
} else {
483+
throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
484+
}
485+
476486
}
477487
}
478488

Diff for: ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
204204
getPadding(Spacing.RIGHT),
205205
getPadding(Spacing.BOTTOM),
206206
mTextAlign,
207-
mTextBreakStrategy);
207+
mTextBreakStrategy,
208+
mJustificationMode);
208209
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
209210
}
210211
}

Diff for: ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java

+16
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.graphics.Color;
1616
import android.graphics.Typeface;
1717
import android.graphics.drawable.Drawable;
18+
import android.text.Layout;
1819
import android.text.Spanned;
1920
import android.text.TextUtils;
2021
import android.text.style.AbsoluteSizeSpan;
@@ -419,6 +420,21 @@ public void testMaxLinesApplied() {
419420
assertThat(textView.getEllipsize()).isEqualTo(TextUtils.TruncateAt.END);
420421
}
421422

423+
@TargetApi(Build.VERSION_CODES.O)
424+
@Test
425+
public void testTextAlignJustifyApplied() {
426+
UIManagerModule uiManager = getUIManagerModule();
427+
428+
ReactRootView rootView = createText(
429+
uiManager,
430+
JavaOnlyMap.of("textAlign", "justify"),
431+
JavaOnlyMap.of(ReactRawTextShadowNode.PROP_TEXT, "test text"));
432+
433+
TextView textView = (TextView) rootView.getChildAt(0);
434+
assertThat(textView.getText().toString()).isEqualTo("test text");
435+
assertThat(textView.getJustificationMode()).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD);
436+
}
437+
422438
/**
423439
* Make sure TextView has exactly one span and that span has given type.
424440
*/

Diff for: ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java

+6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99

1010
import android.content.res.ColorStateList;
1111
import android.graphics.Color;
12+
import android.os.Build;
1213
import android.text.InputType;
1314
import android.text.InputFilter;
15+
import android.text.Layout;
1416
import android.util.DisplayMetrics;
1517
import android.view.Gravity;
1618
import android.view.inputmethod.EditorInfo;
@@ -344,6 +346,10 @@ public void testTextAlign() {
344346
assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.CENTER_HORIZONTAL);
345347
mManager.updateProperties(view, buildStyles("textAlign", null));
346348
assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(defaultHorizontalGravity);
349+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
350+
mManager.updateProperties(view, buildStyles("textAlign", "justify"));
351+
assertThat(view.getJustificationMode()).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD);
352+
}
347353

348354
// TextAlignVertical
349355
mManager.updateProperties(view, buildStyles("textAlignVertical", "top"));

0 commit comments

Comments
 (0)