Kureev / react-native-blur

React Native Blur component
MIT License
3.76k stars 556 forks source link

Bur is invalid when first time load on android #199

Open roselind opened 7 years ago

roselind commented 7 years ago

Bur is invalid when first time load on android

When replacing the picture of the mask below sometimes does not update the content on android

react-native-blur@master and react-native-blur@3.0.0-alpha 2 versions have above the problem

eg // my code: `

imageLoaded() { this.setState({viewRef: findNodeHandle(this.refs.backgroundImage) }); // setTimeout(() => { // this.setState({viewRef:findNodeHandle(this.refs.backgroundImage) }); // }, 200); }

renderBlurView() {
  return (
    <View >
      {this.state.viewRef && <BlurView
        viewRef={this.state.viewRef}
        style={styles.blurView}
        blurRadius={6}
        blurType={'dark'}
        blurAmount = {12}
      />}
    </View>
  )

`

`

//render 函数 : <Image source={this.state.customImage} style={[styles.backImage,styles.blurView]} ref={'backgroundImage'} onLoadEnd={this.imageLoaded.bind(this)} /> {this.renderBlurView()}

//...... `
ndbroadbent commented 7 years ago

Hi @roselind, sorry about this, it's a very tricky race condition that I've also been struggling with. I think the best workaround is to just add a small timeout, like you did in your example:

setTimeout(() => {
  this.setState({viewRef:findNodeHandle(this.refs.backgroundImage) });
}, 200);

I need to figure out the right way to do this. I think there might be a bug in the blurring library that we are using, or perhaps the Android view has not finished rendering before we try to blur it. Any help would be appreciated, if you have time to look at the source code of react-native-blur and could help me figure this out.

DomiR commented 7 years ago

tl;dr BlurView basically takes screenshot of target view and blurs it (all libs do this). But when onLoadEnd from Image is called, the image is not rendered yet, just loaded, therefor the screenshot is empty => no blur 😢. Best option right now is to setTimeout and optionally fade in image.

So I've been trying to figure this out as well. Seems like Fresco is loading the image and setting the asynchronously, thus https://github.com/facebook/react-native/blob/3fda6a9a2b380a750b72d369f5fb4f11540ad1ef/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java#L214 is beeing called and will emit onLoadEnd before view is invalidated and redrawn.

To clarify how I understand blurview works. As soon as we set viewRef in setState it will:

  1. Get react-native-handle of target view
  2. In native code render said view to canvas, basically creating a screenshot (optionally downsampled to boost performance)
  3. Add blur to the same canvas via RenderScript utilising android.renderscript.ScriptIntrinsicBlur (all libs I found basically to the same, mostly they differ a bit in memory management, some even have a slower java based fallback for older devices: e.g. https://github.com/Manabu-GT/EtsyBlur, https://github.com/mmin18/RealtimeBlurView, https://github.com/gogopop/BlurKit-Android, https://github.com/Dimezis/BlurView, https://github.com/wasabeef/Blurry)
  4. Render blurred canvas to components view

If we use the onLoadEnd the Image is not rendered yet into the target view, thus only giving back a blurred grey image. If we use setTimeout it will mostly work, but will kind of flash the original image unblurred at first.

Solution I see three options here:

  1. Figure out another way to wait for images to be loaded and drawn to canvas first.
  2. Provide a mechanism for images to wait via setTimeout and do animated fade ins. e.g. <BlurImageView src=.../>
  3. The next react-native version 0.45 or 0.46 will finally add blurRadius to Images, just wait for it to land, but would not be backwards compatible...

Mentions There is a fork that uses Blurry, but it should not make a difference: https://github.com/carlesnunez/react-native-blurry

ScrollView-Support Basically we would need to recreate snapshot and blur on every scrollEvent. For large scroll-components we would need to screenshot only the visible bits. There is already a fork for the underlying lib Android uses, that would kind of work with ScrollView but it is not yet merged and tested, so probably we won't see this any time soon: https://github.com/500px/500px-android-blur/pull/10/files

roselind commented 7 years ago

thanks ,yours answers. I temporarily solved the problem , The effect is still a little slow I found it that BlurView can be forced update by modifying properties , eg blurRadius , downsampleFactor , overlayColor . Changing only one property (viewRef)will not update ,

code:

imageLoaded() {
  InteractionManager.runAfterInteractions(() => {
        setTimeout(() => {
          var blurRadius = this.state.blurRadius ;
          blurRadius = getRandomNum_MinAndMax_noValue(6,12,blurRadius);
          var downsampleFactor = this.state.downsampleFactor;
          downsampleFactor = getRandomNum_MinAndMax_noValue(4,8,downsampleFactor);
          var tmpColor = getRandomNum_MinAndMax(0,80);
          this.setState({
            viewRef: findNodeHandle(this.refs.backgroundImage),
            blurRadius:blurRadius,
            downsampleFactor:downsampleFactor,
            overlayColor:`rgba(${tmpColor},${tmpColor},${tmpColor},0.5)`
          });
        }, 400);
      });
}
ndbroadbent commented 7 years ago

Thanks very much for the writeup @DomiR, you're totally right. I was struggling this myself when I fixed the Android version, but I couldn't come up with a good solution to really fix the race condition.

It would be awesome if there was some way to monitor the referenced view and detect rendering updates. It sounds like something that should be possible in theory, but I don't have any idea how to do it.

ndbroadbent commented 7 years ago

Wow, it seems that whenever I write up a post or a comment, I always find the answer immediately afterwards. I think I just figured it out: http://stackoverflow.com/a/7735122/304706

We just need to add a global layout listener to the referenced view, and update the blur whenever it changes. This fixes the race condition, but it always mean that we now have a real-time blur whenever the view changes.

I'm going to try this out and see if I can get it to work.

harveyhui commented 7 years ago

Excuse me, has this problem been solved?

roselind commented 7 years ago

Not solved, My present method is to force updates 2 times ,Most of them are correct

harveyhui commented 7 years ago

Could you please explain more clearly? Do you mean to refresh the page again? Refresh two times?

roselind commented 7 years ago

I resets state when first time load on android imageLoaded() { InteractionManager.runAfterInteractions(() => { setTimeout(() => { var blurRadius = this.state.blurRadius ; blurRadius = getRandomNum_MinAndMax_noValue(6,12,blurRadius); var downsampleFactor = this.state.downsampleFactor; downsampleFactor = getRandomNum_MinAndMax_noValue(4,8,downsampleFactor); var tmpColor = getRandomNum_MinAndMax(0,80); this.setState({ viewRef: findNodeHandle(this.refs.backgroundImage), blurRadius:blurRadius, downsampleFactor:downsampleFactor, overlayColor:rgba(${tmpColor},${tmpColor},${tmpColor},0.5)` }); }, 400); }); }

`

Kureev commented 7 years ago

Blur on Android requires a reference to the view you want to blur which is not possible to pre-define before initial render (as far as it's controlled by React and there are no hooks we can use to control it).

kumarpatel commented 7 years ago

@ndbroadbent Just wondering if you've made any progress with re. to adding OnGlobalLayoutListener approach.

No pressure :)

roselind commented 7 years ago

I found it , Only once setting does not work ,Even after the page has been loaded ,I listen a event ,Set it again after a few seconds .

mikelambert commented 7 years ago

For those it might help, Image's blurRadius has supposedly been working for both Android and iOS since RN 0.44. See more details here: https://github.com/facebook/react-native/commit/fc09c54324ff7fcec41e4f55edcca3854c9fa76b

kumarpatel commented 7 years ago

@mikelambert Correct me if I'm wrong but blurRadius only applies to Image component.

This library allows us to blur Views in general.

mikelambert commented 7 years ago

Correct on both counts Kumar. My message wasn't meant to imply otherwise.

Many people in this thread just need a solution that works for Image.

fukemy commented 1 year ago

I am used expo blur then never see laggy with android