Permalink
Browse files

Implement partial rounded borders

Reviewed By: achen1

Differential Revision: D5982241

fbshipit-source-id: 2f694daca7e1b16b5ff65f07c7d15dd558a4b7e8
  • Loading branch information...
RSNara authored and facebook-github-bot committed Oct 19, 2017
1 parent de313f6 commit 4994d6a389b4e41ba25e802edab5d3fdc9e8a4f1
@@ -19,6 +19,7 @@
import android.graphics.PathEffect;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Build;
import com.facebook.react.common.annotations.VisibleForTesting;
@@ -30,15 +31,15 @@
import javax.annotation.Nullable;
/**
* A subclass of {@link Drawable} used for background of {@link ReactViewGroup}. It supports
* drawing background color and borders (including rounded borders) by providing a react friendly
* API (setter for each of those properties).
* A subclass of {@link Drawable} used for background of {@link ReactViewGroup}. It supports drawing
* background color and borders (including rounded borders) by providing a react friendly API
* (setter for each of those properties).
*
* The implementation tries to allocate as few objects as possible depending on which properties are
* set. E.g. for views with rounded background/borders we allocate {@code mPathForBorderRadius} and
* {@code mTempRectForBorderRadius}. In case when view have a rectangular borders we allocate
* {@code mBorderWidthResult} and similar. When only background color is set we won't allocate any
* extra/unnecessary objects.
* <p>The implementation tries to allocate as few objects as possible depending on which properties
* are set. E.g. for views with rounded background/borders we allocate {@code
* mInnerClipPathForBorderRadius} and {@code mInnerClipTempRectForBorderRadius}. In case when view
* have a rectangular borders we allocate {@code mBorderWidthResult} and similar. When only
* background color is set we won't allocate any extra/unnecessary objects.
*/
public class ReactViewBackgroundDrawable extends Drawable {
@@ -83,10 +84,12 @@
/* Used for rounded border and rounded background */
private @Nullable PathEffect mPathEffectForBorderStyle;
private @Nullable Path mPathForBorderRadius;
private @Nullable Path mInnerClipPathForBorderRadius;
private @Nullable Path mOuterClipPathForBorderRadius;
private @Nullable Path mPathForBorderRadiusOutline;
private @Nullable Path mPathForBorder;
private @Nullable RectF mTempRectForBorderRadius;
private @Nullable RectF mInnerClipTempRectForBorderRadius;
private @Nullable RectF mOuterClipTempRectForBorderRadius;
private @Nullable RectF mTempRectForBorderRadiusOutline;
private boolean mNeedUpdatePathForBorderRadius = false;
private float mBorderRadius = YogaConstants.UNDEFINED;
@@ -169,8 +172,13 @@ public void setBorderWidth(int position, float width) {
}
if (!FloatUtil.floatsEqual(mBorderWidth.getRaw(position), width)) {
mBorderWidth.set(position, width);
if (position == Spacing.ALL) {
mNeedUpdatePathForBorderRadius = true;
switch (position) {
case Spacing.ALL:
case Spacing.LEFT:
case Spacing.BOTTOM:
case Spacing.RIGHT:
case Spacing.TOP:
mNeedUpdatePathForBorderRadius = true;
}
invalidateSelf();
}
@@ -266,44 +274,87 @@ public int getColor() {
private void drawRoundedBackgroundWithBorders(Canvas canvas) {
updatePath();
canvas.save();
int useColor = ColorUtil.multiplyColorAlpha(mColor, mAlpha);
if (Color.alpha(useColor) != 0) { // color is not transparent
mPaint.setColor(useColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawPath(mPathForBorderRadius, mPaint);
canvas.drawPath(mInnerClipPathForBorderRadius, mPaint);
}
// maybe draw borders?
float fullBorderWidth = getFullBorderWidth();
if (fullBorderWidth > 0) {
final float borderWidth = getBorderWidthOrDefaultTo(0, Spacing.ALL);
final float borderTopWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.TOP);
final float borderBottomWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.BOTTOM);
final float borderLeftWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.LEFT);
final float borderRightWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.RIGHT);
if (borderTopWidth > 0
|| borderBottomWidth > 0
|| borderLeftWidth > 0
|| borderRightWidth > 0) {
int borderColor = getFullBorderColor();
mPaint.setColor(ColorUtil.multiplyColorAlpha(borderColor, mAlpha));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(fullBorderWidth);
canvas.drawPath(mPathForBorderRadius, mPaint);
mPaint.setStyle(Paint.Style.FILL);
// Draw border
canvas.clipPath(mOuterClipPathForBorderRadius, Region.Op.INTERSECT);
canvas.clipPath(mInnerClipPathForBorderRadius, Region.Op.DIFFERENCE);
canvas.drawRect(getBounds(), mPaint);
}
canvas.restore();
}
private void updatePath() {
if (!mNeedUpdatePathForBorderRadius) {
return;
}
mNeedUpdatePathForBorderRadius = false;
if (mPathForBorderRadius == null) {
mPathForBorderRadius = new Path();
mTempRectForBorderRadius = new RectF();
if (mInnerClipPathForBorderRadius == null) {
mInnerClipPathForBorderRadius = new Path();
}
if (mOuterClipPathForBorderRadius == null) {
mOuterClipPathForBorderRadius = new Path();
}
if (mPathForBorderRadiusOutline == null) {
mPathForBorderRadiusOutline = new Path();
}
if (mInnerClipTempRectForBorderRadius == null) {
mInnerClipTempRectForBorderRadius = new RectF();
}
if (mOuterClipTempRectForBorderRadius == null) {
mOuterClipTempRectForBorderRadius = new RectF();
}
if (mTempRectForBorderRadiusOutline == null) {
mTempRectForBorderRadiusOutline = new RectF();
}
mPathForBorderRadius.reset();
mInnerClipPathForBorderRadius.reset();
mOuterClipPathForBorderRadius.reset();
mPathForBorderRadiusOutline.reset();
mTempRectForBorderRadius.set(getBounds());
mInnerClipTempRectForBorderRadius.set(getBounds());
mOuterClipTempRectForBorderRadius.set(getBounds());
mTempRectForBorderRadiusOutline.set(getBounds());
float fullBorderWidth = getFullBorderWidth();
if (fullBorderWidth > 0) {
mTempRectForBorderRadius.inset(fullBorderWidth * 0.5f, fullBorderWidth * 0.5f);
}
final float borderWidth = getBorderWidthOrDefaultTo(0, Spacing.ALL);
final float borderTopWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.TOP);
final float borderBottomWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.BOTTOM);
final float borderLeftWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.LEFT);
final float borderRightWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.RIGHT);
mInnerClipTempRectForBorderRadius.top += borderTopWidth;
mInnerClipTempRectForBorderRadius.bottom -= borderBottomWidth;
mInnerClipTempRectForBorderRadius.left += borderLeftWidth;
mInnerClipTempRectForBorderRadius.right -= borderRightWidth;
final float borderRadius = getFullBorderRadius();
final float topLeftRadius =
@@ -315,8 +366,22 @@ private void updatePath() {
final float bottomRightRadius =
getBorderRadiusOrDefaultTo(borderRadius, BorderRadiusLocation.BOTTOM_RIGHT);
mPathForBorderRadius.addRoundRect(
mTempRectForBorderRadius,
mInnerClipPathForBorderRadius.addRoundRect(
mInnerClipTempRectForBorderRadius,
new float[] {
Math.max(topLeftRadius - borderLeftWidth, 0),
Math.max(topLeftRadius - borderTopWidth, 0),
Math.max(topRightRadius - borderRightWidth, 0),
Math.max(topRightRadius - borderTopWidth, 0),
Math.max(bottomRightRadius - borderRightWidth, 0),
Math.max(bottomRightRadius - borderBottomWidth, 0),
Math.max(bottomLeftRadius - borderLeftWidth, 0),
Math.max(bottomLeftRadius - borderBottomWidth, 0),
},
Path.Direction.CW);
mOuterClipPathForBorderRadius.addRoundRect(
mOuterClipTempRectForBorderRadius,
new float[] {
topLeftRadius,
topLeftRadius,
@@ -329,6 +394,7 @@ private void updatePath() {
},
Path.Direction.CW);
float extraRadiusForOutline = 0;
if (mBorderWidth != null) {
@@ -350,6 +416,20 @@ private void updatePath() {
Path.Direction.CW);
}
public float getBorderWidthOrDefaultTo(final float defaultValue, final int spacingType) {
if (mBorderWidth == null) {
return defaultValue;
}
final float width = mBorderWidth.getRaw(spacingType);
if (YogaConstants.isUndefined(width)) {
return defaultValue;
}
return width;
}
/**
* Set type of border
*/
@@ -33,6 +33,7 @@
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
import com.facebook.react.uimanager.ReactPointerEventsView;
import com.facebook.react.uimanager.ReactZIndexedViewGroup;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper;
import javax.annotation.Nullable;
@@ -624,13 +625,25 @@ protected void dispatchDraw(Canvas canvas) {
float top = 0f;
float right = getWidth();
float bottom = getHeight();
final float borderWidth = mReactBackgroundDrawable.getFullBorderWidth();
if (borderWidth != 0f) {
left += borderWidth;
top += borderWidth;
right -= borderWidth;
bottom -= borderWidth;
final float borderWidth = mReactBackgroundDrawable.getFullBorderWidth();
final float borderTopWidth =
mReactBackgroundDrawable.getBorderWidthOrDefaultTo(borderWidth, Spacing.TOP);
final float borderBottomWidth =
mReactBackgroundDrawable.getBorderWidthOrDefaultTo(borderWidth, Spacing.BOTTOM);
final float borderLeftWidth =
mReactBackgroundDrawable.getBorderWidthOrDefaultTo(borderWidth, Spacing.LEFT);
final float borderRightWidth =
mReactBackgroundDrawable.getBorderWidthOrDefaultTo(borderWidth, Spacing.RIGHT);
if (borderTopWidth > 0
|| borderLeftWidth > 0
|| borderBottomWidth > 0
|| borderRightWidth > 0) {
left += borderLeftWidth;
top += borderTopWidth;
right -= borderRightWidth;
bottom -= borderBottomWidth;
}
final float borderRadius = mReactBackgroundDrawable.getFullBorderRadius();
@@ -659,14 +672,14 @@ protected void dispatchDraw(Canvas canvas) {
mPath.addRoundRect(
new RectF(left, top, right, bottom),
new float[] {
Math.max(topLeftBorderRadius - borderWidth, 0),
Math.max(topLeftBorderRadius - borderWidth, 0),
Math.max(topRightBorderRadius - borderWidth, 0),
Math.max(topRightBorderRadius - borderWidth, 0),
Math.max(bottomRightBorderRadius - borderWidth, 0),
Math.max(bottomRightBorderRadius - borderWidth, 0),
Math.max(bottomLeftBorderRadius - borderWidth, 0),
Math.max(bottomLeftBorderRadius - borderWidth, 0),
Math.max(topLeftBorderRadius - borderLeftWidth, 0),
Math.max(topLeftBorderRadius - borderTopWidth, 0),
Math.max(topRightBorderRadius - borderRightWidth, 0),
Math.max(topRightBorderRadius - borderTopWidth, 0),
Math.max(bottomRightBorderRadius - borderRightWidth, 0),
Math.max(bottomRightBorderRadius - borderBottomWidth, 0),
Math.max(bottomLeftBorderRadius - borderLeftWidth, 0),
Math.max(bottomLeftBorderRadius - borderBottomWidth, 0),
},
Path.Direction.CW);
canvas.clipPath(mPath);

10 comments on commit 4994d6a

@uribro

This comment has been minimized.

Show comment
Hide comment
@uribro

uribro Jan 8, 2018

This CL breaks the borderRadius inside animated view, after this CL borderRadius inside animating view isn't translating together with the view causing the border to stay in the same place while the rest of the view translating. and it looks like the border is being detached.

This is the issue:
#17224

uribro replied Jan 8, 2018

This CL breaks the borderRadius inside animated view, after this CL borderRadius inside animating view isn't translating together with the view causing the border to stay in the same place while the rest of the view translating. and it looks like the border is being detached.

This is the issue:
#17224

@brunolemos

This comment has been minimized.

Show comment
Hide comment
@brunolemos

brunolemos Mar 8, 2018

Contributor

@RSNara I noticed the same problem: #18266

Contributor

brunolemos replied Mar 8, 2018

@RSNara I noticed the same problem: #18266

@allengleyzer

This comment has been minimized.

Show comment
Hide comment
@allengleyzer

allengleyzer Mar 10, 2018

Contributor

Dashed and dotted borderStyles also broke as a result of this commit: #17251, #18285

Contributor

allengleyzer replied Mar 10, 2018

Dashed and dotted borderStyles also broke as a result of this commit: #17251, #18285

@abarisic86

This comment has been minimized.

Show comment
Hide comment
@abarisic86

abarisic86 Mar 13, 2018

More issues related to this: #17267 & #18000. Can we revert those changes? @achen1

abarisic86 replied Mar 13, 2018

More issues related to this: #17267 & #18000. Can we revert those changes? @achen1

@hramos

This comment has been minimized.

Show comment
Hide comment
@hramos

hramos Mar 16, 2018

Contributor

Reverting 5 months after the commit landed is unlikely to happen - this will need to be fixed forward.

Contributor

hramos replied Mar 16, 2018

Reverting 5 months after the commit landed is unlikely to happen - this will need to be fixed forward.

@mannol

This comment has been minimized.

Show comment
Hide comment
@mannol

mannol Mar 25, 2018

I'll just say that reverting this commit (and other commits related to the border work) did indeed fix it for me.

mannol replied Mar 25, 2018

I'll just say that reverting this commit (and other commits related to the border work) did indeed fix it for me.

@brunolemos

This comment has been minimized.

Show comment
Hide comment
@brunolemos

brunolemos Mar 25, 2018

Contributor

@mannol which others? It would be nice to know the exact changes that caused the issues so we can work on a fix

Contributor

brunolemos replied Mar 25, 2018

@mannol which others? It would be nice to know the exact changes that caused the issues so we can work on a fix

@mannol

This comment has been minimized.

Show comment
Hide comment
@mannol

mannol Mar 26, 2018

@brunolemos I had to revert following commits:

5aa1fb3 7170543 efa4d3c 00c9c1a 875f273 38c2c26 1a7abcf 0f467a2 7ed7593 f788831
4994d6a de313f6

And also, I had to add a new method in ReactViewBackgroundDrawable:

 ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java 
index 496007c8b..414fa26d0 100644
@@ -111,6 +111,11 @@ public class ReactViewBackgroundDrawable extends Drawable {
     }
   }
 
+  public boolean hasRoundedBorders() {
+    return mBorderCornerRadii != null ||
+        (!YogaConstants.isUndefined(mBorderRadius) && mBorderRadius > 0);
+  }
+
   @Override
   protected void onBoundsChange(Rect bounds) {
     super.onBoundsChange(bounds);

Really hope this gets fixed soon; the change is too big for someone to just hop into it without any previous knowledge on the topic.

mannol replied Mar 26, 2018

@brunolemos I had to revert following commits:

5aa1fb3 7170543 efa4d3c 00c9c1a 875f273 38c2c26 1a7abcf 0f467a2 7ed7593 f788831
4994d6a de313f6

And also, I had to add a new method in ReactViewBackgroundDrawable:

 ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java 
index 496007c8b..414fa26d0 100644
@@ -111,6 +111,11 @@ public class ReactViewBackgroundDrawable extends Drawable {
     }
   }
 
+  public boolean hasRoundedBorders() {
+    return mBorderCornerRadii != null ||
+        (!YogaConstants.isUndefined(mBorderRadius) && mBorderRadius > 0);
+  }
+
   @Override
   protected void onBoundsChange(Rect bounds) {
     super.onBoundsChange(bounds);

Really hope this gets fixed soon; the change is too big for someone to just hop into it without any previous knowledge on the topic.

@alpamys-qanybet

This comment has been minimized.

Show comment
Hide comment
@alpamys-qanybet

alpamys-qanybet May 29, 2018

@mannol, can you please just provide your working java classes.

alpamys-qanybet replied May 29, 2018

@mannol, can you please just provide your working java classes.

@alpamys-qanybet

This comment has been minimized.

Show comment
Hide comment
@alpamys-qanybet

alpamys-qanybet May 29, 2018

Hey guys, I just copied the ReactViewBackgroundDrawable.java, ReactViewBackgroundManager.java, ReactViewGroup.java from RN0.50.1 and IT WORKS.

alpamys-qanybet replied May 29, 2018

Hey guys, I just copied the ReactViewBackgroundDrawable.java, ReactViewBackgroundManager.java, ReactViewGroup.java from RN0.50.1 and IT WORKS.

Please sign in to comment.