React-native: Elevation does not animate when it is inside a view which animates its opacity (Android P)

Created on 21 Jan 2019  路  63Comments  路  Source: facebook/react-native

Environment

  React Native Environment Info:
    System:
      OS: macOS 10.14.1
      CPU: x64 Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
      Memory: 40.65 MB / 16.00 GB
      Shell: 5.6.2 - /usr/local/bin/zsh
    Binaries:
      Node: 10.11.0 - ~/.nvm/versions/node/v10.11.0/bin/node
      Yarn: 1.13.0 - ~/.nvm/versions/node/v10.11.0/bin/yarn
      npm: 6.5.0 - ~/.nvm/versions/node/v10.11.0/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 11.4, macOS 10.13, tvOS 11.4, watchOS 4.3
      Android SDK:
        Build Tools: 23.0.1, 26.0.2, 26.0.3, 27.0.2, 27.0.3, 28.0.1, 28.0.2, 28.0.3
        API Levels: 22, 23, 24, 25, 26, 27, 28
    IDEs:
      Android Studio: 3.3 AI-182.5107.16.33.5199772
      Xcode: 9.4.1/9F2000 - /usr/bin/xcodebuild
    npmPackages:
      react: 16.6.3 => 16.6.3
      react-native: 0.59.0 => 0.59.0
    npmGlobalPackages:
      react-native-cli: 2.0.1

Description

What described in the title works in any Android but not on Android P. See the differences:

Android P:

androidp

Below Android P:

notandroidp

Reproducible Demo

https://snack.expo.io/@satya164/elevation-issue---android-p

This can affect libraries like react-navigation when a new screen comes in animating its opacity. See https://github.com/react-navigation/react-navigation/issues/5535.

Can be found here also https://github.com/ferrannp/react-native-elevation-animation-android-pie with react-native 0.59.X.

Bug Help Wanted Author Provided Repro Android Ran Commands

Most helpful comment

My issue:
https://github.com/react-navigation/react-navigation/issues/8676

On android, setting a view's opacity will render artifacts when child views have elevation.

So I've investigated this issue and came to the conclusion, that this is an android intended behavior.

Background:

<View style={{opacity: 0.5}}>
    <View style={{elevation: 5, margin: 10, backgroundColor: 'red'}}>
<View>

To understand this issue we let's dig into react code. Both views will get a BaseViewManager, which holds a reference to a ReactViewGroup.ReactViewGroup subclasses a ViewGroup and not an Android View (because it can have children). When setting a backgroundColor to a ReactViewGroup it creates a ReactViewBackgroundDrawable which subclasses a Drawable and sets it as background. The creation happens in getOrCreateReactViewBackground which composes any already existing background drawable (maybe images use this, in all my test cases the existing drawable was always null) with the new background using LayerDrawable.

Problem:
Now when setting an alpha value (changing opacity) it will do this on the ReactViewGroup directly but not on the background drawable, which is why we have the artifact. As react-native per default set hasOverlappingRendering to false to save memory, this problem is an expected android performance optimization. It seems that some devices do not perform the optimization and I only experienced this when changing the alpha after the first render. See android docs for what this flag does.

In the screenshot below you can see the different parameters (plain android project in this repo, not react-native, although I used the same ReactViewBackgroundDrawable class from react-native which I just extracted and patched to get it running).
There are 6 red 100x100 boxes that get the opacity set on their view or for testing purposes on their background drawable. Within a red box a yellow box is placed with elevation set to 10. In the first row, hasOverlappingRendering is disabled.

screenshot

Repo: https://github.com/DomiR/react-native-opacity-issue
So as you can see on both boxes on the right-hand side setting opacity only on the background is not enough, as all children will still have full opacity. Setting alpha both on the background and on the view itself will result in 0.25 opacity for the background. On the left-hand side is what we really want but we need to hasOverlappingRendering enabled like in the left bottom.

Solution:
I don't know if there is a solution that is good for everyone.
As you can see from the screenshot we need to set hasOverlappingRendering but doing so per default is not performant (facebook blog entry).

Fortunately, we can set this flag using needsOffscreenAlphaCompositing as a view prop.

As @kmagiera mentioned that you should probably enable this only temporarily during animations.
The react-native docs also mention using renderToHardwareTextureAndroid in combination.

PS: What also should work is setting the opacity value for all children as well.

All 63 comments

It looks like you are using an older version of React Native. Please update to the latest release, v0.58 and verify if the issue still exists.

The "鈴狾ld Version" label will be removed automatically once you edit your original post with the results of running react-native info on a project using the latest release.

I've been running into the same issue for awhile now. Seems to be happening as well for 0.58.3.

+1

I am closing this issue because it does not appear to have been verified on the latest release, and there has been no followup in a while.

If you found this thread after encountering the same issue in the latest release, please feel free to create a new issue with up-to-date information by clicking here.

I experience this issue too

