react-native: Rotate transform not working well on View with borderRadius
When rotating a component, its children also rotate as expected, except for the View
s with borderRadius > 0
, which their rounded border does not rotate.
Environment
Environment: OS: macOS High Sierra 10.13.3 Node: 8.9.0 Yarn: 1.5.1 npm: 5.7.1 Watchman: 4.9.0 Xcode: Xcode 9.2 Build version 9C40b Android Studio: 3.0 AI-171.4443003
Packages: (wanted => installed) react: ^16.3.0-alpha.1 => 16.3.0-alpha.1 react-native: ^0.54.0 => 0.54.0
Expected Behavior
Borders should rotate like everything else.
Actual Behavior
<div> </div>^ The one from the right has
overflow: hidden
on the main photo container, so things got worse. We actually can notice it’s not just the border, but the actual view mask / clip bounds that’s not rotating.
Steps to Reproduce
[ANDROID] https://snack.expo.io/@brunolemos/react-native---border-radius-rotate-bug
Full Code
import React, { Component } from 'react';
import { Image, Text, View, StyleSheet } from 'react-native';
import { Constants } from 'expo';
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<View style={styles.block}>
<View style={styles.textContainer}>
<Text>SQUARE</Text>
<Image
source={require('./assets/logo_facebook_square.png')}
style={styles.image}
/>
</View>
<View style={[styles.textContainer, { borderRadius: 10 }]}>
<Text>ROUND</Text>
<Image
source={require('./assets/logo_facebook_square.png')}
style={[styles.image, { borderRadius: 10 }]}
/>
</View>
</View>
<View style={[styles.block, { transform: [{ rotate: '-10deg' }] }]}>
<View style={styles.textContainer}>
<Text>SQUARE</Text>
<Image
source={require('./assets/logo_facebook_square.png')}
style={styles.image}
/>
</View>
<View style={[styles.textContainer, { borderRadius: 10 }]}>
<Text>ROUND</Text>
<Image
source={require('./assets/logo_facebook_square.png')}
style={[styles.image, { borderRadius: 10 }]}
/>
</View>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
block: {
justifyContent: 'center',
alignItems: 'center',
marginBottom: 60,
width: 200,
height: 200,
backgroundColor: 'white',
},
textContainer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
margin: 10,
padding: 10,
borderWidth: 2,
borderColor: 'red',
},
image: {
width: 40,
height: 40,
borderWidth: 4,
borderColor: 'green',
},
});
Investigating + Related
After more investigation this is what I found:
- Everything works fine on react-native 0.49.5;
rotate + borderRadius > 0 + overflow:hidden
bug introduced on 0.50.0; (https://github.com/facebook/react-native/commit/30044fd531c22c4c5e8f1ede206fa7c2c3fd3aa8?)rotate + borderRadius > 0 + borderWidth > 0
bug introduced on 0.51.0; (https://github.com/facebook/react-native/commit/4994d6a389b4e41ba25e802edab5d3fdc9e8a4f1?)- Bug still present on 0.54.0.
Possibly related:
- Commit https://github.com/facebook/react-native/commit/30044fd531c22c4c5e8f1ede206fa7c2c3fd3aa8 (v0.50; cc @AaaChiuuu)
- Commit https://github.com/facebook/react-native/commit/4994d6a389b4e41ba25e802edab5d3fdc9e8a4f1 (0.51; cc @rsnara)
- Issue #18208: Border not scaling on Android 7.0
- Issue #17400: Non zero borderRadius brakes transform on Android
- Issue #17224: Border not animating with scale in 0.51.0
- Issue #17074: Overflow does not work and is inconsistent with iOS, #6802
About this issue
- Original URL
- State: open
- Created 6 years ago
- Reactions: 27
- Comments: 41 (15 by maintainers)
Commits related to this issue
- fix overflow hidden Reviewed By: shergin Differential Revision: D5917111 fbshipit-source-id: e3d97f26b6aada199f700ec6659ace0d7dffd4c5 — committed to facebook/react-native by aaronechiu 7 years ago
- Implement partial rounded borders Reviewed By: achen1 Differential Revision: D5982241 fbshipit-source-id: 2f694daca7e1b16b5ff65f07c7d15dd558a4b7e8 — committed to facebook/react-native by RSNara 7 years ago
- React Native sync for revisions 241c446...b5c6dd2 Summary: This sync includes the following changes: - **[b5c6dd2de](https://github.com/facebook/react/commit/b5c6dd2de )**: Don't use Spread in DevToo... — committed to facebook/react-native by lunaruan 4 years ago
Im not sure whether it is an Android or React Native issue, but here is what is working using RN 0.59.8: Android 21 - Fine Android 22 - Fine Android 23 - Fine Android 24 - Not working Android 25 - Not working Android 26 - Not working Android 27 - Not working Android 28 - Not working Android Q - Fine
Still seeing this in 0.57 on Android. iOS works fine as originally noted.
Apart from child
View
s with borders not rotating, the rotatedView
’s own border is misbehaving too.Before rotation:
After a 45deg rotation:
This is on 55.3
The issue still persists in React Native 0.73. The solution is to set the
renderToHardwareTextureAndroid
prop totrue
.This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.
From my understanding, there were two problems here.
overflow: hidden
for views with non-rounded borders. I’m not completely sure about the history of borders andoverflow: hidden
in React Native, but I just tried v0.49.5 andoverflow: hidden
doesn’t work at all. Child views render on top of their parent’s borders, but are clipped to the outer edge of their parent regardless of theoverflow
property. This clipping algorithm worked perfectly fine for rotated views. However, when it was extended to account for rounded borders, it broke due to what I think was an AOSP bug. Whatever the probem was, it was resolved in the latest release of Android.So really,
overflow: hidden
is the only thing that’s presently broken. Specifically, I think it’s broken whenever the inner border edge has a curved corner, which happens whenborderRadius > borderWidth
. This problem also only occurs on Android API < 28.From where I stand, I see two options:
borderWidth
>borderRadius
or to not use borders. This is a bit of a bummer, I know.overflow:hidden
on android devices running API < 28. Maybe this involves adding a nested view so the contents of the border are clipped to the outer edge of the inner view. Or maybe, there’s—there probably is—a better solution. I’m really not sure. (What I do know is that we have to be careful about view hierarchy depth on older Android devices, because they can easily run into stack overflow errors. So, maybe the multi-layer solution isn’t the best idea?)Previously, I was leaning more toward the first option. That’s why I suggested that we should close this issue. But now that I think about it a bit more, I’m not sure what the best approach is. Any thoughts, @shergin?
Edit 1: I’ll do some more thorough testing and see if things actually are working. Edit 2: Hmm… so it looks like API 28 works perfectly for all cases I’ve tried, but with borders whose width is specified using
border(Top|Left|Bottom|Right)Width
, devices running API 27 and below don’t render rotated borders correctly. Sigh…The issue is caused from the canvas cropping on a
LAYER_TYPE_NONE
on Android Sdk 24-28. Adding the below code toReactViewGroup.java
fixes the issue. I am preparing a pull request.before
canvas.clipPath(mPath)
https://github.com/facebook/react-native/blob/4f5a092bf68a0cd825328ce4a1e6bb41a8fad2e3/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java#L862-L863
Result in the example from @RSNara comment before and after the fix.
The issue was caused from
clipping
the view that had propertyoverflow: 'hidden'
andLAYER_TYPE_NONE
, in specific drawing an area (mPath
) to crop the canvas from the before mentioned view using a method calledaddRoundRect
, which would not take in consideration the view rotation previously applied withview.setRotation()
https://github.com/facebook/react-native/blob/4f5a092bf68a0cd825328ce4a1e6bb41a8fad2e3/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java#L849-L860
The original expo snack posted from @brunolemos does not reproduce anymore on react-native master, but the issue in the above example from @RSNara still reproduces.
The issue and solution was tested on Physical Devices and Emulators running Android Sdk 24-29, my pull request will explicitly set the
layerType
forView
s with propertyoverflow: 'hidden'
on devices running Android 24-28 and avoid this issue.I am preparing the pull request. Thanks a lot for reading this message 😃
Still seen at 0.61.5 and Android SDK 25, but not seen at Android SDK 29, seems using
overflow: 'visible'
for the parent view could solve the problem.Same shit different day😭
I also still have this issue on some devices. Working fine on Android 6.0.1 but not on 8.1.0. Using RN 0.59.4
Tagged as a regression so the bot does not close this due to staleness. Thanks @rsnara for the writeup.
0.56.0-rc
should have the fixes you mentioned.Hey @brunolemos!
Your problem reproduces on React Native ~0.55.2. As evidence, here’s a screenshot:
But I also ran your code against React Native master and saw that the rounded border rendering has been fixed. Screenshot:
However, it still looks like clipping is broken (with rounded borders).
Code: https://snack.expo.io/Hyc6HMPxX
Edit: I did some digging around and narrowed it down to the
canvas.clipPath
call insideReactViewGroup.drawdispatchOverflowDraw
. Clipping works perfectly fine if I zero all the radii and use canvas.clipPath. However, if I leave the radii non-zero, it fails. An interesting consequence is that whenborderWidth
>borderRadius
, clipping works correctly (because the clipped rectangle won’t be rounded at that point).I’ll look into this a bit further.
+1
I can reproduce this on the latest stable release.
Thanks for posting this! It looks like your issue may refer to an older version of React Native. Can you reproduce the issue on the latest stable release?
Thank you for your contributions.
How to Contribute • What to Expect from Maintainers
This happened in react-native 0.61.0. When set rotate and border Radius and overflow hidden to the parent, the child works fine, but when I set background color to children, parent border radius does not work.
I’m trying to prepare a pull request to fix this, would be happy to receive your advice. I think this was caused from: https://github.com/facebook/react-native/blob/b81c8b51fc6fe3c2dece72e3fe500e175613c5d4/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java#L809-L823
Changing the code fixed the issue diplayed in the comment from @RSNara. Issue would reproduce as from your screenshot above. The result is displayed below.
The
radii
array of floats[X,Y]
for each corner of the square are the radius distance used to draw the corner. Thepath
is a square with rounded corners used to clip the canvas available for drawing.In the below example the clipped
Canvas
is displayed in red while therectangle
displayed in theCanvas
is displayed in blue. If we set[X,Y]
to[0,0]
, we fix this as we do not crop corners (but it is not the solution).I believe the clipping is caused by using the wrong
[X,Y]
values. The[X,Y]
values should be the same used to draw the parent corners.Issues that I found till now: FIRST-ISSUE: The borderRadius is converted from DPI to Pixels while the original value is in Pixels: https://github.com/facebook/react-native/blob/673cbb3110855c45beb7e340b61e7daf927d9ade/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java#L107-L114
SECOND-ISSUE: I don’t see any logic to
transform
themPath
and give it the-10dg
rotation before clipping. I applied the transformation, but seems to not fix the problem. Removing the transform from theView
fixes the problem, so I believe this is connected to transform and clip. https://github.com/facebook/react-native/blob/b81c8b51fc6fe3c2dece72e3fe500e175613c5d4/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java#L822THIRD-ISSUE: The issue is caused by using
addRoundRect
or drawing the path withlineTo
andquadTo
. The problem is not experienced when usingaddRect
or removing the rounded corners.Related Issue https://github.com/facebook/react-native/issues/3198
You could try fixing this by using prop
rendertohardwaretextureandroid
with value oftrue
https://reactnative.dev/docs/view#rendertohardwaretextureandroid
My pr would just enable hardware accelleration on that view after applying the border radius, but you could solve this issue by enabling hardware accelleration on the view in Javascript for Android API N till P
https://github.com/facebook/react-native/blob/e6658a203db20e7950648de55cb80f14961470e1/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java#L267-L270
the prop allows you to call
setLayerType(HARDWARE)
on each ReactView
.https://github.com/facebook/react-native/blob/1465c8f3874cdee8c325ab4a4916fda0b3e43bdb/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java#L120-L122
https://github.com/facebook/react-native/issues/29312#issuecomment-892405146
We are also running into this issue. Tried removing individual border radius widths and sizes, however that breaks specifying different border radius colours for each side on the affect platforms.
Changing this:
To this:
Breaks the separate border colours, they are all black on android. Works on iOS.
@johnyoonh can you open a new issue? This is marked as fix, so we’d need to get more details from you in order to determine if this is the same issue or something else.
I was able to get
overflow: 'hidden'
to work by also adding the stylezIndex: 1
to the same element. It seems #3198 was closed.