software-mansion / react-native-reanimated

React Native's Animated library reimplemented
https://docs.swmansion.com/react-native-reanimated/
MIT License
8.86k stars 1.29k forks source link

transform Matrix not included in the shared Transition example, #4817

Closed hamdij0maa closed 1 year ago

hamdij0maa commented 1 year ago

Description

https://github.com/software-mansion/react-native-reanimated/blob/main/app/src/examples/SharedElementTransitions/CustomTransition.tsx

try a pan gesture handler gesture for going back, when using the animation in the example, there is a flickering in the position here is the component :

Steps to reproduce

https://github.com/software-mansion/react-native-reanimated/blob/main/app/src/examples/SharedElementTransitions/CustomTransition.tsx

try a pan gesture handler gesture for going back, when using the animation in the example, there is a flickering in the position here is the component :

import * as React from 'react';
import {BackHandler, Platform, Pressable} from 'react-native';

import {NavigationProp, useIsFocused} from '@react-navigation/native';
import {useTheme} from '@shopify/restyle';
import {Gesture, GestureDetector} from 'react-native-gesture-handler';
import {
  Extrapolate,
  FadeIn,
  interpolate,
  runOnJS,
  useAnimatedStyle,
  useDerivedValue,
  useSharedValue,
  withSpring,
  WithSpringConfig,
} from 'react-native-reanimated';
import {clamp, snapPoint, useVector} from 'react-native-redash';
import {useSafeAreaInsets} from 'react-native-safe-area-context';

import Box, {AnimatedBox} from '../../theme/Box';
import {SIZES} from '../../theme/sizes';
import {
  HEADER_GO_BACK_ICON_SIZE,
  ROUND_BUTTON_SIZE,
  Theme,
} from '../../theme/theme';
import {ArrowLeftSVG} from '../svg/icons/ArrowLeftSVG';
import shouldStopLegend from '../../legends/shouldStop-legend';
import BackgroundThread from '@gennadysx/react-native-background-thread';
import shouldFreezeLegend from '../../legends/shouldFreezeLegend';
export const SPRING_CONFIG: WithSpringConfig = {
  damping: 80,
  mass: 0.6,
  overshootClamping: false,
  restSpeedThreshold: 0.1,
  restDisplacementThreshold: 0.1,
};

export interface IGoBackPanGestureHandlerProps {
  children: React.ReactNode;
  navigation: NavigationProp<any>;
}

const onStart = () => {
  BackgroundThread.run(() => {
    shouldStopLegend.set(true);
  }, BackgroundThread.BackgroundThreadPriority.max);
};

const onCancel = () => {
  BackgroundThread.run(() => {
    shouldStopLegend.set(false);
  }, BackgroundThread.BackgroundThreadPriority.max);
};

