Skip to content

Commit 4994d6a

Browse files
RSNarafacebook-github-bot
authored andcommitted
Implement partial rounded borders
Reviewed By: achen1 Differential Revision: D5982241 fbshipit-source-id: 2f694daca7e1b16b5ff65f07c7d15dd558a4b7e8
1 parent de313f6 commit 4994d6a

File tree

2 files changed

+137
-44
lines changed

2 files changed

+137
-44
lines changed

ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java

Lines changed: 110 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.graphics.PathEffect;
2020
import android.graphics.Rect;
2121
import android.graphics.RectF;
22+
import android.graphics.Region;
2223
import android.graphics.drawable.Drawable;
2324
import android.os.Build;
2425
import com.facebook.react.common.annotations.VisibleForTesting;
@@ -30,15 +31,15 @@
3031
import javax.annotation.Nullable;
3132

3233
/**
33-
* A subclass of {@link Drawable} used for background of {@link ReactViewGroup}. It supports
34-
* drawing background color and borders (including rounded borders) by providing a react friendly
35-
* API (setter for each of those properties).
34+
* A subclass of {@link Drawable} used for background of {@link ReactViewGroup}. It supports drawing
35+
* background color and borders (including rounded borders) by providing a react friendly API
36+
* (setter for each of those properties).
3637
*
37-
* The implementation tries to allocate as few objects as possible depending on which properties are
38-
* set. E.g. for views with rounded background/borders we allocate {@code mPathForBorderRadius} and
39-
* {@code mTempRectForBorderRadius}. In case when view have a rectangular borders we allocate
40-
* {@code mBorderWidthResult} and similar. When only background color is set we won't allocate any
41-
* extra/unnecessary objects.
38+
* <p>The implementation tries to allocate as few objects as possible depending on which properties
39+
* are set. E.g. for views with rounded background/borders we allocate {@code
40+
* mInnerClipPathForBorderRadius} and {@code mInnerClipTempRectForBorderRadius}. In case when view
41+
* have a rectangular borders we allocate {@code mBorderWidthResult} and similar. When only
42+
* background color is set we won't allocate any extra/unnecessary objects.
4243
*/
4344
public class ReactViewBackgroundDrawable extends Drawable {
4445

@@ -83,10 +84,12 @@ private static enum BorderStyle {
8384

8485
/* Used for rounded border and rounded background */
8586
private @Nullable PathEffect mPathEffectForBorderStyle;
86-
private @Nullable Path mPathForBorderRadius;
87+
private @Nullable Path mInnerClipPathForBorderRadius;
88+
private @Nullable Path mOuterClipPathForBorderRadius;
8789
private @Nullable Path mPathForBorderRadiusOutline;
8890
private @Nullable Path mPathForBorder;
89-
private @Nullable RectF mTempRectForBorderRadius;
91+
private @Nullable RectF mInnerClipTempRectForBorderRadius;
92+
private @Nullable RectF mOuterClipTempRectForBorderRadius;
9093
private @Nullable RectF mTempRectForBorderRadiusOutline;
9194
private boolean mNeedUpdatePathForBorderRadius = false;
9295
private float mBorderRadius = YogaConstants.UNDEFINED;
@@ -169,8 +172,13 @@ public void setBorderWidth(int position, float width) {
169172
}
170173
if (!FloatUtil.floatsEqual(mBorderWidth.getRaw(position), width)) {
171174
mBorderWidth.set(position, width);
172-
if (position == Spacing.ALL) {
173-
mNeedUpdatePathForBorderRadius = true;
175+
switch (position) {
176+
case Spacing.ALL:
177+
case Spacing.LEFT:
178+
case Spacing.BOTTOM:
179+
case Spacing.RIGHT:
180+
case Spacing.TOP:
181+
mNeedUpdatePathForBorderRadius = true;
174182
}
175183
invalidateSelf();
176184
}
@@ -266,44 +274,87 @@ public int getColor() {
266274

267275
private void drawRoundedBackgroundWithBorders(Canvas canvas) {
268276
updatePath();
277+
canvas.save();
278+
269279
int useColor = ColorUtil.multiplyColorAlpha(mColor, mAlpha);
270280
if (Color.alpha(useColor) != 0) { // color is not transparent
271281
mPaint.setColor(useColor);
272282
mPaint.setStyle(Paint.Style.FILL);
273-
canvas.drawPath(mPathForBorderRadius, mPaint);
283+
canvas.drawPath(mInnerClipPathForBorderRadius, mPaint);
274284
}
275-
// maybe draw borders?
276-
float fullBorderWidth = getFullBorderWidth();
277-
if (fullBorderWidth > 0) {
285+
286+
final float borderWidth = getBorderWidthOrDefaultTo(0, Spacing.ALL);
287+
final float borderTopWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.TOP);
288+
final float borderBottomWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.BOTTOM);
289+
final float borderLeftWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.LEFT);
290+
final float borderRightWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.RIGHT);
291+
292+
if (borderTopWidth > 0
293+
|| borderBottomWidth > 0
294+
|| borderLeftWidth > 0
295+
|| borderRightWidth > 0) {
278296
int borderColor = getFullBorderColor();
279297
mPaint.setColor(ColorUtil.multiplyColorAlpha(borderColor, mAlpha));
280-
mPaint.setStyle(Paint.Style.STROKE);
281-
mPaint.setStrokeWidth(fullBorderWidth);
282-
canvas.drawPath(mPathForBorderRadius, mPaint);
298+
mPaint.setStyle(Paint.Style.FILL);
299+
300+
// Draw border
301+
canvas.clipPath(mOuterClipPathForBorderRadius, Region.Op.INTERSECT);
302+
canvas.clipPath(mInnerClipPathForBorderRadius, Region.Op.DIFFERENCE);
303+
canvas.drawRect(getBounds(), mPaint);
283304
}
305+
306+
canvas.restore();
284307
}
285308

286309
private void updatePath() {
287310
if (!mNeedUpdatePathForBorderRadius) {
288311
return;
289312
}
313+
290314
mNeedUpdatePathForBorderRadius = false;
291-
if (mPathForBorderRadius == null) {
292-
mPathForBorderRadius = new Path();
293-
mTempRectForBorderRadius = new RectF();
315+
316+
if (mInnerClipPathForBorderRadius == null) {
317+
mInnerClipPathForBorderRadius = new Path();
318+
}
319+
320+
if (mOuterClipPathForBorderRadius == null) {
321+
mOuterClipPathForBorderRadius = new Path();
322+
}
323+
324+
if (mPathForBorderRadiusOutline == null) {
294325
mPathForBorderRadiusOutline = new Path();
326+
}
327+
328+
if (mInnerClipTempRectForBorderRadius == null) {
329+
mInnerClipTempRectForBorderRadius = new RectF();
330+
}
331+
332+
if (mOuterClipTempRectForBorderRadius == null) {
333+
mOuterClipTempRectForBorderRadius = new RectF();
334+
}
335+
336+
if (mTempRectForBorderRadiusOutline == null) {
295337
mTempRectForBorderRadiusOutline = new RectF();
296338
}
297339

298-
mPathForBorderRadius.reset();
340+
mInnerClipPathForBorderRadius.reset();
341+
mOuterClipPathForBorderRadius.reset();
299342
mPathForBorderRadiusOutline.reset();
300343

301-
mTempRectForBorderRadius.set(getBounds());
344+
mInnerClipTempRectForBorderRadius.set(getBounds());
345+
mOuterClipTempRectForBorderRadius.set(getBounds());
302346
mTempRectForBorderRadiusOutline.set(getBounds());
303-
float fullBorderWidth = getFullBorderWidth();
304-
if (fullBorderWidth > 0) {
305-
mTempRectForBorderRadius.inset(fullBorderWidth * 0.5f, fullBorderWidth * 0.5f);
306-
}
347+
348+
final float borderWidth = getBorderWidthOrDefaultTo(0, Spacing.ALL);
349+
final float borderTopWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.TOP);
350+
final float borderBottomWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.BOTTOM);
351+
final float borderLeftWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.LEFT);
352+
final float borderRightWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.RIGHT);
353+
354+
mInnerClipTempRectForBorderRadius.top += borderTopWidth;
355+
mInnerClipTempRectForBorderRadius.bottom -= borderBottomWidth;
356+
mInnerClipTempRectForBorderRadius.left += borderLeftWidth;
357+
mInnerClipTempRectForBorderRadius.right -= borderRightWidth;
307358

