Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not working when start & end nodes are on the same screen (Android\Expo) #9

Closed
anfen opened this issue Dec 3, 2019 · 10 comments
Closed

Comments

@anfen
Copy link

anfen commented Dec 3, 2019

I used the code example in the "Basic usage" section of this repo, but no animation occurred.

I found no problem with react-navigation-shared-element, so have presumed Android & Expo is working... should it therefore work in the same screen?

@IjzerenHein
Copy link
Owner

Well in general it should work. Can you share the code that you implemented?

So what needs to happen is that both the start- and end-nodes need to be mounted. And then you should render the Transition component. The transition component will then measure and clone the start- and end-nodes and hide them both. And it will then draw a clone on the screen, and you can control its position by setting the position prop (e.g. through an Animated.Value).

@anfen
Copy link
Author

anfen commented Dec 4, 2019

I've extracted a simplified version of my code below. The animation is started by a button press, and so everything should be mounted when the button is pressed. I also tried the below code with animating <View />'s rather than <Text />'s, but still no animation. I checked that the <SharedElementTransition /> is rendered over the screen, by giving it a backgroundColor, and it does.

The dependencies:
"expo": "35.0.0",
"react": "16.8.6",
"react-dom": "16.8.6",
"react-native": "0.59.10",
"react-native-screens": "2.0.0-alpha.7",
"react-native-shared-element": "^0.5.2",
"react-navigation-shared-element": "0.5.0", <== This works when testing screen-to-screen <SharedElements />

The code snippets:

const [animatedValue, setAnimatedValue] = useState(new Animated.Value(0))

let startAncestor;
let startNode;
let endAncestor;
let endNode;

Animated.timing(animatedValue, {
    toValue: 1,
    duration: 300
}).start()

<Animated.View style={[styles.container, animatedContainerStyle]}>
    <View style={styles.innerContainer}>            
        <View ref={ref => startAncestor = nodeFromRef(ref)}>
            <SharedElement onNode={node => startNode = node}>
                <Text>HERE</Text> 
            </SharedElement>           
        </View>

        <View ref={ref => endAncestor = nodeFromRef(ref)}>            
            <SharedElement onNode={node => endNode = node}>
                <Text>HERE</Text>
            </SharedElement>
        </View>
    </View>

    <View style={[StyleSheet.absoluteFill]}>
        <SharedElementTransition
            start={{
                node: startNode,
                ancestor: startAncestor
            }}
            end={{
                node: endNode,
                ancestor: endAncestor
            }}
            position={animatedValue}
            animation='move'
            resize='auto'
            align='auto'
            debug={true}
            />
    </View>
</Animated.View>

@IjzerenHein
Copy link
Owner

Looking at the code, it seems that the node- & ancestor refs are stored in a local variable of your functional component. This will however not trigger a re-render of your component and that state will not propagate in the SharedElementTransition. Try storing startAncestor, startNode, endAncestor, endNode in the state as well.

@anfen
Copy link
Author

anfen commented Dec 9, 2019

I've tried your suggestion, but still no animation. If I use your setState() suggestion it causes maximum callstack errors. I created a simplified class component, based solely of your "Basic Usage" example and your response above. Could you tell me where I'm going wrong please?

import React from 'react'
import {View, Animated, StyleSheet, Text} from 'react-native'
import {
    SharedElement,
    SharedElementTransition,
    nodeFromRef,
    SharedElementNode
  } from 'react-native-shared-element';

type Props = {    
}

type State = {    
}

export class SharedElementTest extends React.Component<Props, State> {
    animatedValue: Animated.Value;
    
    constructor(props) {
        super(props)
        this.state = {            
        }

        this.animatedValue = new Animated.Value(0)
    }

    componentDidMount() {
        console.log('componentDidMount')
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log('componentDidUpdate', prevProps)
    }

    onRevealPress = () => {
        console.log('onRevealPress')

        Animated.timing(this.animatedValue, {
            toValue: 1,
            duration: 300
        }).start()
    }