I added a repo with react-native init that reproduces the issue https://github.com/ferrannp/react-native-elevation-animation-android-pie.

Hi i'm experiencing this issue as well on Android Pie only (earlier versions are fine), any suggestions on work arounds?

I'm experiencing this issue too. Does anybody found a workaround for this issue?

Is anyone working on this? We are facing the same issue

Also facing this issue, any solution?

damn this is bad, never thought it would be like that , for now I can't use elevation because it leave the shadow for a little while , when the opacity goes to 0

I'm also facing this issue. Does anyone know any workaround? Especially annoying when I'm trying to animate a box with several buttons inside it, the shadows appear before the buttons do.

Buy an iPhone and never get this issue again ;-)

@RichardLindhout It's not personal dude :D , look at it from a developer point of view

@RichardLindhout i wish. unfortunately, Android has a 76% market share, and 10% of Android devices are running Android P. Elevation/shadows are an important part of Material Design, which the majority of native Android apps feature one way or another, so fixing this issue is pretty important.

Here's a sketchy workaround I made, basically it just starts animating the elevation halfway through the opacity animation, and they both finish at the same time. Of course, this workaround only works for specific situations and probably isn't particularly reliable anyway. Also, if you use easing with an animation it might look odd. https://snack.expo.io/H1PtFET7S (sorry for the messy code, I wrote it on my phone). Hope this helps someone

@RichardLindhout i wish. unfortunately, Android has a 76% market share, and 10% of Android devices are running Android P. Elevation/shadows are an important part of Material Design, which the majority of native Android apps feature one way or another, so fixing this issue is pretty important.

I agree, I was just joking and I hope this issue will be resolved soon!
Screenshot_20190811-125637

Even some of the simplest apps need to make use of fading an item with an elevation. This needs an (actual) workaround/fix ASAP.

Even some of the simplest apps need to make use of fading an item with an elevation. This needs an (actual) workaround/fix ASAP.

@reidkersey I worked around this by creating a shadow image and wrapping my view in an ImageBackground. You couldn鈥檛 tell the difference in my case.

Sent with GitHawk

same

"react-native": "0.59.9",
"react": "16.8.3"

Still an issue.

Any update on this?, almost one year has passed.

Please, it needs to be fixed, we can't go to production without elevation but navigating between pages is awful, there are black rectangles everywhere.

Happens as well with Android 10

Hey folks, 2 questions:
1) is there any PR open to solve this that you are aware of?

2) have you tested with the latest 61 RC?

Still facing this issue, hopefully there will be a fix soon. Also happens with the react-navigation animation.

EDIT:
@kelset

Just tested with version "react-native": "0.61.0-rc.3" and still facing this issue on Android P.
Tested with a Samsung galaxy s8 as device.

I have the same problem.
Any ideas where the problem originates from?

We replace opacity to border in android version, seems like this is only solution for now

+1

I am facing the same issue in android 10 also.

Almost the same problem, the core problem might be the same:
https://github.com/facebook/react-native/issues/25093

Try to set opacity: 0.99 in styles, I don't know why but it works for me

+1
image
Issue in Android 10.

Having the same issue here. Latest version React Native 0.61.5.

