/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict-local * @format */ import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; import ToggleNativeDriver from './utils/ToggleNativeDriver'; import * as React from 'react'; import {useLayoutEffect, useRef, useState} from 'react'; import { Animated, Button, PanResponder, StyleSheet, Text, View, } from 'react-native'; function TextBox({children}: $ReadOnly<{children: React.Node}>): React.Node { // Prevent touch from being hijacked by Text return ( {children} ); } function AnimatedEventExample({ containerPageXY, useNativeDriver, }: $ReadOnly<{ containerPageXY: $ReadOnly<{x: number, y: number}>, useNativeDriver: boolean, }>): React.Node { const boxRef = useRef>(); const pointerPageXY = useRef( new Animated.ValueXY( { x: containerPageXY.x, y: containerPageXY.y, }, {useNativeDriver}, ), ).current; const dragStartOffsetXY = useRef( new Animated.ValueXY({x: 0, y: 0}, {useNativeDriver}), ).current; // We'll no longer have to subtract containerPageXY.x/y from offset, if we can animate left/top props natively // TODO: T222117268 Adopt fabric driven c++ native animated in RNTester const finalOffsetX = Animated.subtract( Animated.subtract(pointerPageXY.x, dragStartOffsetXY.x), containerPageXY.x, ); const finalOffsetY = Animated.subtract( Animated.subtract(pointerPageXY.y, dragStartOffsetXY.y), containerPageXY.y, ); const syncAnimationToHostView = () => { boxRef.current?.setNativeProps({ transform: [ {translateX: finalOffsetX.__getValue()}, {translateY: finalOffsetY.__getValue()}, ], }); }; return ( { // Animated change sometimes doesn't commit to Fabric, and box will jump back to offset before animation // This is to make sure that finalOffsetX/Y are synced to native host view // TODO: T222117268 Adopt fabric driven c++ native animated in RNTester syncAnimationToHostView(); }} style={[ styles.box, { backgroundColor: useNativeDriver ? 'orange' : 'violet', transform: [ { translateX: finalOffsetX, }, { translateY: finalOffsetY, }, ], }, ]}> Use {useNativeDriver ? 'Native' : 'JS'} Animated.event ); } function PanResponderExample({ useNativeDriver, }: $ReadOnly<{useNativeDriver: boolean}>): React.Node { const finalOffsetXY = useRef( new Animated.ValueXY({x: 0, y: 0}, {useNativeDriver}), ).current; const dragStartOffsetXY = useRef({x: 0, y: 0}).current; const panResponder = useRef( PanResponder.create({ onMoveShouldSetPanResponder: (pressEvent, gestureState) => { dragStartOffsetXY.x = finalOffsetXY.x.__getValue(); dragStartOffsetXY.y = finalOffsetXY.y.__getValue(); return true; }, onPanResponderMove: (pressEvent, gestureState) => { if (gestureState.dx !== 0) { finalOffsetXY.x.setValue(dragStartOffsetXY.x + gestureState.dx); } if (gestureState.dy !== 0) { finalOffsetXY.y.setValue(dragStartOffsetXY.y + gestureState.dy); } }, }), ).current; return ( Use PanResponder{' '} {`+ ${useNativeDriver ? 'Native Animated value' : 'JS Animated value'}`} ); } function PanGestureExample(): React.Node { const [busy, setBusy] = useState(false); const [useNativeDriver, setUseNativeDriver] = useState(false); const containerRef = useRef>(); const [containerPageXY, setContainerPageXY] = useState(null); useLayoutEffect(() => { containerRef.current?.measure((x, y, width, height, pageX, pageY) => { setContainerPageXY({x: pageX, y: pageY}); }); }, []); function sleep(t: number) { setBusy(true); setTimeout(() => { const start = Date.now(); while (Date.now() - start < t) { // sleeping } setBusy(false); }, 1000); } return (