React-native: ImageBackground cannot use Animated component

Created on 23 Nov 2017  路  9Comments  路  Source: facebook/react-native

Is this a bug report?

Yes

Have you read the Contributing Guidelines?

Yes

Environment

Environment:
OS: macOS Sierra 10.12.6
Node: 8.7.0
Yarn: 1.2.1
npm: 5.4.2
Watchman: 4.9.0
Xcode: Xcode 8.3.3 Build version 8E3004b
Android Studio: 2.3 AI-162.4069837

Packages: (wanted => installed)
react: 16.0.0 => 16.0.0
react-native: 0.50.2 => 0.50.2

Steps to Reproduce

(Write your steps here:)

Expected Behavior

I expect it's still can be used after nested Image is not supported anymore.

Actual Behavior

I got red screen of death regarding this error because added animated to ImageBackground.

screen shot 2017-11-23 at 11 23 40 am

Reproducible Demo

https://github.com/MrHazimAli/CurrencyConverter

Locked

Most helpful comment

@MrHazimAli Here is how I did it:

I wrapped the ImageBackground component in a View

// Logo.js

import React, { Component } from 'react';
import { View, Text, ImageBackground, Keyboard, Animated, Platform } from 'react-native';

import styles from './styles';

const ANIMATION_DURATION = 250;

class Logo extends Component {
  constructor(props) {
    super(props);

    this.containerImageWidth = new Animated.Value(styles.$largeContainerSize);
    this.imageWidth = new Animated.Value(styles.$largeImageSize);
  }

  componentDidMount() {
    let showListener = 'keyboardWillShow';
    let hideListener = 'keyboardWillHide';
    if (Platform.OS === 'android') {
      showListener = 'keyboardDidShow';
      hideListener = 'keyboardDidHide';
    }
    this.keyboardShowListener = Keyboard.addListener(showListener, this.keyboardShow);
    this.keyboardHideListener = Keyboard.addListener(hideListener, this.keyboardHide);
  }

  componentWillUnmount() {
    this.keyboardShowListener.remove();
    this.keyboardHideListener.remove();
  }

  keyboardShow = () => {
    Animated.parallel([
      Animated.timing(this.containerImageWidth, {
        toValue: styles.$smallContainerSize,
        duration: ANIMATION_DURATION,
      }),
      Animated.timing(this.imageWidth, {
        toValue: styles.$smallImageSize,
        duration: ANIMATION_DURATION,
      }),
    ]).start();
  };

  keyboardHide = () => {
    Animated.parallel([
      Animated.timing(this.containerImageWidth, {
        toValue: styles.$largeContainerSize,
        duration: ANIMATION_DURATION,
      }),
      Animated.timing(this.imageWidth, {
        toValue: styles.$largeImageSize,
        duration: ANIMATION_DURATION,
      }),
    ]).start();
  };

  render() {
    const containerImageStyle = [
      styles.containerImage,
      { width: this.containerImageWidth, height: this.containerImageWidth },
    ];

    const imageStyle = [styles.logo, { width: this.imageWidth }];

    return (
      <View style={styles.container}>
        <Animated.View style={containerImageStyle}> // <- here
          <ImageBackground
            source={require('./images/background.png')}
            style={styles.backgroundImage}
            resizeMode="contain"
          >
            <Animated.Image
              source={require('./images/logo.png')}
              style={imageStyle}
              resizeMode="contain"
            />
          </ImageBackground>
        </Animated.View>
        <Text style={styles.text}>Currency Converter</Text>
      </View>
    );
  }
}

export default Logo;
// styles.js
import EStyleSheet from 'react-native-extended-stylesheet';
import { Dimensions } from 'react-native';

const imageWidth = Dimensions.get('window').width / 2;

const styles = EStyleSheet.create({
  $largeContainerSize: imageWidth,
  $largeImageSize: imageWidth / 2,
  $smallContainerSize: imageWidth / 2,
  $smallImageSize: imageWidth / 4,

  container: {
    alignItems: 'center',
  },
  containerImage: {
    alignItems: 'center',
    justifyContent: 'center',
    width: '$largeContainerSize',
    height: '$largeContainerSize',
  },
  backgroundImage: {          // <- new
    alignItems: 'center',
    justifyContent: 'center',
    alignSelf: 'stretch',
    flex: 1,
  },
  logo: {
    width: '$largeImageSize',
  },
  text: {
    fontWeight: '600',
    fontSize: 28,
    letterSpacing: -0.5,
    marginTop: 15,
    color: '$white',
  },
});

export default styles;

All 9 comments

Dear @MrHazimAli , That's not a bug.
And it not how Animated is supposed to be used.

Guess you were confused Animated with the package react-native-animatable.
https://github.com/oblador/react-native-animatable

oh.. I see, because Im just following this tutorial and it stated that Animated is imported from react native and in the previous version I can just use nested children and use animated using both of the image component..

https://learn.handlebarlabs.com/courses/175915/lectures/2643157

