react-navigation / rfcs

RFCs for changes to React Navigation
Other
88 stars 16 forks source link

Customizable per-screen transitions with StackNavigator #10

Closed brentvatne closed 3 years ago

brentvatne commented 6 years ago

This is highly requested and it would be very convenient. Need to outline the use cases, how some hypothetical new API would look and how it would improve over the current way to do this with separate stacks. Also, are there things we cannot currently do without screen-specific transitions?

https://github.com/react-navigation/react-navigation/issues/2585 includes a link to several issues about this.

Copied over from @satya164's post on https://github.com/react-navigation/react-navigation/issues/3217:


Recently I needed to customize screen transition animation for a single screen and it wasn't really straightforward. This proposal aims to provide a simple and straightforward way to specify screen transition for a single screen.

There is an existing proposal https://github.com/react-navigation/react-navigation/issues/175, but it looks very confusing and difficult to use.

Problem

Currently, to be able to customize screen transition for a specific screen, you need to configure it globally and it's not very intuitive. It looks something like this:

navigationOptions: {
  transitionSpec: {
    duration: 200,
  },
  transitionConfig: () => ({
    screenInterpolator: props => {
      // Transitioning to search screen (navigate)
      if (props.scene.route.routeName === 'Search') {
        return CardStackStyleInterpolator.forFade(props);
      }

      const last = props.scenes[props.scenes.length - 1];

      // Transitioning from search screen (goBack)
      if (last.route.routeName === 'Search') {
        return CardStackStyleInterpolator.forFade(props);
      }

      return CardStackStyleInterpolator.forHorizontal(props);
    },
  }),
}

It's kinda weird and also doesn't allow me to specify a different duration for the back transition.

Proposal

When customizing the transition for a specific screen, we would want to customize 2 different transitions:

  1. Animation when navigating to the screen
  2. Animation when going back from the screen

For both animations, we should be able to specify which style properties should be animated, e.g. opacity, transform or both, also control the duration of both animations individually. We should also be able to specify transitions for the header buttons.

Keeping these in mind, the following would work for the above scenario:

type TransitionConfigurator = (
  toTransitionProps: TransitionProps,
  fromTransitionProps: TransitionProps) =>  {
    transitionSpec?: NavigationTransitionSpec,
    screenInterpolator?: Object,
    headerLeftInterpolator?: Object,
    headerTitleInterpolator?: Object,
    headerRightInterpolator?: Object,
  } 

This differs from the current type definition https://github.com/react-navigation/react-navigation/blob/master/src/TypeDefinition.js#L522 and therefore is a breaking change. However, it's possible to support both styles and deprecate the old style gradually without breaking existing code.

This also changes the behavior so that:

  1. Empty object (screenInterpolator: {}) will disable transition animations for any property (I think this is the current behavior too)
  2. null/undefined (screenInterpolator: null) will use the default transition animations (this is a new behavior)

Basically, this proposal aims to keep the semantics as close to the previous semantics but changes the way the options are specified to be more flexible.

Usage example

navigationOptions: {
  transitionConfig: (toTransitionProps, fromTransitionProps) => {
    const isBack = fromTransitionProps.navigation.state.index >= toTransitionProps.navigation.state.index;
    const routeName = isBack ? fromTransitionProps.scene.route.routeName : toTransitionProps.scene.route.routeName;

    // This check is only for the case where the transitionConfig is specified globally per navigator basis
    // If the config is specified per screen basis, then `routeName` will always refer to the current screen
    if (routeName === 'Search') {
      return {
        transitionSpec: { duration: isBack ? 150 : 200 },
        screenInterpolator: CardStackStyleInterpolator.forFade(props),
      }
    }
  },
}

Options specified per screen will always take priority over options specified globally.

When navigation from ScreenA -> ScreenB, the transitionConfig option on both screens are called with the same set of arguments, i.e. toTransitionProps.scene refers to ScreenB and fromTransitionProps refers to ScreenA. However, the config returned from ScreenA.navigationOptions.transitionConfig is applied only to ScreenA and the config returned from ScreenB.navigationOptions.transitionConfig is applied only to ScreenB.

n1ru4l commented 6 years ago

Would be awesome if there is also a way to specify the gestures for that screen. E.g. using the example of your Problem section results in a gesture that do not match the animation.

DriesVS commented 6 years ago

I have the same fix! But as @n1ru4l pointed out, the gesture direction is defined by mode. Would be handy to dynamically configure the gesture direction based on sceneProps.

jemmyphan commented 6 years ago

@brentvatne if I'm not mistaken. since react-navigation/react-navigation@e27ad22, transitionConfig has had these parameters already. And it's working right now. is it safe to use it now? since the documentation hasn't been updated.

brentvatne commented 6 years ago

@jemmyphan - I don't think so -- this works the same as before after that commit unless I'm missing something

jemmyphan commented 6 years ago

@brentvatne I totally missed react-navigation/react-navigation@2ee8548. 😅

spruce-bruce commented 6 years ago

This is related - have you thought much about allowing the ability to override transitions per navigate call as well? It's kind of edge casey, but I can imagine creating a screen and wanting to choose at time of navigation how the transition will happen.

props.navigation.navigate('Screen', params, transitionConfigOverride)?

marcshilling commented 6 years ago

What @spruce-bruce said above is what I need. My use case is a button that the user can tap to push a screen (with animation), versus me wanting to programmatically push that screen without animation when something else happens.

n1ru4l commented 6 years ago

@spruce-bruce Can't we have a more generic API that might be more expandable in the future?

e.g. like

props.navigation.navigate({
  screen: `Screen`,
  params: {},
  transitionConfig: {}
})

I prefer having a single object with sub-properties over having list of arguments

spruce-bruce commented 6 years ago

Certainly - I wouldn't be dogmatic about the api example I gave, just showing "fewest api changes" possible example to highlight the idea

Bonobomagno commented 6 years ago

options specified per screen will always take priority over options specified globally.

Is this in react-navigation 2? Because it don't work on my navigator, using it per screen basis.

djw27 commented 6 years ago

Just wanted to jump on board here and say that coming from ex-navigation this feels like an area that's lacking.

We use modals and stacks within tabs fairly interchangeably and made heavy use of specifying the following when using ex-navigation:

static route = {
  styles: {
    ...NavigationStyles.FloatVertical, 
 },
}

on screens which we wanted to appear as modals.

The initial proposal from @satya164 is fairly similar similar to this - is there anyone working on this which I can help out with or does this need starting afresh?

satya164 commented 6 years ago

@spruce-bruce @n1ru4l I think that's also possible with the proposed API. You'll need to pass some param which you can check in the transition config to return a different animation.

fbn4sc commented 6 years ago

I cannot find CardStackStyleInterpolator.

Any workaround?

Sixbitunder commented 6 years ago

@fbn4sc the CardStackStyleInterpolator has been replaced by StackViewStyleInterpolator

you can Import it by adding

import StackViewStyleInterpolator from 'react-navigation/src/views/StackView/StackViewStyleInterpolator';

remeber also to switch props in sceneProps

elizond0 commented 6 years ago

@Sixbitunder Thanks for your answer, it works. when I swipe the page from left to right on android, how could navigation “go back” just like the ios gesture.

kaantyy commented 5 years ago

I cannot find either CardStackStyleInterpolator or StackViewStyleInterpolator.

Workaround with v2.14.2?

brentvatne commented 5 years ago

@kaantyy: https://github.com/react-navigation/react-navigation-stack/blob/dab7f2515e2bc7c3b57a89a23f9c3ed2dfdece44/src/index.js#L28-L42

nandorojo commented 5 years ago

This would be an amazing addition.

hxiongg commented 5 years ago

Any update on this?

branegg-old commented 5 years ago

:(

brentvatne commented 5 years ago

stack is being rewritten - https://github.com/react-navigation/stack/pull/131

martinjuhasz commented 3 years ago

Are there any news on this @brentvatne ?

brentvatne commented 3 years ago

you can do this in v5. https://www.youtube.com/watch?v=PvjV96CNPqM. just use options on screen instead of on navigator