mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-03-06 17:34:07 +08:00
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:
committed by
Facebook Github Bot
parent
af52693e9c
commit
d2153fc58d
@@ -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 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import static org.mockito.Mockito.mock;
|
||||
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 class ReactTextTest {
|
||||
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 @@ package com.facebook.react.views.textinput;
|
||||
|
||||
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 class ReactTextInputPropertyTest {
|
||||
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"));
|
||||
|
||||
Reference in New Issue
Block a user