const GoBackPanGestureHandlerMemo: React.FC<IGoBackPanGestureHandlerProps> = ({
  children,
  navigation,
}) => {
  const insets = useSafeAreaInsets();
  const {spacing} = useTheme<Theme>();
  const isGestureActive = useSharedValue(false);
  const translation = useVector(0, 0);
  const isFocused = useIsFocused();
  const snapBack = useSharedValue(false);
  const runOnlyOnce = useSharedValue(false);

  React.useEffect(() => {
    let blurListenerEnded = navigation.addListener('blur', () => {
      shouldFreezeLegend.set(false);
    });

    let focusListenerEnded = navigation.addListener('focus', () => {
      shouldFreezeLegend.set(true);
    });

    return () => {
      navigation.removeListener('blur', blurListenerEnded);
      navigation.removeListener('focus', focusListenerEnded);
    };
  }, [isFocused, navigation]);

  const goBackHandler = React.useCallback(() => {
    BackgroundThread.run(() => {
      try {
        isFocused && navigation?.canGoBack() && navigation?.goBack();
        runOnlyOnce.value = true;
      } catch (e) {
        return;
      }
    }, BackgroundThread.BackgroundThreadPriority.max);
  }, [isFocused, navigation, runOnlyOnce]);

  const panGesture = React.useMemo(
    () =>
      Gesture.Pan()

        .activeOffsetX(10)
        .simultaneousWithExternalGesture()
        .cancelsTouchesInView(true)

        .onStart(() => {
          runOnJS(onStart)();

          isGestureActive.value = true;
        })

        .onChange(({translationX, translationY}) => {
          if (Math.abs(translationY) > 5) {
            isGestureActive.value = true;
            translation.x.value =
              clamp(
                translationX,
                -SIZES.screenWidth / 1.5,
                SIZES.screenWidth / 1.5,
              ) * 0.65;

            translation.y.value = clamp(
              translationY * 0.85,
              0,
              SIZES.screenHeight / 2,
            );
          }
        })

        .onEnd(({velocityX, velocityY}) => {
          isGestureActive.value = false;
          snapBack.value =
            snapPoint(Math.abs(translation.x.value), velocityX, [
              0,
              SIZES.screenWidth,
            ]) >=
              SIZES.screenWidth * 0.25 ||
            snapPoint(Math.abs(translation.y.value), velocityY, [
              0,
              SIZES.screenHeight * 0.25,
            ]) >=
              SIZES.screenHeight * 0.25;
          if (snapBack.value && !runOnlyOnce.value) {
            runOnJS(goBackHandler)();
          } else {
            translation.x.value = withSpring(0, SPRING_CONFIG);
            translation.y.value = withSpring(0, SPRING_CONFIG, () =>
              runOnJS(onCancel)(),
            );
          }
        })
        .onTouchesCancelled(() => {
          runOnJS(onCancel)();
          isGestureActive.value = false;
        }),
    [
      goBackHandler,
      isGestureActive,
      runOnlyOnce.value,
      snapBack,
      translation.x,
      translation.y,
    ],
  );

  React.useEffect(() => {
    const handler = BackHandler.addEventListener(
      'hardwareBackPress',
      () => isFocused,
    );

    return () => {
      handler.remove();
    };
  }, [isFocused]);

  const style = useAnimatedStyle(() => {
    const scale = interpolate(
      translation.y.value,
      [0, SIZES.screenHeight],
      [1, 0.6],
      Extrapolate.CLAMP,
    );
    return {
      transform: [
        {translateX: translation.x.value * 0.7},
        {translateY: translation.y.value * 0.9},
        {scale},
      ],
    };
  }, []);

  const overlayOpacity = useDerivedValue(() => {
    return interpolate(
      translation.y.value,
      [0, 5, SIZES.screenHeight * 0.7],
      [0.5, 0.7, 0],
      Extrapolate.CLAMP,
    );
  });
  const overlayStyle = useAnimatedStyle(() => {
    return {
      opacity: overlayOpacity.value,
    };
  });

  return (
    <>
      <AnimatedBox
        pointerEvents="none"
        position="absolute"
        left={0}
        opacity={0.5}
        right={0}
        top={0}
        bottom={0}
        backgroundColor="black"
        style={overlayStyle}
      />
      <GestureDetector gesture={panGesture}>
        <AnimatedBox
          pointerEvents="box-none"
          overflow="hidden"
          position="relative"
          flex={1}
          style={style}>
          {children}
          <AnimatedBox
            entering={FadeIn.delay(200).duration(300)}
            position="absolute"
            left={spacing.m}
            top={insets.top + (Platform.OS === 'android' ? 2 : -2)}>
            <Pressable android_disableSound onPressIn={goBackHandler}>
              <Box
                height={ROUND_BUTTON_SIZE}
                width={ROUND_BUTTON_SIZE}
                justifyContent="center"
                alignItems="flex-start">
                <ArrowLeftSVG size={HEADER_GO_BACK_ICON_SIZE} color={'white'} />
              </Box>
            </Pressable>
          </AnimatedBox>
        </AnimatedBox>
      </GestureDetector>
    </>
  );
};

const GoBackPanGestureHandler = React.memo(
  GoBackPanGestureHandlerMemo,
  () => true,
);

export {GoBackPanGestureHandler};

Snack or a link to a repository

https://github.com/software-mansion/react-native-reanimated/blob/main/app/src/examples/SharedElementTransitions/CustomTransition.tsx

Reanimated version

3.4.0

React Native version

0.72.0

Platforms

Android, iOS

JavaScript runtime

Hermes

Workflow

Expo bare workflow

Architecture

Paper (Old Architecture)

Build type

Debug mode

Device

iOS simulator

Device model

iPhone 14, Pixel

Acknowledgements

Yes

github-actions[bot] commented 1 year ago

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?