    render() {
        return (
            <View style={styles.container}>                
                <View style={styles.innerContainer}>            
                    <View style={styles.questionIconButton} ref={ref => this.startAncestor = nodeFromRef(ref)}>
                        <SharedElement onNode={node => this.startNode = node}>
                            <Text onPress={this.onRevealPress} style={styles.questionMarkText}>HERE</Text> 
                        </SharedElement>                        
                    </View>
    
                    <View style={styles.thumbButtonContainer} ref={ref => this.endAncestor = nodeFromRef(ref)}>            
                        <SharedElement onNode={node => this.endNode = node}>
                            <Text style={styles.didYouRememberText}>HERE</Text>
                        </SharedElement>
                    </View>
                </View>
    
                <View style={StyleSheet.absoluteFill}>
                    <SharedElementTransition
                        start={{
                            node: this.startNode,
                            ancestor: this.startAncestor
                        }}
                        end={{
                            node: this.endNode,
                            ancestor: this.endAncestor
                        }}
                        position={this.animatedValue}
                        animation='move'
                        resize='auto'
                        align='auto'
                        debug={false}                        
                        />
                </View>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        width: 300,
        height: 70,
        backgroundColor: 'blue',
        borderRadius: 50,   
        overflow: 'hidden'     
    },
    innerContainer: {
        flex: 1,
        flexDirection: 'row',
        justifyContent: 'space-evenly',
        alignItems: 'center'        
    },
    button: {       
        backgroundColor: 'green'
    },
    thumbButtonContainer: {

    },
    thumbsButton: {

    },
    icon: {
        color: 'white',
        
    },
    questionIcon: {
        fontSize: 50
    },
    questionIconButton: {
        backgroundColor: 'black',
        width: 80,
        height: '100%',
        justifyContent: 'center'
    },
    questionMarkText: {
        fontSize: 50,
        color: 'white'
    },
    didYouRememberText: {
        color: 'white'
    }
})

@IjzerenHein
Copy link
Owner

Hi, if you get a "maximum callstack size reached" error then you have a problem with your state-flow. This is not related to the shared-element library.

I should probably rename "Basic Usage" to "General Concept", as you probably can't implement it in a single Component (nor do I understand why you want to do this).
If you're looking for a reference implementation, then please have a look at the Router component in the Example app.

What exactly are you trying to achieve here?

@anfen
Copy link
Author

anfen commented Dec 11, 2019

The SharedElementTest component above is the only component in the test app that I created.

The result I'm trying to achieve is, when a button is pressed, a is animated from it's start position, to its end position, which is on the same screen.

I could do this manually via the Animated library, but as does this out of the box, it seemed reasonable to leverage that functionality.

I've got working when navigating from screen to screen, so hoped animation within the same screen would be feasible.

Do you think now that it isn't possible?

Is there anything I can try to help investigate the issue further?

@IjzerenHein
Copy link
Owner

Do you think now that it isn't possible?

Yes I do think it's possible.

Is there anything I can try to help investigate the issue further?

Please provide me with a working example repo or Expo snack that contains the code. You do have to structure your code in such a way that whenever the nodes are changed, that the <SharedElementTransition> is rendered with those nodes. That is the trigger for the native code to hide the original elements and render the cloned version on the screen.

@tiliv
Copy link

tiliv commented Aug 27, 2021

I struggled with the maximum stack overflow issue, and discovered that it happens in a function component with

ref={ref => setContainerNode(nodeFromRef(ref))}

This triggers a state set in the render phase, which is illegal. I fixed this by forcing a setTimeout:

const _setContainerNode = (ref) => {
  setTimeout(() => {
    setContainerNode(nodeFromRef(ref));
  });
};

// render step:
ref={_setContainerNode}

@ravis-farooq
Copy link

ravis-farooq commented Aug 4, 2022

I tried to do it in functional based component but failed to do so Kindly Help.

 import {
  SharedElement,
  SharedElementTransition,
  nodeFromRef,
} from 'react-native-shared-element';
import * as React from 'react';
import {
  View,
  StyleSheet,
  Animated,
  Dimensions,
  TouchableOpacity,
  Image,
} from 'react-native';

