react-native-blur icon indicating copy to clipboard operation
react-native-blur copied to clipboard

Bur is invalid when first time load on android

Open roselind opened this issue 7 years ago • 16 comments

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 [email protected] 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()} <View style={styles.contentContainer}> <TouchableOpacity onPress={this.headerOnClicked.bind(this)}> <Image style={styles.circleImage} source={this.state.customImage} onError = {this.imageHeadOnloadEndError.bind(this)} > </Image> //......

`

roselind avatar May 11 '17 10:05 roselind

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.

ndbroadbent avatar May 13 '17 09:05 ndbroadbent

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

DomiR avatar May 16 '17 15:05 DomiR

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);
      });
}

roselind avatar May 17 '17 01:05 roselind

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 avatar May 17 '17 07:05 ndbroadbent

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.

ndbroadbent avatar May 17 '17 07:05 ndbroadbent

Excuse me, has this problem been solved?

harveyhui avatar Jun 09 '17 03:06 harveyhui

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

roselind avatar Jun 09 '17 05:06 roselind

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

harveyhui avatar Jun 09 '17 05:06 harveyhui

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); }); }

`

roselind avatar Jun 14 '17 10:06 roselind

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).

Kureev avatar Jun 14 '17 11:06 Kureev

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

No pressure :)

kumarpatel avatar Jun 14 '17 20:06 kumarpatel

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 .

roselind avatar Jun 15 '17 02:06 roselind

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

mikelambert avatar Jun 23 '17 09:06 mikelambert

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

This library allows us to blur Views in general.

kumarpatel avatar Jun 27 '17 14:06 kumarpatel

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.

mikelambert avatar Jun 27 '17 17:06 mikelambert

I am used expo blur then never see laggy with android

fukemy avatar Mar 03 '23 10:03 fukemy