From 5aa1fb3ff326a429e33a443576da866f2a63c20c Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Tue, 21 Nov 2017 11:07:36 -0800 Subject: [PATCH] Fix border-rendering in APIs < 18 Summary: `Canvas.clipPath` isn't supported with hardware acceleration in APIs below 18. The rounded border rendering logic for Android relies on this method. Therefore, rounded borders do not render correctly on such devices. **Screenshot of Nexus 5 running API 17 (Before these changes):** https://pxl.cl/9rsf **The fix**: If the API version is less than 18 and we're rendering rounded borders, I disable hardware acceleration. Otherwise, I enable it. I'm going to check to see if this has perf regressions by running a CT-Scan. With this change, rounded borders render correctly on Android devices running versions of Android between Honeycomb to JellyBean MR2. **Screenshot of Nexus 5 running API 17 (After these changes):** https://pxl.cl/9rrk Reviewed By: xiphirx Differential Revision: D6153087 fbshipit-source-id: 16e35be096051ac817c8b8bcdd132ecff3b4b167 --- .../view/ReactViewBackgroundDrawable.java | 32 ++++++++++++++++--- .../react/views/view/ReactViewGroup.java | 15 ++++++++- 2 files changed, 42 insertions(+), 5 deletions(-) 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 b32855b1450625..4990dd4236a893 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 @@ -129,16 +129,29 @@ public ReactViewBackgroundDrawable(Context context) { @Override public void draw(Canvas canvas) { updatePathEffect(); - boolean roundedBorders = mBorderCornerRadii != null || - (!YogaConstants.isUndefined(mBorderRadius) && mBorderRadius > 0); - - if (!roundedBorders) { + if (!hasRoundedBorders()) { drawRectangularBackgroundWithBorders(canvas); } else { drawRoundedBackgroundWithBorders(canvas); } } + public boolean hasRoundedBorders() { + if (!YogaConstants.isUndefined(mBorderRadius) && mBorderRadius > 0) { + return true; + } + + if (mBorderCornerRadii != null) { + for (final float borderRadii : mBorderCornerRadii) { + if (!YogaConstants.isUndefined(borderRadii) && borderRadii > 0) { + return true; + } + } + } + + return false; + } + @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); @@ -620,23 +633,29 @@ private void updatePath() { * border of V will render inside O. * *

Let BorderWidth = (borderTop, borderLeft, borderBottom, borderRight). + * *

Let I (for inner) = O - BorderWidth. * *

Then, remembering that O and I are rectangles and that I is inside O, O - I gives us the * border of V. Therefore, we can use canvas.clipPath to draw V's border. * *

canvas.clipPath(O, Region.OP.INTERSECT); + * *

canvas.clipPath(I, Region.OP.DIFFERENCE); + * *

canvas.drawRect(O, paint); * *

This lets us draw non-rounded single-color borders. * *

To extend this algorithm to rounded single-color borders, we: + * *

1. Curve the corners of O by the (border radii of V) using Path#addRoundRect. + * *

2. Curve the corners of I by (border radii of V - border widths of V) using * Path#addRoundRect. * *

Let O' = curve(O, border radii of V). + * *

Let I' = curve(I, border radii of V - border widths of V) * *

The rationale behind this decision is the (first sentence of the) following section in the @@ -647,7 +666,9 @@ private void updatePath() { * render curved single-color borders: * *

canvas.clipPath(O, Region.OP.INTERSECT); + * *

canvas.clipPath(I, Region.OP.DIFFERENCE); + * *

canvas.drawRect(O, paint); * *

To extend this algorithm to rendering multi-colored rounded borders, we render each side @@ -655,8 +676,11 @@ private void updatePath() { * border radii are 0. Then, the four quadrilaterals would be: * *

Left: (O.left, O.top), (I.left, I.top), (I.left, I.bottom), (O.left, O.bottom) + * *

Top: (O.left, O.top), (I.left, I.top), (I.right, I.top), (O.right, O.top) + * *

Right: (O.right, O.top), (I.right, I.top), (I.right, I.bottom), (O.right, O.bottom) + * *

Bottom: (O.right, O.bottom), (I.right, I.bottom), (I.left, I.bottom), (O.left, O.bottom) * *

Now, lets consider what happens when we render a rounded border (radii != 0). For the sake diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index 8e80168a33b29e..3d7a4299ec0546 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -233,7 +233,20 @@ public void setBorderRadius(float borderRadius) { } public void setBorderRadius(float borderRadius, int position) { - getOrCreateReactViewBackground().setRadius(borderRadius, position); + ReactViewBackgroundDrawable backgroundDrawable = getOrCreateReactViewBackground(); + backgroundDrawable.setRadius(borderRadius, position); + + if (Build.VERSION_CODES.HONEYCOMB < Build.VERSION.SDK_INT + && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + final int UPDATED_LAYER_TYPE = + backgroundDrawable.hasRoundedBorders() + ? View.LAYER_TYPE_SOFTWARE + : View.LAYER_TYPE_HARDWARE; + + if (UPDATED_LAYER_TYPE != getLayerType()) { + setLayerType(UPDATED_LAYER_TYPE, null); + } + } } public void setBorderStyle(@Nullable String style) {