export default App = () => {
  const progress = React.useRef(new Animated.Value(0)).current;
  const [isScene2Visible, setisScene2Visible] = React.useState(false);
  const [isInProgress, setisInProgress] = React.useState(false);
  const [scene1Ancestor, setscene1Ancestor] = React.useState(undefined);
  const [scene2Ancestor, setscene2Ancestor] = React.useState(undefined);
  const [scene1Node, setscene1Node] = React.useState(undefined);
  const [scene2Node, setscene2Node] = React.useState(undefined);

  const onPressNavigate = () => {
    // this.setState({ isScene2Visible: true, isInProgress: true });
    setisScene2Visible(true);
    setisInProgress(true);

    Animated.timing(progress, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }).start(() => setisInProgress(false));
  };

  const onPressBack = () => {
    // this.setState({ isInProgress: true });
    setisInProgress(true);
    Animated.timing(progress, {
      toValue: 0,
      duration: 1000,
      useNativeDriver: true,
    }).start(() => {
      setisScene2Visible(false);
      setisInProgress(false);
    });
  };

  const onSetScene1Ref = ref => {
    setTimeout(() => {
      setscene1Ancestor(nodeFromRef(ref));
    });
  };

  const onSetScene2Ref = ref => {
    setTimeout(() => {
      setscene2Ancestor(nodeFromRef(ref));
    });
  };

  const {width} = Dimensions.get('window');
  return (
    <>
      <TouchableOpacity
        style={styles.container}
        activeOpacity={0.5}
        onPress={() => {
          isScene2Visible ? onPressBack() : onPressNavigate();
        }}>
        {/* Scene 1 */}
        <Animated.View
          style={{
            ...StyleSheet.absoluteFillObject,
            transform: [{translateX: Animated.multiply(-200, progress)}],
          }}>
          <View style={styles.scene} ref={onSetScene1Ref}>
            <SharedElement
              onNode={node => {
                setTimeout(() => {
                  setscene1Node(node);
                });
              }}>
              <Image
                style={styles.image1}
                source={{
                  uri: 'https://images.unsplash.com/photo-1659366100362-d4547c23ceeb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80',
                }}
              />
            </SharedElement>
          </View>
        </Animated.View>

        {/* Scene 2 */}
        {isScene2Visible ? (
          <Animated.View
            style={{
              ...StyleSheet.absoluteFillObject,
              transform: [
                {
                  translateX: Animated.multiply(
                    -width,
                    Animated.add(progress, -1),
                  ),
                },
              ],
            }}>
            <View style={styles.scene2} ref={onSetScene2Ref}>
              <SharedElement
                onNode={node => {
                  setTimeout(() => {
                    setscene2Node(node);
                  });
                }}>
                <Image
                  style={styles.image2}
                  source={{
                    uri: 'https://images.unsplash.com/photo-1659366100362-d4547c23ceeb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80',
                  }}
                />
              </SharedElement>
            </View>
          </Animated.View>
        ) : undefined}
      </TouchableOpacity>

      {/* Transition overlay */}
      {isInProgress ? (
        <View style={styles.sharedElementOverlay} pointerEvents="none">
          <SharedElementTransition
            start={{
              node: scene1Node,
              ancestor: scene1Ancestor,
            }}
            end={{
              node: scene2Node,
              ancestor: scene2Ancestor,
            }}
            position={progress}
            animation="move"
            resize="stretch"
            align="auto"
          />
        </View>
      ) : undefined}
    </>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#ecf0f1',
  },
  scene: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'white',
    justifyContent: 'center',
    alignItems: 'center',
  },
  scene2: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: '#00d8ff',
    justifyContent: 'center',
    alignItems: 'center',
  },
  image1: {
    resizeMode: 'cover',
    width: 160,
    height: 160,
    // Images & border-radius have quirks in Expo SDK 35/36
    // Uncomment the next line when SDK 37 has been released
    //borderRadius: 80
  },
  image2: {
    resizeMode: 'cover',
    width: 300,
    height: 300,
    borderRadius: 0,
  },
  sharedElementOverlay: {
    ...StyleSheet.absoluteFillObject,
  },
});

@IjzerenHein
Copy link
Owner

Solution provided here:
#115 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants