Skip to content
Permalink
Browse files

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
  • Loading branch information...
sunnylqm authored and facebook-github-bot committed Mar 5, 2019
1 parent af52693 commit d2153fc58d825006076a3fce12e0f7eb84479132
@@ -325,6 +325,12 @@ class TextExample extends React.Component<{}> {
right right right right right right right right right right right
right right
</Text>
<Text style={{textAlign: 'justify'}}>
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.
</Text>
</RNTesterBlock>
<RNTesterBlock title="Unicode">
<View>
@@ -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();
}
@@ -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);
}
}
@@ -26,6 +26,7 @@
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;
}
}
@@ -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
@@ -79,6 +79,9 @@ public Object updateLocalData(
// TODO add textBreakStrategy prop into local Data
int textBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;

// TODO add justificationMode prop into local Data
int justificationMode = Layout.JUSTIFICATION_MODE_NONE;

return new ReactTextUpdate(
spanned,
-1, // TODO add this into local Data?
@@ -88,7 +91,9 @@ public Object updateLocalData(
textViewProps.getEndPadding(),
textViewProps.getBottomPadding(),
textViewProps.getTextAlign(),
textBreakStrategy);
textBreakStrategy,
justificationMode
);
}

@Override
@@ -50,6 +50,8 @@
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);
}

}
}

@@ -15,6 +15,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;
@@ -460,19 +461,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);
}

}
}

@@ -204,7 +204,8 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
getPadding(Spacing.RIGHT),
getPadding(Spacing.BOTTOM),
mTextAlign,
mTextBreakStrategy);
mTextBreakStrategy,
mJustificationMode);
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
}
}
@@ -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.
*/
@@ -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"));

0 comments on commit d2153fc

Please sign in to comment.
You can’t perform that action at this time.