IjzerenHein / react-native-magic-move

Create magical move transitions between scenes in react-native 🐰🎩✨
https://expo.io/@ijzerenhein/react-native-magic-move-demo
MIT License
974 stars 45 forks source link

No animation when using react-navigation SwitchNavigator #2

Open JohnnyDevX opened 5 years ago

JohnnyDevX commented 5 years ago

Hi! Love this library!

But I'm having problems morphing one element into another when elements are on separate stacks / switch navigators.

All the examples are in react-native-router-flux which I'm totally unfamiliar with.

could you make some very simple examples in plain react-navigation v3 where elements are on separate stacks?

I'll show it exactly:

this is App.js

import React from 'react';
import { createAppContainer } from 'react-navigation';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';

import getStore from '@redux';
import AppNavigator from '@navigation';
import NavigationService from '@navigation/service';

import * as MagicMove from 'react-native-magic-move';
import "react-navigation-magic-move";

const { Store, Persistor } = getStore();
const AppContainer = createAppContainer(AppNavigator);

export default class App extends React.Component {

  render() {
    return (
      <Provider store={Store}>
        <PersistGate loading={null} persistor={Persistor}>
          <MagicMove.Provider>
            <AppContainer ref={navigatorRef => { NavigationService.setTopLevelNavigator(navigatorRef); }} />
          </MagicMove.Provider>        
        </PersistGate>
      </Provider>
    );
  }

}

Navigation

...
export default createSwitchNavigator({
  one: StackOne,
  two: StackTwo
});

StackOne ( FAB that is going to morph to a View with flex: 1 on a StackTwo

render() {

  let MagFAB = MagicMove.createMagicMoveComponent(FAB);

    return (
      <MagicMove.Scene style={styles.container}>

        ...

        <MagFAB 
          id="FABtoView"
          icon="md-add" 
          color={colors.secondary} 
          icolor="#FFFFFF" 
          onpres={()=>this.props.navigation.navigate('two')} />

      </MagicMove.Scene>
    );
  }
}

StackTwo

render() {
  return (
    <MagicMove.Scene style={{flex: 1}}>
      <MagicMove.View id="FABtoView" style={{flex:1}}>
        <ScrollView style={styles.container}>
          ...
        </ScrollView>            
      </MagicMove.View>
    </MagicMove.Scene>
  );
}

And the FAB element is:

  <TouchableWithoutFeedback onPress={this.handlePress}>
    <View style={[styles.fab, { backgroundColor: this.props.color ? this.props.color : '#5354FF' }]}>
      <Icon.Ionicons name={this.props.icon} size={24} color={this.props.icolor} />
    </View>
  </TouchableWithoutFeedback>

const styles = StyleSheet.create({

  fab: {
    alignItems: 'center',
    justifyContent: 'center',
    position: 'absolute',
    bottom: 32,
    right: 32,
    borderRadius: 1000,
    height: 56,
    width:  56
  }

});

What happens? So I'm on StackOne and I click the FAB and it navigates me to a StackTwo properly, no errors, but also no animation. No effect.

I tried to do the same but instead of Switch navigator I used a StackNavigator, like this:

export default createStackNavigator({
  one: ScreenOne,
  two: ScreenTwo
}, ...);

and even worse - got error that measureLayout is not a function, both ways (StackOne -> StackTwo and when going back). // edit, more specifically: component.getRef().measureLayout is not a function. (In 'component.getRef().measureLayout((0, _reactNative.findNodeHandle)(sceneRef), onSuccess, onFail)', 'component.getRef().measureLayout' is undefined)

Stack trace:
  node_modules\react-native\Libraries\YellowBox\YellowBox.js:59:8 in error
  node_modules\react-native-magic-move\src\Animation.js:145:20 in errorHandler
  node_modules\promise\setimmediate\core.js:37:14 in tryCallOne
  node_modules\promise\setimmediate\core.js:123:25 in <unknown>
  node_modules\react-native\Libraries\Core\Timers\JSTimers.js:152:14 in _callTimer
...

What am I missing?

IjzerenHein commented 5 years ago

Alright, I will take a closer look at this shortly. Looking at the code and the error you are getting, I think it is related to the FAB component. I haven't tested very extensively with wrapping other components yet, but it might have some trouble with the TouchableWithoutFeedback, which does a sort of a pass-through internally. Could you try wrapping that one in a View and see whether the problem still persists?

KonradBDev commented 5 years ago

Yes, I will do this in one hour, when I'm back home.

JohnnyDevX commented 5 years ago

Ok, wrapped the FAB's TouchableWithoutFeedback in a View and nothing changed. Still, it navigates but without any animations (SwitchNavigator)

IjzerenHein commented 5 years ago

Could you try setting the collapsed={false} prop on the View?

JohnnyDevX commented 5 years ago

collabsed={false} gives no effect, you probably meant collapsable={false} (InteliiSense) and it makes the FAB dont work... no animation nor navigation

JohnnyDevX commented 5 years ago

btw I'm going to leave Expo and React Navigation (after months of messing with it) and go for Wix Navigation... Expo doesnt support in-app purchases, not even planned react-navigation api is very confusing, and the JS-powered navigation elements are really slow and painful to use... plus Wix Navigation support some animations like shared elements out of the box. Sounds good.

IjzerenHein commented 5 years ago

