From 28ba749ba0152df3517386ae802fcba5606fb83a Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Thu, 8 Sep 2016 02:55:42 -0700 Subject: [PATCH] Android: Add support for having borders on & components Summary: Currently, `` and `` components on Android do not support borders. This change adds support for the borderRadius, borderColor, and borderWidth props on the `` and `` 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 --- .../java/com/facebook/react/views/text/BUCK | 1 + .../react/views/text/ReactTextView.java | 51 +++++++++++++++++ .../views/text/ReactTextViewManager.java | 55 +++++++++++++++++++ .../react/views/textinput/ReactEditText.java | 51 +++++++++++++++++ .../textinput/ReactTextInputManager.java | 55 +++++++++++++++++++ .../view/ReactViewBackgroundDrawable.java | 2 +- .../react/views/text/ReactTextTest.java | 3 +- 7 files changed, 216 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK index 53bab845baaec8..5e335761534268 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK @@ -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', diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index cd7ea1acf1bb1d..f137a1d2ffd00a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -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 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 void onFinishTemporaryDetach() { } } + @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 void updateView() { @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; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java index f222e393688258..673a98cea13f0c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java @@ -16,13 +16,17 @@ 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>> 24); + view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent); + } + @Override public void updateExtraData(ReactTextView view, Object extraData) { ReactTextUpdate update = (ReactTextUpdate) extraData; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index ffa6e911aa7300..9529a3e327912b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -14,9 +14,11 @@ 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.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 void onFinishTemporaryDetach() { } } + @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. diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index 89e2f10149d457..b46bf81e7fc739 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -28,6 +28,8 @@ 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.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>> 24); + view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent); + } + @Override protected void onAfterUpdateTransaction(ReactEditText view) { super.onAfterUpdateTransaction(view); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java index 9b995cd5f403ab..ecd1c19bacd59e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java @@ -43,7 +43,7 @@ * {@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; diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java index 7d8f15829f5476..b91bc5eab2a521 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java @@ -38,6 +38,7 @@ 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 void testBackgroundColorStyleApplied() { 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