screen shot 2017-11-24 at 12 27 36 pm
screen shot 2017-11-24 at 12 28 01 pm

@MrHazimAli Animated.ImageBackground is something certainly doesn't exist.
So though U were confused Animated with any other 3rd party lib.

<Animated.ImageBackground><Animated.Image /></Animated.ImageBackground>

And please read the Error Message. It states very clear that the Component U trying to render is not a component but an undefined

I see, alright.. noted..
just thought that maybe ImageBackground component will also support the animated like image before.. sorry for taking your time and thought it was a bug, will explore more :-)

thanks! 馃憤

@yshing Hi.

So how can we animate the ImageBackground component ?

@MrHazimAli Here is how I did it:

I wrapped the ImageBackground component in a View

// Logo.js

import React, { Component } from 'react';
import { View, Text, ImageBackground, Keyboard, Animated, Platform } from 'react-native';

import styles from './styles';

const ANIMATION_DURATION = 250;

class Logo extends Component {
  constructor(props) {
    super(props);

    this.containerImageWidth = new Animated.Value(styles.$largeContainerSize);
    this.imageWidth = new Animated.Value(styles.$largeImageSize);
  }

  componentDidMount() {
    let showListener = 'keyboardWillShow';
    let hideListener = 'keyboardWillHide';
    if (Platform.OS === 'android') {
      showListener = 'keyboardDidShow';
      hideListener = 'keyboardDidHide';
    }
    this.keyboardShowListener = Keyboard.addListener(showListener, this.keyboardShow);
    this.keyboardHideListener = Keyboard.addListener(hideListener, this.keyboardHide);
  }

  componentWillUnmount() {
    this.keyboardShowListener.remove();
    this.keyboardHideListener.remove();
  }

  keyboardShow = () => {
    Animated.parallel([
      Animated.timing(this.containerImageWidth, {
        toValue: styles.$smallContainerSize,
        duration: ANIMATION_DURATION,
      }),
      Animated.timing(this.imageWidth, {
        toValue: styles.$smallImageSize,
        duration: ANIMATION_DURATION,
      }),
    ]).start();
  };

  keyboardHide = () => {
    Animated.parallel([
      Animated.timing(this.containerImageWidth, {
        toValue: styles.$largeContainerSize,
        duration: ANIMATION_DURATION,
      }),
      Animated.timing(this.imageWidth, {
        toValue: styles.$largeImageSize,
        duration: ANIMATION_DURATION,
      }),
    ]).start();
  };

  render() {
    const containerImageStyle = [
      styles.containerImage,
      { width: this.containerImageWidth, height: this.containerImageWidth },
    ];

    const imageStyle = [styles.logo, { width: this.imageWidth }];

    return (
      <View style={styles.container}>
        <Animated.View style={containerImageStyle}> // <- here
          <ImageBackground
            source={require('./images/background.png')}
            style={styles.backgroundImage}
            resizeMode="contain"
          >
            <Animated.Image
              source={require('./images/logo.png')}
              style={imageStyle}
              resizeMode="contain"
            />
          </ImageBackground>
        </Animated.View>
        <Text style={styles.text}>Currency Converter</Text>
      </View>
    );
  }
}

export default Logo;
// styles.js
import EStyleSheet from 'react-native-extended-stylesheet';
import { Dimensions } from 'react-native';

const imageWidth = Dimensions.get('window').width / 2;

const styles = EStyleSheet.create({
  $largeContainerSize: imageWidth,
  $largeImageSize: imageWidth / 2,
  $smallContainerSize: imageWidth / 2,
  $smallImageSize: imageWidth / 4,

  container: {
    alignItems: 'center',
  },
  containerImage: {
    alignItems: 'center',
    justifyContent: 'center',
    width: '$largeContainerSize',
    height: '$largeContainerSize',
  },
  backgroundImage: {          // <- new
    alignItems: 'center',
    justifyContent: 'center',
    alignSelf: 'stretch',
    flex: 1,
  },
  logo: {
    width: '$largeImageSize',
  },
  text: {
    fontWeight: '600',
    fontSize: 28,
    letterSpacing: -0.5,
    marginTop: 15,
    color: '$white',
  },
});

export default styles;

on Logo.js (Do not use the ImageBackground)

<View style={styles.container} resizeMode="contain" >
        <Image style={styles.containerImage} source={require('./images/background.png')} />
        <Image style={styles.image} source={require('./images/logo.png')} resizeMode="contain" />
        <Text style={styles.text}>Currency Converter</Text>
</View>

on styles.js (only change the image property)
image: { position: 'absolute', margin: 37, width: '$largeImageSize', },

I'm also taking this Currency Converter course and this Animated component does not work in ImageBackground component.

you can use var AnimatedImage = Animated.createAnimatedComponent(ImageBackground)
instead of Animated.Image

https://stackoverflow.com/questions/48240857/animation-image-component-cannot-contain-child-even-when-child-component-positi

Thank you @leoabacade works for me

Was this page helpful?
0 / 5 - 0 ratings