mirror of
https://github.com/status-im/react-native.git
synced 2025-02-26 08:05:34 +00:00
Android: Expose textBreakStrategy on Text and TextInput
Summary: Android has a text API called breakStrategy for controlling how paragraphs are broken up into lines. For example, some modes support automatically hyphenating words so a word can be split across lines while others do not. One source of complexity is that Android provides different defaults for `breakStrategy` for `TextView` vs `EditText`. `TextView`'s default is `BREAK_STRATEGY_HIGH_QUALITY` while `EditText`'s default is `BREAK_STRATEGY_SIMPLE`. In addition to exposing `textBreakStrategy`, this change also fixes a couple of rendering glitches with `Text` and `TextInput`. `TextView` and `EditText` have different default values for `breakStrategy` and `hyphenationFrequency` than `StaticLayout`. Consequently, we were using different parameters for measuring and rendering. Whenever measuring and rendering parameters are inconsistent, it can result in visual glitches such as the text taking up too much space or being clipped. This change fixes these inconsistencies by setting `breakStrategy` and `hyphenat Closes https://github.com/facebook/react-native/pull/11007 Differential Revision: D4227495 Pulled By: lacker fbshipit-source-id: c2d96bd0ddc7bd315fda016fb4f1b5108a2e35cf
This commit is contained in:
parent
c93643c079
commit
c0ea23cfb0
@ -330,6 +330,12 @@ const TextInput = React.createClass({
|
||||
* The default value is `false`.
|
||||
*/
|
||||
multiline: PropTypes.bool,
|
||||
/**
|
||||
* Set text break strategy on Android API Level 23+, possible values are `simple`, `highQuality`, `balanced`
|
||||
* The default value is `simple`.
|
||||
* @platform android
|
||||
*/
|
||||
textBreakStrategy: React.PropTypes.oneOf(['simple', 'highQuality', 'balanced']),
|
||||
/**
|
||||
* Callback that is called when the text input is blurred.
|
||||
*/
|
||||
@ -724,6 +730,7 @@ const TextInput = React.createClass({
|
||||
text={this._getText()}
|
||||
children={children}
|
||||
disableFullscreenUI={this.props.disableFullscreenUI}
|
||||
textBreakStrategy={this.props.textBreakStrategy}
|
||||
/>;
|
||||
|
||||
return (
|
||||
|
@ -33,6 +33,7 @@ const viewConfig = {
|
||||
selectable: true,
|
||||
adjustsFontSizeToFit: true,
|
||||
minimumFontScale: true,
|
||||
textBreakStrategy: true,
|
||||
}),
|
||||
uiViewClassName: 'RCTText',
|
||||
};
|
||||
@ -116,6 +117,12 @@ const Text = React.createClass({
|
||||
* This prop is commonly used with `ellipsizeMode`.
|
||||
*/
|
||||
numberOfLines: React.PropTypes.number,
|
||||
/**
|
||||
* Set text break strategy on Android API Level 23+, possible values are `simple`, `highQuality`, `balanced`
|
||||
* The default value is `highQuality`.
|
||||
* @platform android
|
||||
*/
|
||||
textBreakStrategy: React.PropTypes.oneOf(['simple', 'highQuality', 'balanced']),
|
||||
/**
|
||||
* Invoked on mount and layout changes with
|
||||
*
|
||||
|
@ -82,6 +82,7 @@ public class ViewProps {
|
||||
public static final String TEXT_ALIGN = "textAlign";
|
||||
public static final String TEXT_ALIGN_VERTICAL = "textAlignVertical";
|
||||
public static final String TEXT_DECORATION_LINE = "textDecorationLine";
|
||||
public static final String TEXT_BREAK_STRATEGY = "textBreakStrategy";
|
||||
|
||||
public static final String ALLOW_FONT_SCALING = "allowFontScaling";
|
||||
|
||||
|
@ -15,6 +15,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.text.BoringLayout;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
@ -248,14 +249,27 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
||||
(!YogaConstants.isUndefined(desiredWidth) && desiredWidth <= width))) {
|
||||
// Is used when the width is not known and the text is not boring, ie. if it contains
|
||||
// unicode characters.
|
||||
layout = new StaticLayout(
|
||||
|
||||
int hintWidth = (int) Math.ceil(desiredWidth);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
layout = new StaticLayout(
|
||||
text,
|
||||
textPaint,
|
||||
(int) Math.ceil(desiredWidth),
|
||||
hintWidth,
|
||||
Layout.Alignment.ALIGN_NORMAL,
|
||||
1.f,
|
||||
0.f,
|
||||
true);
|
||||
} else {
|
||||
layout = StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, hintWidth)
|
||||
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
|
||||
.setLineSpacing(0.f, 1.f)
|
||||
.setIncludePad(true)
|
||||
.setBreakStrategy(mTextBreakStrategy)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build();
|
||||
}
|
||||
|
||||
} else if (boring != null && (unconstrainedWidth || boring.width <= width)) {
|
||||
// Is used for single-line, boring text when the width is either unknown or bigger
|
||||
// than the width of the text.
|
||||
@ -270,14 +284,25 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
||||
true);
|
||||
} else {
|
||||
// Is used for multiline, boring text and the width is known.
|
||||
layout = new StaticLayout(
|
||||
text,
|
||||
textPaint,
|
||||
(int) width,
|
||||
Layout.Alignment.ALIGN_NORMAL,
|
||||
1.f,
|
||||
0.f,
|
||||
true);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
layout = new StaticLayout(
|
||||
text,
|
||||
textPaint,
|
||||
(int) width,
|
||||
Layout.Alignment.ALIGN_NORMAL,
|
||||
1.f,
|
||||
0.f,
|
||||
true);
|
||||
} else {
|
||||
layout = StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, (int) width)
|
||||
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
|
||||
.setLineSpacing(0.f, 1.f)
|
||||
.setIncludePad(true)
|
||||
.setBreakStrategy(mTextBreakStrategy)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
if (mNumberOfLines != UNSET &&
|
||||
@ -317,6 +342,8 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
||||
protected float mFontSizeInput = UNSET;
|
||||
protected int mLineHeightInput = UNSET;
|
||||
protected int mTextAlign = Gravity.NO_GRAVITY;
|
||||
protected int mTextBreakStrategy = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ?
|
||||
0 : Layout.BREAK_STRATEGY_HIGH_QUALITY;
|
||||
|
||||
private float mTextShadowOffsetDx = 0;
|
||||
private float mTextShadowOffsetDy = 0;
|
||||
@ -549,6 +576,25 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.TEXT_BREAK_STRATEGY)
|
||||
public void setTextBreakStrategy(@Nullable String textBreakStrategy) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (textBreakStrategy == null || "highQuality".equals(textBreakStrategy)) {
|
||||
mTextBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
|
||||
} else if ("simple".equals(textBreakStrategy)) {
|
||||
mTextBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
|
||||
} else if ("balanced".equals(textBreakStrategy)) {
|
||||
mTextBreakStrategy = Layout.BREAK_STRATEGY_BALANCED;
|
||||
} else {
|
||||
throw new JSApplicationIllegalArgumentException("Invalid textBreakStrategy: " + textBreakStrategy);
|
||||
}
|
||||
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SHADOW_OFFSET)
|
||||
public void setTextShadowOffset(ReadableMap offsetMap) {
|
||||
mTextShadowOffsetDx = 0;
|
||||
@ -607,7 +653,8 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
||||
getPadding(Spacing.TOP),
|
||||
getPadding(Spacing.END),
|
||||
getPadding(Spacing.BOTTOM),
|
||||
getTextAlign()
|
||||
getTextAlign(),
|
||||
mTextBreakStrategy
|
||||
);
|
||||
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
|
||||
/**
|
||||
@ -26,6 +27,32 @@ public class ReactTextUpdate {
|
||||
private final float mPaddingRight;
|
||||
private final float mPaddingBottom;
|
||||
private final int mTextAlign;
|
||||
private final int mTextBreakStrategy;
|
||||
|
||||
/**
|
||||
* @deprecated Use a non-deprecated constructor for ReactTextUpdate instead. This one remains
|
||||
* because it's being used by a unit test that isn't currently open source.
|
||||
*/
|
||||
@Deprecated
|
||||
public ReactTextUpdate(
|
||||
Spannable text,
|
||||
int jsEventCounter,
|
||||
boolean containsImages,
|
||||
float paddingStart,
|
||||
float paddingTop,
|
||||
float paddingEnd,
|
||||
float paddingBottom,
|
||||
int textAlign) {
|
||||
this(text,
|
||||
jsEventCounter,
|
||||
containsImages,
|
||||
paddingStart,
|
||||
paddingTop,
|
||||
paddingEnd,
|
||||
paddingBottom,
|
||||
textAlign,
|
||||
Layout.BREAK_STRATEGY_HIGH_QUALITY);
|
||||
}
|
||||
|
||||
public ReactTextUpdate(
|
||||
Spannable text,
|
||||
@ -35,7 +62,8 @@ public class ReactTextUpdate {
|
||||
float paddingTop,
|
||||
float paddingEnd,
|
||||
float paddingBottom,
|
||||
int textAlign) {
|
||||
int textAlign,
|
||||
int textBreakStrategy) {
|
||||
mText = text;
|
||||
mJsEventCounter = jsEventCounter;
|
||||
mContainsImages = containsImages;
|
||||
@ -44,6 +72,7 @@ public class ReactTextUpdate {
|
||||
mPaddingRight = paddingEnd;
|
||||
mPaddingBottom = paddingBottom;
|
||||
mTextAlign = textAlign;
|
||||
mTextBreakStrategy = textBreakStrategy;
|
||||
}
|
||||
|
||||
public Spannable getText() {
|
||||
@ -77,4 +106,8 @@ public class ReactTextUpdate {
|
||||
public int getTextAlign() {
|
||||
return mTextAlign;
|
||||
}
|
||||
|
||||
public int getTextBreakStrategy() {
|
||||
return mTextBreakStrategy;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.os.Build;
|
||||
import android.text.Layout;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
@ -69,6 +70,11 @@ public class ReactTextView extends TextView implements ReactCompoundView {
|
||||
mTextAlign = nextTextAlign;
|
||||
}
|
||||
setGravityHorizontal(mTextAlign);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (getBreakStrategy() != update.getTextBreakStrategy()) {
|
||||
setBreakStrategy(update.getTextBreakStrategy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,6 +19,7 @@ import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.os.Build;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.SpannableStringBuilder;
|
||||
@ -340,6 +341,11 @@ public class ReactEditText extends EditText {
|
||||
mIsSettingTextFromJS = true;
|
||||
getText().replace(0, length(), spannableStringBuilder);
|
||||
mIsSettingTextFromJS = false;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (getBreakStrategy() != reactTextUpdate.getTextBreakStrategy()) {
|
||||
setBreakStrategy(reactTextUpdate.getTextBreakStrategy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,7 +10,10 @@
|
||||
package com.facebook.react.views.textinput;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.OverridingMethodsMustInvokeSuper;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
@ -22,12 +25,14 @@ import com.facebook.yoga.YogaMeasureFunction;
|
||||
import com.facebook.yoga.YogaNodeAPI;
|
||||
import com.facebook.yoga.YogaMeasureOutput;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import com.facebook.react.uimanager.ViewDefaults;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.views.view.MeasureUtil;
|
||||
import com.facebook.react.views.text.ReactTextShadowNode;
|
||||
@ -42,6 +47,8 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements
|
||||
private int mJsEventCount = UNSET;
|
||||
|
||||
public ReactTextInputShadowNode() {
|
||||
mTextBreakStrategy = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ?
|
||||
0 : Layout.BREAK_STRATEGY_SIMPLE;
|
||||
setMeasureFunction(this);
|
||||
}
|
||||
|
||||
@ -100,6 +107,12 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements
|
||||
editText.setLines(mNumberOfLines);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (editText.getBreakStrategy() != mTextBreakStrategy) {
|
||||
editText.setBreakStrategy(mTextBreakStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
editText.measure(
|
||||
MeasureUtil.getMeasureSpec(width, widthMode),
|
||||
MeasureUtil.getMeasureSpec(height, heightMode));
|
||||
@ -118,6 +131,23 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements
|
||||
mJsEventCount = mostRecentEventCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextBreakStrategy(@Nullable String textBreakStrategy) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (textBreakStrategy == null || "simple".equals(textBreakStrategy)) {
|
||||
mTextBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
|
||||
} else if ("highQuality".equals(textBreakStrategy)) {
|
||||
mTextBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
|
||||
} else if ("balanced".equals(textBreakStrategy)) {
|
||||
mTextBreakStrategy = Layout.BREAK_STRATEGY_BALANCED;
|
||||
} else {
|
||||
throw new JSApplicationIllegalArgumentException("Invalid textBreakStrategy: " + textBreakStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
|
||||
super.onCollectExtraUpdates(uiViewOperationQueue);
|
||||
@ -146,7 +176,8 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements
|
||||
getPadding(Spacing.TOP),
|
||||
getPadding(Spacing.END),
|
||||
getPadding(Spacing.BOTTOM),
|
||||
mTextAlign
|
||||
mTextAlign,
|
||||
mTextBreakStrategy
|
||||
);
|
||||
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user