Android: Add support for having borders on <Text> & <TextInput> components
Summary: Currently, `<Text>` and `<TextInput>` components on Android do not support borders. This change adds support for the borderRadius, borderColor, and borderWidth props on the `<Text>` and `<TextInput>` components on Android. ReactViewGroup already implements this functionality so we copied its implementation over into the ReactTextView and ReactEditText classes. **Test plan (required)** Verified that the various border props work on Text and TextInput components in a test app. Adam Comella Microsoft Corp. Closes https://github.com/facebook/react-native/pull/9658 Differential Revision: D3819993 Pulled By: lexs fbshipit-source-id: 183b0aa95369dd781f03b5a1f0f409ab47284e39
This commit is contained in:
parent
afde9da93d
commit
28ba749ba0
|
@ -11,6 +11,7 @@ android_library(
|
|||
react_native_target('java/com/facebook/react/common:common'),
|
||||
react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
|
||||
react_native_target('java/com/facebook/react/uimanager:uimanager'),
|
||||
react_native_target('java/com/facebook/react/views/view:view'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
package com.facebook.react.views.text;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.text.Layout;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
|
@ -21,6 +23,7 @@ import android.widget.TextView;
|
|||
import com.facebook.csslayout.FloatUtil;
|
||||
import com.facebook.react.uimanager.ReactCompoundView;
|
||||
import com.facebook.react.uimanager.ViewDefaults;
|
||||
import com.facebook.react.views.view.ReactViewBackgroundDrawable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
@ -38,6 +41,8 @@ public class ReactTextView extends TextView implements ReactCompoundView {
|
|||
private int mNumberOfLines = ViewDefaults.NUMBER_OF_LINES;
|
||||
private TextUtils.TruncateAt mEllipsizeLocation = TextUtils.TruncateAt.END;
|
||||
|
||||
private ReactViewBackgroundDrawable mReactBackgroundDrawable;
|
||||
|
||||
public ReactTextView(Context context) {
|
||||
super(context);
|
||||
mDefaultGravityHorizontal =
|
||||
|
@ -204,6 +209,15 @@ public class ReactTextView extends TextView implements ReactCompoundView {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundColor(int color) {
|
||||
if (color == Color.TRANSPARENT && mReactBackgroundDrawable == null) {
|
||||
// don't do anything, no need to allocate ReactBackgroundDrawable for transparent background
|
||||
} else {
|
||||
getOrCreateReactViewBackground().setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void setGravityHorizontal(int gravityHorizontal) {
|
||||
if (gravityHorizontal == 0) {
|
||||
gravityHorizontal = mDefaultGravityHorizontal;
|
||||
|
@ -233,4 +247,41 @@ public class ReactTextView extends TextView implements ReactCompoundView {
|
|||
@Nullable TextUtils.TruncateAt ellipsizeLocation = mNumberOfLines == ViewDefaults.NUMBER_OF_LINES ? null : mEllipsizeLocation;
|
||||
setEllipsize(ellipsizeLocation);
|
||||
}
|
||||
|
||||
public void setBorderWidth(int position, float width) {
|
||||
getOrCreateReactViewBackground().setBorderWidth(position, width);
|
||||
}
|
||||
|
||||
public void setBorderColor(int position, float color, float alpha) {
|
||||
getOrCreateReactViewBackground().setBorderColor(position, color, alpha);
|
||||
}
|
||||
|
||||
public void setBorderRadius(float borderRadius) {
|
||||
getOrCreateReactViewBackground().setRadius(borderRadius);
|
||||
}
|
||||
|
||||
public void setBorderRadius(float borderRadius, int position) {
|
||||
getOrCreateReactViewBackground().setRadius(borderRadius, position);
|
||||
}
|
||||
|
||||
public void setBorderStyle(@Nullable String style) {
|
||||
getOrCreateReactViewBackground().setBorderStyle(style);
|
||||
}
|
||||
|
||||
private ReactViewBackgroundDrawable getOrCreateReactViewBackground() {
|
||||
if (mReactBackgroundDrawable == null) {
|
||||
mReactBackgroundDrawable = new ReactViewBackgroundDrawable();
|
||||
Drawable backgroundDrawable = getBackground();
|
||||
super.setBackground(null); // required so that drawable callback is cleared before we add the
|
||||
// drawable back as a part of LayerDrawable
|
||||
if (backgroundDrawable == null) {
|
||||
super.setBackground(mReactBackgroundDrawable);
|
||||
} else {
|
||||
LayerDrawable layerDrawable =
|
||||
new LayerDrawable(new Drawable[]{mReactBackgroundDrawable, backgroundDrawable});
|
||||
super.setBackground(layerDrawable);
|
||||
}
|
||||
}
|
||||
return mReactBackgroundDrawable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,13 +16,17 @@ import android.text.TextUtils;
|
|||
import android.view.Gravity;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.facebook.csslayout.CSSConstants;
|
||||
import com.facebook.csslayout.Spacing;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.uimanager.BaseViewManager;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewDefaults;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.uimanager.annotations.ReactPropGroup;
|
||||
|
||||
/**
|
||||
* Manages instances of spannable {@link TextView}.
|
||||
|
@ -37,6 +41,10 @@ public class ReactTextViewManager extends BaseViewManager<ReactTextView, ReactTe
|
|||
@VisibleForTesting
|
||||
public static final String REACT_CLASS = "RCTText";
|
||||
|
||||
private static final int[] SPACING_TYPES = {
|
||||
Spacing.ALL, Spacing.LEFT, Spacing.RIGHT, Spacing.TOP, Spacing.BOTTOM,
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
|
@ -85,7 +93,54 @@ public class ReactTextViewManager extends BaseViewManager<ReactTextView, ReactTe
|
|||
public void setSelectable(ReactTextView view, boolean isSelectable) {
|
||||
view.setTextIsSelectable(isSelectable);
|
||||
}
|
||||
|
||||
@ReactPropGroup(names = {
|
||||
ViewProps.BORDER_RADIUS,
|
||||
ViewProps.BORDER_TOP_LEFT_RADIUS,
|
||||
ViewProps.BORDER_TOP_RIGHT_RADIUS,
|
||||
ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
|
||||
ViewProps.BORDER_BOTTOM_LEFT_RADIUS
|
||||
}, defaultFloat = CSSConstants.UNDEFINED)
|
||||
public void setBorderRadius(ReactTextView view, int index, float borderRadius) {
|
||||
if (!CSSConstants.isUndefined(borderRadius)) {
|
||||
borderRadius = PixelUtil.toPixelFromDIP(borderRadius);
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
view.setBorderRadius(borderRadius);
|
||||
} else {
|
||||
view.setBorderRadius(borderRadius, index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "borderStyle")
|
||||
public void setBorderStyle(ReactTextView view, @Nullable String borderStyle) {
|
||||
view.setBorderStyle(borderStyle);
|
||||
}
|
||||
|
||||
@ReactPropGroup(names = {
|
||||
ViewProps.BORDER_WIDTH,
|
||||
ViewProps.BORDER_LEFT_WIDTH,
|
||||
ViewProps.BORDER_RIGHT_WIDTH,
|
||||
ViewProps.BORDER_TOP_WIDTH,
|
||||
ViewProps.BORDER_BOTTOM_WIDTH,
|
||||
}, defaultFloat = CSSConstants.UNDEFINED)
|
||||
public void setBorderWidth(ReactTextView view, int index, float width) {
|
||||
if (!CSSConstants.isUndefined(width)) {
|
||||
width = PixelUtil.toPixelFromDIP(width);
|
||||
}
|
||||
view.setBorderWidth(SPACING_TYPES[index], width);
|
||||
}
|
||||
|
||||
@ReactPropGroup(names = {
|
||||
"borderColor", "borderLeftColor", "borderRightColor", "borderTopColor", "borderBottomColor"
|
||||
}, customType = "Color")
|
||||
public void setBorderColor(ReactTextView view, int index, Integer color) {
|
||||
float rgbComponent = color == null ? CSSConstants.UNDEFINED : (float) ((int)color & 0x00FFFFFF);
|
||||
float alphaComponent = color == null ? CSSConstants.UNDEFINED : (float) ((int)color >>> 24);
|
||||
view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExtraData(ReactTextView view, Object extraData) {
|
||||
ReactTextUpdate update = (ReactTextUpdate) extraData;
|
||||
|
|
|
@ -14,9 +14,11 @@ import javax.annotation.Nullable;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
@ -39,6 +41,7 @@ import com.facebook.react.views.text.CustomStyleSpan;
|
|||
import com.facebook.react.views.text.ReactTagSpan;
|
||||
import com.facebook.react.views.text.ReactTextUpdate;
|
||||
import com.facebook.react.views.text.TextInlineImageSpan;
|
||||
import com.facebook.react.views.view.ReactViewBackgroundDrawable;
|
||||
|
||||
/**
|
||||
* A wrapper around the EditText that lets us better control what happens when an EditText gets
|
||||
|
@ -76,6 +79,8 @@ public class ReactEditText extends EditText {
|
|||
private final InternalKeyListener mKeyListener;
|
||||
private boolean mDetectScrollMovement = false;
|
||||
|
||||
private ReactViewBackgroundDrawable mReactBackgroundDrawable;
|
||||
|
||||
private static final KeyListener sKeyListener = QwertyKeyListener.getInstanceForFullKeyboard();
|
||||
|
||||
public ReactEditText(Context context) {
|
||||
|
@ -478,6 +483,52 @@ public class ReactEditText extends EditText {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundColor(int color) {
|
||||
if (color == Color.TRANSPARENT && mReactBackgroundDrawable == null) {
|
||||
// don't do anything, no need to allocate ReactBackgroundDrawable for transparent background
|
||||
} else {
|
||||
getOrCreateReactViewBackground().setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBorderWidth(int position, float width) {
|
||||
getOrCreateReactViewBackground().setBorderWidth(position, width);
|
||||
}
|
||||
|
||||
public void setBorderColor(int position, float color, float alpha) {
|
||||
getOrCreateReactViewBackground().setBorderColor(position, color, alpha);
|
||||
}
|
||||
|
||||
public void setBorderRadius(float borderRadius) {
|
||||
getOrCreateReactViewBackground().setRadius(borderRadius);
|
||||
}
|
||||
|
||||
public void setBorderRadius(float borderRadius, int position) {
|
||||
getOrCreateReactViewBackground().setRadius(borderRadius, position);
|
||||
}
|
||||
|
||||
public void setBorderStyle(@Nullable String style) {
|
||||
getOrCreateReactViewBackground().setBorderStyle(style);
|
||||
}
|
||||
|
||||
private ReactViewBackgroundDrawable getOrCreateReactViewBackground() {
|
||||
if (mReactBackgroundDrawable == null) {
|
||||
mReactBackgroundDrawable = new ReactViewBackgroundDrawable();
|
||||
Drawable backgroundDrawable = getBackground();
|
||||
super.setBackground(null); // required so that drawable callback is cleared before we add the
|
||||
// drawable back as a part of LayerDrawable
|
||||
if (backgroundDrawable == null) {
|
||||
super.setBackground(mReactBackgroundDrawable);
|
||||
} else {
|
||||
LayerDrawable layerDrawable =
|
||||
new LayerDrawable(new Drawable[]{mReactBackgroundDrawable, backgroundDrawable});
|
||||
super.setBackground(layerDrawable);
|
||||
}
|
||||
}
|
||||
return mReactBackgroundDrawable;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class will redirect *TextChanged calls to the listeners only in the case where the text
|
||||
* is changed by the user, and not explicitly set by JS.
|
||||
|
|
|
@ -28,6 +28,8 @@ import android.view.View;
|
|||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.facebook.csslayout.CSSConstants;
|
||||
import com.facebook.csslayout.Spacing;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
|
@ -42,10 +44,12 @@ import com.facebook.react.uimanager.UIManagerModule;
|
|||
import com.facebook.react.uimanager.ViewDefaults;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.annotations.ReactPropGroup;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
|
||||
import com.facebook.react.views.text.DefaultStyleValuesUtil;
|
||||
import com.facebook.react.views.text.ReactTextUpdate;
|
||||
import com.facebook.react.views.text.ReactTextView;
|
||||
import com.facebook.react.views.text.TextInlineImageSpan;
|
||||
|
||||
/**
|
||||
|
@ -55,6 +59,10 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
|||
|
||||
/* package */ static final String REACT_CLASS = "AndroidTextInput";
|
||||
|
||||
private static final int[] SPACING_TYPES = {
|
||||
Spacing.ALL, Spacing.LEFT, Spacing.RIGHT, Spacing.TOP, Spacing.BOTTOM,
|
||||
};
|
||||
|
||||
private static final int FOCUS_TEXT_INPUT = 1;
|
||||
private static final int BLUR_TEXT_INPUT = 2;
|
||||
|
||||
|
@ -497,6 +505,53 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
|||
view.setImeActionLabel(returnKeyLabel, IME_ACTION_ID);
|
||||
}
|
||||
|
||||
@ReactPropGroup(names = {
|
||||
ViewProps.BORDER_RADIUS,
|
||||
ViewProps.BORDER_TOP_LEFT_RADIUS,
|
||||
ViewProps.BORDER_TOP_RIGHT_RADIUS,
|
||||
ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
|
||||
ViewProps.BORDER_BOTTOM_LEFT_RADIUS
|
||||
}, defaultFloat = CSSConstants.UNDEFINED)
|
||||
public void setBorderRadius(ReactEditText view, int index, float borderRadius) {
|
||||
if (!CSSConstants.isUndefined(borderRadius)) {
|
||||
borderRadius = PixelUtil.toPixelFromDIP(borderRadius);
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
view.setBorderRadius(borderRadius);
|
||||
} else {
|
||||
view.setBorderRadius(borderRadius, index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "borderStyle")
|
||||
public void setBorderStyle(ReactEditText view, @Nullable String borderStyle) {
|
||||
view.setBorderStyle(borderStyle);
|
||||
}
|
||||
|
||||
@ReactPropGroup(names = {
|
||||
ViewProps.BORDER_WIDTH,
|
||||
ViewProps.BORDER_LEFT_WIDTH,
|
||||
ViewProps.BORDER_RIGHT_WIDTH,
|
||||
ViewProps.BORDER_TOP_WIDTH,
|
||||
ViewProps.BORDER_BOTTOM_WIDTH,
|
||||
}, defaultFloat = CSSConstants.UNDEFINED)
|
||||
public void setBorderWidth(ReactEditText view, int index, float width) {
|
||||
if (!CSSConstants.isUndefined(width)) {
|
||||
width = PixelUtil.toPixelFromDIP(width);
|
||||
}
|
||||
view.setBorderWidth(SPACING_TYPES[index], width);
|
||||
}
|
||||
|
||||
@ReactPropGroup(names = {
|
||||
"borderColor", "borderLeftColor", "borderRightColor", "borderTopColor", "borderBottomColor"
|
||||
}, customType = "Color")
|
||||
public void setBorderColor(ReactEditText view, int index, Integer color) {
|
||||
float rgbComponent = color == null ? CSSConstants.UNDEFINED : (float) ((int)color & 0x00FFFFFF);
|
||||
float alphaComponent = color == null ? CSSConstants.UNDEFINED : (float) ((int)color >>> 24);
|
||||
view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAfterUpdateTransaction(ReactEditText view) {
|
||||
super.onAfterUpdateTransaction(view);
|
||||
|
|
|
@ -43,7 +43,7 @@ import com.facebook.csslayout.Spacing;
|
|||
* {@code mBorderWidthResult} and similar. When only background color is set we won't allocate any
|
||||
* extra/unnecessary objects.
|
||||
*/
|
||||
/* package */ class ReactViewBackgroundDrawable extends Drawable {
|
||||
public class ReactViewBackgroundDrawable extends Drawable {
|
||||
|
||||
private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
|
||||
private static final int DEFAULT_BORDER_RGB = 0x00FFFFFF & DEFAULT_BORDER_COLOR;
|
||||
|
|
|
@ -38,6 +38,7 @@ import com.facebook.react.uimanager.UIImplementation;
|
|||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.views.view.ReactViewBackgroundDrawable;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -343,7 +344,7 @@ public class ReactTextTest {
|
|||
JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
|
||||
|
||||
Drawable backgroundDrawable = ((TextView) rootView.getChildAt(0)).getBackground();
|
||||
assertThat(((ColorDrawable) backgroundDrawable).getColor()).isEqualTo(Color.BLUE);
|
||||
assertThat(((ReactViewBackgroundDrawable) backgroundDrawable).getColor()).isEqualTo(Color.BLUE);
|
||||
}
|
||||
|
||||
// JELLY_BEAN is needed for TextView#getMaxLines(), which is OK, because in the actual code we
|
||||
|
|
Loading…
Reference in New Issue