react navigation 手势操作 + 自定义转场动画

最终达到的一个效果

snack 地址: https://snack.expo.io/@midoushitongtong/react-navigation-gesture-transition

可以通过 expo,在你的设备上直接运行它。

实现步骤

配置转场动画的过渡时间

通过配置 transitionSpec 属性,来配置每个页面转场动画的过渡时间,此属性可以针对特定的页面进行单独的配置。

{
  transitionSpec: {
    // 进入页面的配置
    open: {
      animation: 'timing',
      config: {
        duration: 450,
        easing: Easing.bezier(0.35, 0.39, 0, 1)
      }
    },
    // 关闭页面的配置
    close: {
      animation: 'timing',
      config: {
        duration: 450,
        easing: Easing.bezier(0.35, 0.39, 0, 1)
      }
    }
  }
}

配置转场动画的动画效果

通过配置 transform 属性,来配置每个页面转场动画的动画效果,动画效果有很多种,例如【淡入淡出X/Y轴水平平移Z轴水平旋转放大/缩小,等】,这里主要以X轴水平平移作为参考,其他配置类似,此属性可以针对特定的页面进行单独的配置。

{
  transform: [
    {
      // next:true 为关闭的页面, false 为打开的页面
      translateX: next
        ? Animated.interpolate(next.progress, {
            inputRange: [0, 1],
            // 关闭的页面从 (0 的位置) 水平平移到 (-屏幕宽度 * 三分之一的位置)
            outputRange: [0, -layouts.screen.width * 0.3],
          })
        : Animated.interpolate(current.progress, {
            inputRange: [0, 1],
            // 打开的页面从 (屏幕宽度位置) 水平平移到 (0的位置)
            outputRange: [layouts.screen.width, 0],
          }),
    },
    // ... 需要配置更多的动画效果,可以根据上面的配置依次往下面添加
  ];
}

配置手势操作

在 react navigation 中,配置手势操作非常简单,只需配置两个属性gestureEnabled以及gestureDirection,前者用于开启或关闭手势操作,后者用于控制手势操作为左右滑动还是上下滑动,此属性可以针对特定的页面进行单独的配置。

{
  gestureEnabled: true,
  gestureDirection: 'horizontal',
  // gestureDirection: 'vertical'
}

整合

将上面的配置整合在一起,也就是上面 gif 中第一个转场动画的配置
看起来像这个样子

<Stack.Screen
  name="A"
  component={A}
  options={{
    gestureEnabled: true,
    gestureDirection: 'horizontal',
    transitionSpec: {
      open: {
        animation: 'timing',
        config: {
          duration: 450,
          easing: Easing.bezier(0.35, 0.39, 0, 1),
        },
      },
      close: {
        animation: 'timing',
        config: {
          duration: 450,
          easing: Easing.bezier(0.35, 0.39, 0, 1),
        },
      },
    },
    cardStyleInterpolator: ({ current, next, layouts }) => {
      return {
        cardStyle: {
          transform: [
            {
              translateX: next
                ? Animated.interpolate(next.progress, {
                    inputRange: [0, 1],
                    outputRange: [0, -layouts.screen.width * 0.3],
                  })
                : Animated.interpolate(current.progress, {
                    inputRange: [0, 1],
                    outputRange: [layouts.screen.width, 0],
                  }),
            },
          ],
        },
      };
    },
  }}
/>

参考资料

https://callstack.com/blog/custom-screen-transitions-in-react-navigation

https://reactnavigation.org