Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Text: Implement textAlign justify for android O+ #22477

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions RNTester/js/TextExample.android.js
Expand Up @@ -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>
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down
Expand Up @@ -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)) {
Expand Down Expand Up @@ -217,7 +221,8 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
getPadding(Spacing.END),
getPadding(Spacing.BOTTOM),
getTextAlign(),
mTextBreakStrategy);
mTextBreakStrategy,
mJustificationMode);
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
}
}
Expand Down
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -61,7 +63,8 @@ public ReactTextUpdate(
float paddingEnd,
float paddingBottom,
int textAlign,
int textBreakStrategy) {
int textBreakStrategy,
int justificationMode) {
mText = text;
mJsEventCounter = jsEventCounter;
mContainsImages = containsImages;
Expand All @@ -71,6 +74,7 @@ public ReactTextUpdate(
mPaddingBottom = paddingBottom;
mTextAlign = textAlign;
mTextBreakStrategy = textBreakStrategy;
mJustificationMode = justificationMode;
}

public Spannable getText() {
Expand Down Expand Up @@ -108,4 +112,8 @@ public int getTextAlign() {
public int getTextBreakStrategy() {
return mTextBreakStrategy;
}

public int getJustificationMode() {
return mJustificationMode;
}
}
Expand Up @@ -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
Expand Down
Expand Up @@ -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?
Expand All @@ -88,7 +91,9 @@ public Object updateLocalData(
textViewProps.getEndPadding(),
textViewProps.getBottomPadding(),
textViewProps.getTextAlign(),
textBreakStrategy);
textBreakStrategy,
justificationMode
);
}

@Override
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

}
}

Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -455,19 +456,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);
}

}
}

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