react-navigation/react-navigation

Android native transition between screens

ferrannp opened this issue · 3 comments

Update 1

This might be even more complicated checking the actual newest source:

activity_open_enter.xml
activity_open_exit.xml
activity_close_enter.xml
activity_close_exit.xml

Original

We had a discussion about this in the past @ericvicenti @satya164. To me, right now it looks similar to the default Android behaviour but still I think it is not as it is on Android. Specially when going back (exit animation).

This is the current implementation (TransitionConfig.js):

// Standard Android navigation transition when opening an Activity
const FadeInFromBottomAndroid = ({
  // See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
  transitionSpec: {
    duration: 350,
    easing: Easing.out(Easing.poly(5)),  // decelerate
    timing: Animated.timing,
  },
  screenInterpolator: CardStackStyleInterpolator.forFadeFromBottomAndroid,
} : TransitionConfig);

// Standard Android navigation transition when closing an Activity
const FadeOutToBottomAndroid = ({
  // See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_close_exit.xml
  transitionSpec: {
    duration: 230,
    easing: Easing.in(Easing.poly(4)),  // accelerate
    timing: Animated.timing,
  },
  screenInterpolator: CardStackStyleInterpolator.forFadeFromBottomAndroid,
} : TransitionConfig);

But when I check those links: http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml & http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_close_exit.xml#26, I see a different configuration.

in
alpha: 200ms, decelerate_quart(2)
position: 350ms, decelerate_quint(2.5)

out
alpha: 150ms, linear, delay 150
position: 250ms, accelerate_quart(2.5)

First of all there are two animations (alpha and position) that run in parallel (with different durations and easing). I've been trying to replicate the values directly in Transitioner.js. Right now it looks like:

const animations = indexHasChanged
  ? [
    timing(
      progress,
      {
        ...transitionSpec,
        toValue: 1,
      },
    ),
    timing(
      position,
      {
        ...transitionSpec,
        toValue: nextProps.navigation.state.index,
      },
    ),
  ]
  : [];

...
Animated.parallel(animations).start(this._onTransitionEnd);
});

I tried to replicate the config:

let alphaDelay = 0, alphaDuration, alphaEasing, positionDuration, positionEasing;
if (transitionSpec.duration === 350) {
  alphaDuration = 200;
  alphaEasing = Easing.bezier(0.165, 0.840, 0.440, 1.000); // ease-out-quart
  positionDuration = 350;
  positionEasing = Easing.bezier(0.230, 1.000, 0.320, 1.000); // ease-out-quint
} else { // 250
  alphaDuration = 150;
  alphaDelay = 100;
  alphaEasing = Easing.linear();
  positionDuration = 250;
  positionEasing = Easing.bezier(0.895, 0.030, 0.685, 0.220); // ease-in-quart
}

const animations = indexHasChanged
  ? [
    timing(
      progress,
      {
        ...transitionSpec,
        duration: alphaDuration,
        easing: alphaEasing,
        delay: alphaDelay > 0 ? alphaDelay : 0,
        toValue: 1,
      },
    ),
    timing(
      position,
      {
        ...transitionSpec,
        duration: positionDuration,
        easing: positionEasing,
        toValue: nextProps.navigation.state.index,
      },
    ),
  ]
  : [];

...
Animated.parallel(animations).start(this._onTransitionEnd);

It looks like:

ezgif-3-ee25b4c40d

But I feel I am still missing something. Even that it uses Animated.parallel, it does not seem that the alpha one (the first one) is affecting at all. Actually, if I remove it completely from the array, I do not see any difference (I think). UPDATE: Instead of parallel I think we need Animated.stagger instead

What do you think? :)

Consider sending a PR that improves animations!

There is also an Android "More" transition when in settings. I created an RFC - react-navigation/rfcs#35