Text: Implement textAlign justify for android O+ (#22477)

Summary:
Add textAlign justify for android O+(api level >=26)
Resolves https://github.com/facebook/react-native/issues/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: https://github.com/facebook/react-native/pull/22477

Differential Revision: D13512004

Pulled By: cpojer

fbshipit-source-id: e20f4976bfd957a5faeae0bbed2ff27c03023bb1
This commit is contained in:
Sunny Luo
2019-03-05 00:35:08 -08:00
committed by Facebook Github Bot
parent af52693e9c
commit d2153fc58d
11 changed files with 130 additions and 45 deletions
@@ -265,6 +265,9 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
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 abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
@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 class ReactTextShadowNode extends ReactBaseTextShadowNode {
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 class ReactTextShadowNode extends ReactBaseTextShadowNode {
getPadding(Spacing.END),
getPadding(Spacing.BOTTOM),
getTextAlign(),
mTextBreakStrategy);
mTextBreakStrategy,
mJustificationMode);
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
}
}
@@ -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 class 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 class ReactTextUpdate {
float paddingEnd,
float paddingBottom,
int textAlign,
int textBreakStrategy) {
int textBreakStrategy,
int justificationMode) {
mText = text;
mJsEventCounter = jsEventCounter;
mContainsImages = containsImages;
@@ -71,6 +74,7 @@ public class ReactTextUpdate {
mPaddingBottom = paddingBottom;
mTextAlign = textAlign;
mTextBreakStrategy = textBreakStrategy;
mJustificationMode = justificationMode;
}
public Spannable getText() {
@@ -108,4 +112,8 @@ public class ReactTextUpdate {
public int getTextBreakStrategy() {
return mTextBreakStrategy;
}
public int getJustificationMode() {
return mJustificationMode;
}
}
@@ -72,6 +72,11 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
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 class ReactTextViewManager
// 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 class ReactTextViewManager
textViewProps.getEndPadding(),
textViewProps.getBottomPadding(),
textViewProps.getTextAlign(),
textBreakStrategy);
textBreakStrategy,
justificationMode
);
}
@Override
@@ -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 class TextAttributeProps {
}
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.support.v4.content.ContextCompat;
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 class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
@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 class ReactTextInputShadowNode extends ReactBaseTextShadowNode
getPadding(Spacing.RIGHT),
getPadding(Spacing.BOTTOM),
mTextAlign,
mTextBreakStrategy);
mTextBreakStrategy,
mJustificationMode);
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
}
}