308359
final float borderRadius = getFullBorderRadius();
309360
final float topLeftRadius =
@@ -315,8 +366,22 @@ private void updatePath() {
315366
final float bottomRightRadius =
316367
getBorderRadiusOrDefaultTo(borderRadius, BorderRadiusLocation.BOTTOM_RIGHT);
317368

318-
mPathForBorderRadius.addRoundRect(
319-
mTempRectForBorderRadius,
369+
mInnerClipPathForBorderRadius.addRoundRect(
370+
mInnerClipTempRectForBorderRadius,
371+
new float[] {
372+
Math.max(topLeftRadius - borderLeftWidth, 0),
373+
Math.max(topLeftRadius - borderTopWidth, 0),
374+
Math.max(topRightRadius - borderRightWidth, 0),
375+
Math.max(topRightRadius - borderTopWidth, 0),
376+
Math.max(bottomRightRadius - borderRightWidth, 0),
377+
Math.max(bottomRightRadius - borderBottomWidth, 0),
378+
Math.max(bottomLeftRadius - borderLeftWidth, 0),
379+
Math.max(bottomLeftRadius - borderBottomWidth, 0),
380+
},
381+
Path.Direction.CW);
382+
383+
mOuterClipPathForBorderRadius.addRoundRect(
384+
mOuterClipTempRectForBorderRadius,
320385
new float[] {
321386
topLeftRadius,
322387
topLeftRadius,
@@ -329,6 +394,7 @@ private void updatePath() {
329394
},
330395
Path.Direction.CW);
331396

397+
332398
float extraRadiusForOutline = 0;
333399

334400
if (mBorderWidth != null) {
@@ -350,6 +416,20 @@ private void updatePath() {
350416
Path.Direction.CW);
351417
}
352418

419+
public float getBorderWidthOrDefaultTo(final float defaultValue, final int spacingType) {
420+
if (mBorderWidth == null) {
421+
return defaultValue;
422+
}
423+
424+
final float width = mBorderWidth.getRaw(spacingType);
425+
426+
if (YogaConstants.isUndefined(width)) {
427+
return defaultValue;
428+
}
429+
430+
return width;
431+
}
432+
353433
/**
354434
* Set type of border
355435
*/

ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
3434
import com.facebook.react.uimanager.ReactPointerEventsView;
3535
import com.facebook.react.uimanager.ReactZIndexedViewGroup;
36+
import com.facebook.react.uimanager.Spacing;
3637
import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper;
3738
import javax.annotation.Nullable;
3839

@@ -624,13 +625,25 @@ protected void dispatchDraw(Canvas canvas) {
624625
float top = 0f;
625626
float right = getWidth();
626627
float bottom = getHeight();
627-
final float borderWidth = mReactBackgroundDrawable.getFullBorderWidth();
628628

629-
if (borderWidth != 0f) {
630-
left += borderWidth;
631-
top += borderWidth;
632-
right -= borderWidth;
633-
bottom -= borderWidth;
629+
final float borderWidth = mReactBackgroundDrawable.getFullBorderWidth();
630+
final float borderTopWidth =
631+
mReactBackgroundDrawable.getBorderWidthOrDefaultTo(borderWidth, Spacing.TOP);
632+
final float borderBottomWidth =
633+
mReactBackgroundDrawable.getBorderWidthOrDefaultTo(borderWidth, Spacing.BOTTOM);
634+
final float borderLeftWidth =
635+
mReactBackgroundDrawable.getBorderWidthOrDefaultTo(borderWidth, Spacing.LEFT);
636+
final float borderRightWidth =
637+
mReactBackgroundDrawable.getBorderWidthOrDefaultTo(borderWidth, Spacing.RIGHT);
638+
639+
if (borderTopWidth > 0
640+
|| borderLeftWidth > 0
641+
|| borderBottomWidth > 0
642+
|| borderRightWidth > 0) {
643+
left += borderLeftWidth;
644+
top += borderTopWidth;
645+
right -= borderRightWidth;
646+
bottom -= borderBottomWidth;
634647
}
635648

636649
final float borderRadius = mReactBackgroundDrawable.getFullBorderRadius();
@@ -659,14 +672,14 @@ protected void dispatchDraw(Canvas canvas) {
659672
mPath.addRoundRect(
660673
new RectF(left, top, right, bottom),
661674
new float[] {
662-
Math.max(topLeftBorderRadius - borderWidth, 0),
663-
Math.max(topLeftBorderRadius - borderWidth, 0),
664-
Math.max(topRightBorderRadius - borderWidth, 0),
665-
Math.max(topRightBorderRadius - borderWidth, 0),
666-
Math.max(bottomRightBorderRadius - borderWidth, 0),
667-
Math.max(bottomRightBorderRadius - borderWidth, 0),
668-
Math.max(bottomLeftBorderRadius - borderWidth, 0),
669-
Math.max(bottomLeftBorderRadius - borderWidth, 0),
675+
Math.max(topLeftBorderRadius - borderLeftWidth, 0),
676+
Math.max(topLeftBorderRadius - borderTopWidth, 0),
677+
Math.max(topRightBorderRadius - borderRightWidth, 0),
678+
Math.max(topRightBorderRadius - borderTopWidth, 0),
679+
Math.max(bottomRightBorderRadius - borderRightWidth, 0),
680+
Math.max(bottomRightBorderRadius - borderBottomWidth, 0),
681+
Math.max(bottomLeftBorderRadius - borderLeftWidth, 0),
682+
Math.max(bottomLeftBorderRadius - borderBottomWidth, 0),
670683
},
671684
Path.Direction.CW);
672685
canvas.clipPath(mPath);

0 commit comments

Comments
 (0)