Yes that's what I meant collapsable, sorry my bad. Hmm, odd, it's hard to assess what could be wrong here. It would be best if there is an Expo snack that I can take a look at. Also, there is the debug prop on the magic-move components. It's super useful and will print out all kinds of log information to the console about the components. If you set that on the component you were expecting a transition on and dump the log here, I will probably be able to reason about whats going on.

IjzerenHein commented 5 years ago

Yep, Wix navigation is also pretty cool. I used it in a project a while back and it worked well. The only thing was that the API was marked as "experimental" for a really long time which sucked. I haven't worked with react-navigation much either to be honest. In the last project we used react-native-router-flux and that was pretty straightforward and worked quite well. The documentation of that needs a bit more work through.. The thing I do like about react-navigation is the ability to run it with react-native-web, which is pretty sweet. I didn't know Wix support shared components to be honest, I really need to check that out again. And as for this library, it's still quite young and I need to iron out the issues. There's more coming in terms of transitions and native performance optimisations in the near future. Cheers

Johan-dutoit commented 5 years ago

This isn't specific to the FAB. See this quickly thrown together example (testable in expo-snack)

PS. Nice lib!

import React from 'react';
import { View, Text } from 'react-native';
import {
  createAppContainer,
  createSwitchNavigator,
  createStackNavigator,
} from 'react-navigation';

import * as MagicMove from 'react-native-magic-move';
import 'react-navigation-magic-move';

class Screen1 extends React.Component {
  render() {
    return (
      <MagicMove.Scene  style={{backgroundColor: 'green'}}>
        <MagicMove.View id="logoA" transition={MagicMove.Transition.flip.x}>
          <Text onPress={() => this.props.navigation.navigate('ScreenB')}>
            SAME STACK (GO TO B)
          </Text>
        </MagicMove.View>
        <MagicMove.View id="logo2" transition={MagicMove.Transition.flip.x}>
          <Text onPress={() => this.props.navigation.navigate('two')}>
            SWITCH STACK (GO TO 2)
          </Text>
        </MagicMove.View>
      </MagicMove.Scene>
    );
  }
}

class Screen2 extends React.Component {
  render() {
    return (
      <MagicMove.Scene style={{backgroundColor: 'red'}}>
        <MagicMove.View id="logoA" transition={MagicMove.Transition.flip}>
          <Text onPress={() => this.props.navigation.navigate('ScreenA')}>
             SAME STACK (GO TO A)
          </Text>
        </MagicMove.View>
         <MagicMove.View id="logo2" transition={MagicMove.Transition.flip.x}>
          <Text onPress={() => this.props.navigation.navigate('one')}>
            SWITCH STACK (GO TO 1)
          </Text>
        </MagicMove.View>
      </MagicMove.Scene>
    );
  }
}

const StackOne = createStackNavigator({
  ScreenA: {
    screen: Screen1,
  },
  ScreenB: {
    screen: Screen2,
  },
});

const StackTwo = createStackNavigator({
  Screen: {
    screen: Screen2,
  },
});

const AppNavigator = createSwitchNavigator({
  one: StackOne,
  two: StackTwo,
});

const AppContainer = createAppContainer(AppNavigator);

export default class App extends React.Component {
  render() {
    return (
      <MagicMove.Provider>
        <AppContainer />
      </MagicMove.Provider>
    );
  }
}
IjzerenHein commented 5 years ago

Alright cool, thanks for that example @Johan-dutoit ! πŸ‘ I'm gonna take a look and see what's going on there

IjzerenHein commented 5 years ago

Hi, I've investigated the problem and I understand what's going on now. Magic-move can only animate when both Scenes are mounted at any given time (the previous scene needs to be mounted when the new scene is mounted/activated). Unfortunately, the SwitchNavigator, unmounts the old scene before mounting the new scene. Here's a transcript of what's going on:

[MagicMove] Unmounted scene "Screen" (active = true)
[MagicMove] Unmounted View "logoA"
[MagicMove] Unmounted View "logo2"
[MagicMove] Mounted View "logoA" (active = false)
[MagicMove] Mounted View "logo2" (active = false)
[MagicMove] Mounted scene "__autoSceneId3" (active = false)
[MagicMove] Activated scene "ScreenA"
[MagicMove] Not animating View "logoA" (no previous component found)
[MagicMove] Not animating View "logo2" (no previous component found)

I currently don't have a solution for this., but I'm gonna give it some more thought. A solution could be a custom SwitchNavigator that keeps the old scene briefly mounted and shows the new scene on top.

Johan-dutoit commented 5 years ago

Glad you figured out the underlying problem @IjzerenHein!

I don't have this issue myself, as I use the switch navigators differently, which is to create different flows in the app (for a lack of a better terminology), i.e. Onboarding flow vs login flow vs auth'd flow.

So I'm not entirely convinced this needs to be supported. That being said, if it's an 'easy' fix, then why not.

Let me know if you need a hand.

IjzerenHein commented 5 years ago

Hey Johan, yes I'd love a hand πŸ˜„

I'm not sure if it's an easy fix though. It would largely depend on how complex the SwitchNavigator is. The goal here would be to make sure the old scene is still mounted at the time the new scene is mounted. I can imagine having a custom SwitchNavigator in react-navigation-magic-move that people need to pull-in when they want to address this problem. But perhaps there are other solutions as well.

cheers, Hein

lucianomlima commented 5 years ago

It's possible to have animations on SwitchNavigator https://github.com/react-navigation/react-navigation-animated-switch