Search code examples
reactjsanimationreact-nativereact-native-gesture-handler

Why can't I get a nativeEvent callback to fire in React Native's Animated.event()?


Why isn't the nativeEvent callback firing in my Animated.event call when a box is dragged?

My ultimate goal is to implement logic to prevent draggable gesture-controlled components from going off-screen, but I can't do that until I figure out why the desired callback never fires.

I set an onGestureEvent callback for a <PanGestureHandler /> component, and I pass it an Animated.event() call with a nativeEvent callback within it (see their docs for an example of how to do this).

I know that the nativeEvent callback isn't firing because the debug and console.log calls in the Animated.block() are not outputting anything to the console (I'm running it using Expo--debug link here). Also, the set(_translateX, translationX) line in the Animate.block() call is never executing either, otherwise I'd expect to see the boxes moving while dragging them (instead of when a touch is released).

Note that if you uncomment the following block, and remove the { nativeEvent: function... } object directly after it, the animation works as expected:

    {
      nativeEvent: {translationX: _translateX}
    },

I feel like I'm missing something very simple but I'm at a loss to figure out what it is.

Here's an expo Expo link for debugging: https://snack.expo.io/d8xCeHhtj

Here's my code:

import React, { Component } from 'react';
import {
  Dimensions,
  StyleSheet,
  Text,
  View,
  Button,
  Animated
} from 'react-native';
import {
  PanGestureHandler,
  ScrollView,
  State,
} from 'react-native-gesture-handler';
const {
  and,
  block,
  clockRunning,
  set,
  Clock,
  cond,
  eq,
  debug,
  Extrapolate,
  max,
  lessThan,
  greaterOrEq,
  Value,
  startClock,
  timing,
  call,
  stopClock,
} = Animated;

function Slider({color, width, height}) {
  const screenWidth = Dimensions.get('window').width;
  const _translateX = new Animated.Value(0);
  const _lastOffset = {x: 0};

  const cmpStyles = StyleSheet.create({
    box: {
      width: width,
      height: height,
      alignSelf: 'center',
      backgroundColor: color,
      margin: 30,
      zIndex: 200,
      color: color,
      transform: [
        {translateX: _translateX},
      ],
    },
  });

  const _onGestureEvent = Animated.event(
        [
  // Uncomment this block to see the original animation
    /*
            {
                nativeEvent: {translationX: _translateX}
            },
    */
  // Comment the following object when uncommenting the previous section
            {
                nativeEvent: function({ translationX, absoluteX }) {
                    return block([
                        debug('x', translationX),
                        call([], () => console.log('the code block was executed')),
                        set(_translateX, translationX),
                    ])
                }
            },
  // ------------------------------
        ],
        {
            useNativeDriver: true,
            listener: (event, gestureState) => {
                const {absoluteX, translationX} = event.nativeEvent;
                //console.log('translationX' + translationX);
                //console.log('dest' + _translateX._value);
            }
        }
  );
  const _onHandlerStateChange = event => {
    const {
      oldState,
      translationX,
      absoluteX,
    } = event.nativeEvent;
    if (oldState === State.ACTIVE) {
      //if (absoluteX + translationX > screenWidth) {
      //console.log("translationX: " + translationX);
      //console.log("screenWidth" + screenWidth);

      // Set the slider to correct position when gesture is released
      _lastOffset.x += translationX;
      _translateX.setOffset(_lastOffset.x);
      _translateX.setValue(0);
    }
  };

  return (
    <PanGestureHandler
      onGestureEvent={_onGestureEvent}
      onHandlerStateChange={_onHandlerStateChange}
            >
      <Animated.View style={cmpStyles.box} />
    </PanGestureHandler>
  );
}

export default function Example() {
  const width = 60;
  const height = 60;

  return (
    <View style={styles.scrollView}>
      <Slider color={'red'} width={width} height={height} />
      <Slider color={'blue'} width={width} height={height} />
      <Slider color={'green'} width={width} height={height} />
      <Slider color={'orange'} width={width} height={height} />
    </View>
  );
}

const styles = StyleSheet.create({
  scrollView: {
    flex: 1,
    marginTop: 120,
  },
})

Thank you for your help.


Solution

  • you can use Animated from react-native-reanimated.

    becasue nativeEvent call is not working when we import Animated from react-native

    import React, { Component } from "react";
    import { Dimensions, StyleSheet, Text, View, Button } from "react-native";
    import { PanGestureHandler, State } from "react-native-gesture-handler";
    import Animated from "react-native-reanimated";
    
    const {
      and,
      block,
      clockRunning,
      set,
      Clock,
      cond,
      eq,
      debug,
      Extrapolate,
      max,
      lessThan,
      greaterOrEq,
      Value,
      startClock,
      timing,
      call,
      stopClock,
      event,
    } = Animated;
    
    function Slider({ color, width, height }) {
      const screenWidth = Dimensions.get("window").width;
      const _translateX = new Value(0);
      const _lastOffset = { x: 0 };
    
      const cmpStyles = StyleSheet.create({
        box: {
          width: width,
          height: height,
          alignSelf: "center",
          backgroundColor: color,
          margin: 30,
          zIndex: 200,
          color: color,
          transform: [{ translateX: _translateX }],
        },
      });
      const _onGestureEvent = event(
        [
          // Uncomment this block to see the original animation
          /*
                {
                    nativeEvent: {translationX: _translateX}
                },
        */
          // Comment the following object when uncommenting the previous section
          {
            nativeEvent: ({ translationX: x, translationY: y, state }) =>
              block([
                // debug('x', _translateX),
                call([], () => console.log("the code block was executed")),
                set(_translateX, x),
              ]),
          },
        ],
        // ------------------------------
        {
            useNativeDriver: true,
            listener: (event, gestureState) => {
                const {absoluteX, translationX} = event.nativeEvent;
                //console.log('translationX' + translationX);
                //console.log('dest' + _translateX._value);
            }
        }
      );
      const _onHandlerStateChange = (event) => {
        const { oldState, translationX, absoluteX } = event.nativeEvent;
        if (oldState === State.ACTIVE) {
          //if (absoluteX + translationX > screenWidth) {
          //console.log("translationX: " + translationX);
          //console.log("screenWidth" + screenWidth);
    
          // Set the slider to correct position when gesture is released
          _lastOffset.x += translationX;
          _translateX.setValue(_lastOffset.x);
        }
      };
    
      return (
        <PanGestureHandler
          onGestureEvent={_onGestureEvent}
          onHandlerStateChange={_onHandlerStateChange}
        >
          <Animated.View style={cmpStyles.box} />
        </PanGestureHandler>
      );
    }
    
    export default function Example() {
      const width = 60;
      const height = 60;
    
      return (
        <View style={styles.scrollView}>
          <Slider color={"red"} width={width} height={height} />
          <Slider color={"blue"} width={width} height={height} />
          <Slider color={"green"} width={width} height={height} />
          <Slider color={"orange"} width={width} height={height} />
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      scrollView: {
        flex: 1,
        marginTop: 120,
      },
    });