Seriously guys, is anybody working on this ?
It's a major issue for a while now..
(Though I admit the real problem here is Android's shadows handling, but it impacts every UI developer everyday)

Confirming this is still an issue on 0.62.2. This is really bad/alarming!

Also still an issue on 0.63.0-rc.0

Facing same issue, not good that has not been solved for this long :(

I really want this issue fixed. It looks really weird when we animate a view with elevation.

This isn't just an Android 9 issue either, it also happens on 10.

Facing the same problem here. Just to add to the ones expecting a solution. Can confirm that is also a problem on Android 10.

Same issue here.

This is a horrible experience. Does anybody have a work-around for this?

For those who want a workaround for this. Use Androw (https://github.com/folofse/androw/tree/master/react-native-androw) or something similar to render shadows on Android instead of using elevation.

There are performance drawbacks within lists, but otherwise works pretty well

@iremlopsum this looks amazing! We probably need this or something like it in the core.

I have the same problem. still no solution yet?

Same issue here

can not reproduce on API 28 with react native latest master branch. Please could you explain me the issue? Thanks.

I can also confirm this is indeed an issue. We're doing hacky things like having overlay views that fade out instead of fading in the view we're interested in and even this unfortunately isn't a catch all workaround.

If this isn't looked at, does anyone have any other workaround?

@Return-1 I can write you a pull request if you explain the issue. I can not reproduce it so I can not fix it.

@Return-1 I can write you a pull request if you explain the issue. I can not reproduce it so I can not fix it.

You literally can see the issue in your example ?

@fabriziobertoglio1987 can you test if you can reproduce on 0.62, 0.63 and master? Then we might found out what fixed it.

@Return-1 I can write you a pull request if you explain the issue. I can not reproduce it so I can not fix it.

You literally can see the issue in your example ?

TRUUUUUEEE

@radko93 issue is still there in latest versions I'm afraid. its also one that affects major packages like react navigation

having to tell clients that we should avoid using elevation and shadow properties on the UI because react native does not support it is a little embarrassing if I'm being honest : /

what i have not yet managed to determine is which devices it happens for, unfortunately it seems to be a big chunk of them

@fabriziobertoglio1987 The child needs to have elevation.

<Animated.View style={{ opacity: animatedOpacityValue }}>
  <View style={{ width: 100, height: 100, backgroundColor: 'pink', elevation 5 }} />
</Animated.View>

This is the easiest way to reproduce

happening on 0.62.2

@Return-1 you don't have to tell clients that you can not do it. You can receive my paid assistance to fix this. My rate is 10 USD per hour. I am sorry that you are experiencing this.

Thanks a lot
I wish you a good day
Fabrizio Bertoglio

happening on 0.63.1

image

I experienced it a few times in different react-native version, also in react-native 63.0
What works for me is using react-native-shadow in such cases which creates fake SVG shadow 馃槑
Warning: it doesn't look the same as a native shadow, but in my opinion pretty good. Maybe use an original one for iOS and react-native-shadow for Android

For example

          <BoxShadow
            setting={{
              border: 2,
              color: '#000',
              height: 400,
              opacity: 0.25,
              radius: 10,
              width: Dimensions.get('window').width - 40,
              x: 0,
              y: 3,
            }}
          >
            <View
              style={{
                flex: 1,
                paddingHorizontal: 40,
                paddingVertical: 30,
                backgroundColor: 'white',
                borderRadius: 10,
              }}
            >
                  // your content
            </View>
          </BoxShadow>

This is how it looks eventually on Android:
image

@CyxouD Thank you for the workaround, i will keep it in mind for when shadows are a necessity. It's such a pity, Neumorphism employs shadows a lot and i'm facing such a scenario but i will keep it in mind. The main issue is with react-navigation to be honest but what can you do : /

@CyxouD I believe it can be edited in the setting prop so it looks more natural maybe even more than the elevation.

My issue:
https://github.com/react-navigation/react-navigation/issues/8676

On android, setting a view's opacity will render artifacts when child views have elevation.

So I've investigated this issue and came to the conclusion, that this is an android intended behavior.

Background:

<View style={{opacity: 0.5}}>
    <View style={{elevation: 5, margin: 10, backgroundColor: 'red'}}>
<View>

To understand this issue we let's dig into react code. Both views will get a BaseViewManager, which holds a reference to a ReactViewGroup.ReactViewGroup subclasses a ViewGroup and not an Android View (because it can have children). When setting a backgroundColor to a ReactViewGroup it creates a ReactViewBackgroundDrawable which subclasses a Drawable and sets it as background. The creation happens in getOrCreateReactViewBackground which composes any already existing background drawable (maybe images use this, in all my test cases the existing drawable was always null) with the new background using LayerDrawable.

Problem:
Now when setting an alpha value (changing opacity) it will do this on the ReactViewGroup directly but not on the background drawable, which is why we have the artifact. As react-native per default set hasOverlappingRendering to false to save memory, this problem is an expected android performance optimization. It seems that some devices do not perform the optimization and I only experienced this when changing the alpha after the first render. See android docs for what this flag does.

In the screenshot below you can see the different parameters (plain android project in this repo, not react-native, although I used the same ReactViewBackgroundDrawable class from react-native which I just extracted and patched to get it running).
There are 6 red 100x100 boxes that get the opacity set on their view or for testing purposes on their background drawable. Within a red box a yellow box is placed with elevation set to 10. In the first row, hasOverlappingRendering is disabled.

screenshot

Repo: https://github.com/DomiR/react-native-opacity-issue
So as you can see on both boxes on the right-hand side setting opacity only on the background is not enough, as all children will still have full opacity. Setting alpha both on the background and on the view itself will result in 0.25 opacity for the background. On the left-hand side is what we really want but we need to hasOverlappingRendering enabled like in the left bottom.

Solution:
I don't know if there is a solution that is good for everyone.
As you can see from the screenshot we need to set hasOverlappingRendering but doing so per default is not performant (facebook blog entry).

Fortunately, we can set this flag using needsOffscreenAlphaCompositing as a view prop.

As @kmagiera mentioned that you should probably enable this only temporarily during animations.
The react-native docs also mention using renderToHardwareTextureAndroid in combination.

PS: What also should work is setting the opacity value for all children as well.

what about option "renderToHardwareTextureAndroid" for View?

Thanks @DomiR , can confirm that setting needsOffscreenAlphaCompositing on the animated view seems to solve the issue.

Before:
2020-10-17 12 19 34

After:
2020-10-17 12 19 07

PR example.

Was this page helpful?
0 / 5 - 0 ratings