0

I am trying to run a few simple animations using react-native-animatable library. (But I believe the question should be generic to any react animations so adding other tags as well.)

The problem is, in the first time, the image animates just as expected. But when aimed to start second animation animation with the gesture, the image translation starts from its original coordinates.

A search yielt, in Android development (which is obviously not my case) there seems a method, setFillAfter which sets the coordinate after the animation.

My question is, how to set the location (left / top values for example) to the final translated point so that consecutive animation starts from the point the previous translation left.

The expo snack for below code block is here.

import * as React from 'react';
import { Image, StyleSheet, ImageBackground } from 'react-native';

import * as Animatable from 'react-native-animatable';
import { PanGestureHandler, State } from 'react-native-gesture-handler';

import testImg from './test.png';
import backImg from './back.png';

export default class App extends React.Component {
    onTestMove(event) {
        this.testAnimRef.transitionTo({
            translateX: event.nativeEvent.translationX,
            translateY: event.nativeEvent.translationY,
        }, 0);

    }
    render() {
        return (
            <ImageBackground source={backImg} style={{ flex: 1 }} >
                <PanGestureHandler
                    key={`test`}
                    onGestureEvent={(e) => { this.onTestMove(e) }}
                    onHandlerStateChange={e => { }}
                >
                    <Animatable.View style={styles._animatable_view}
                        ref={((ref) => { this.testAnimRef = ref }).bind(this)}
                        useNativeDriver={true}
                    >
                        <Image source={testImg} style={styles._image} />
                    </Animatable.View>
                </PanGestureHandler>
            </ImageBackground>
        );
    }
}

const styles = StyleSheet.create({
    _image: {
        width: 50,
        height: 25,
        resizeMode: 'contain',
        backgroundColor: 'black',
        borderColor: 'gainsboro',
        borderWidth: 2,
    },
    _animatable_view: {
        position: "absolute",
        top: 200,
        left: 100,
    },
});

Mehmet Kaplan
  • 1,723
  • 2
  • 20
  • 43

1 Answers1

2

I had the same problem trying to move around some cards in a view, and upon further dragging, they would reset to their origin.

My theory is/was that while the translated view would have its x / y coordinates translated, this would not apply to the parent of that view, and so the animated event passed from that component would initially have the original coordinates (nuke me if I'm wrong here)

So my solution was to keep an initial offset value in state, and maintain this every time the user releases the dragged motion

  _onHandleGesture: any
  constructor(props: OwnProps) {
    super(props)
    this.state = {
      animationValue: new Animated.ValueXY({ x: 0, y: 0 }),
      initialOffset: { x: 0, y: 0 },
    }
    this._onHandleGesture = (e: PanGestureHandlerGestureEvent) => {
      this.state.animationValue.setValue({
        x: e.nativeEvent.translationX + this.state.initialOffset.x, <- add initial offset to coordinates passed
        y: e.nativeEvent.translationY + this.state.initialOffset.y,
      })
    }
  }

  _acceptCard = (cardValue: number) => {
    const { targetLocation, onAccept } = this.props

    const { x, y } = targetLocation

    onAccept(cardValue)

    Animated.spring(this.state.animationValue, {
  // Some animation here
    }).start(() => {
      this.setState({ initialOffset: targetLocation }) // <- callback to set state value for next animation start
    })
  }

and the render method

  <PanGestureHandler
        onHandlerStateChange={this.onPanHandlerStateChange}
        onGestureEvent={this._onHandleGesture}
        failOffsetX={[-xThreshold, xThreshold]}
      >
        <Animated.View
          style={{
            position: "absolute",
            left: 0,
            top: 0,
            transform: [{ translateX: this.state.animationValue.x }, { translateY: this.state.animationValue.y }],
          }}
        >
          <CardTile size={size} content={content} layout={layout} backgroundImage={backgroundImage} shadow={shadow} />
        </Animated.View>
      </PanGestureHandler>

This example is based on the react-native-gesture-handler library, but the concept should apply to other solutions. Dont know if this way is advisable, though it is functional.

Hope this helps!