Permalink
Browse files

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
  • Loading branch information...
RSNara authored and facebook-github-bot committed Nov 21, 2017
1 parent 6082856 commit 5aa1fb3ff326a429e33a443576da866f2a63c20c
@@ -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.
*
* <p>Let BorderWidth = (borderTop, borderLeft, borderBottom, borderRight).
*
* <p>Let I (for inner) = O - BorderWidth.
*
* <p>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.
*
* <p>canvas.clipPath(O, Region.OP.INTERSECT);
*
* <p>canvas.clipPath(I, Region.OP.DIFFERENCE);
*
* <p>canvas.drawRect(O, paint);
*
* <p>This lets us draw non-rounded single-color borders.
*
* <p>To extend this algorithm to rounded single-color borders, we:
*
* <p>1. Curve the corners of O by the (border radii of V) using Path#addRoundRect.
*
* <p>2. Curve the corners of I by (border radii of V - border widths of V) using
* Path#addRoundRect.
*
* <p>Let O' = curve(O, border radii of V).
*
* <p>Let I' = curve(I, border radii of V - border widths of V)
*
* <p>The rationale behind this decision is the (first sentence of the) following section in the
@@ -647,16 +666,21 @@ private void updatePath() {
* render curved single-color borders:
*
* <p>canvas.clipPath(O, Region.OP.INTERSECT);
*
* <p>canvas.clipPath(I, Region.OP.DIFFERENCE);
*
* <p>canvas.drawRect(O, paint);
*
* <p>To extend this algorithm to rendering multi-colored rounded borders, we render each side
* of the border as its own quadrilateral. Suppose that we were handling the case where all the
* border radii are 0. Then, the four quadrilaterals would be:
*
* <p>Left: (O.left, O.top), (I.left, I.top), (I.left, I.bottom), (O.left, O.bottom)
*
* <p>Top: (O.left, O.top), (I.left, I.top), (I.right, I.top), (O.right, O.top)
*
* <p>Right: (O.right, O.top), (I.right, I.top), (I.right, I.bottom), (O.right, O.bottom)
*
* <p>Bottom: (O.right, O.bottom), (I.right, I.bottom), (I.left, I.bottom), (O.left, O.bottom)
*
* <p>Now, lets consider what happens when we render a rounded border (radii != 0). For the sake
@@ -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) {

0 comments on commit 5aa1fb3

Please sign in to comment.