javascriptreact-nativeanimationpanresponder

Panresponder or Animated.View doesnt work when the item to animate is in front of a scrollable view


Hey everyone :) Should be a function for a navigation avatar which sticks to the closest corner. While coding I used a simple circle as a placeholder. The problem is that the following code works perfectly fine when imported to another screen, but when I replace <View style={styles.circle} /> with an image <Image source={require("../assets/dude.png")} resizeMode="contain" style={{width: 180, height: 240,}}/> it doesnt work anymore? Like I can see the image, but the animations work extremely buggy and it just goes anywhere, nothing like it's supposed to do?

I tried it also with Animated.Image instead of the view and giving it all the parameters, still no change. The weird thing is that the Image works perfectly fine if I were to run this code as a screen itself, but when I import it only the circle view works, not the image?

EDIT: Just found the issue: if the Animated.Image is in front of a Scrollable View, even if it isn't part of that View, it bugs. If I replace the image with anything else (like a Box), it works fine, only the image bugs in that manner :) which leads me to my next question: How can I fix that?

so this is my code:

import React from "react";
import {
  StyleSheet,
  View,
  Dimensions,
  Animated,
  PanResponder,
  Image,
} from "react-native";

export default class Avatar extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      pan: new Animated.ValueXY(),
      screenMeasurements: {
        width: Screen.width / 2,
        height: Screen.height / 2,
      },
    };

    this.panResponder = PanResponder.create({
      onStartShouldSetPanResponder: () => true,

      onPanResponderGrant: () => {
        this.state.pan.setOffset({
          x: this.state.pan.x._value,
          y: this.state.pan.y._value,
        });
      },

      onPanResponderMove: Animated.event([
        null,
        {
          dx: this.state.pan.x,
          dy: this.state.pan.y,
        },
      ]),

      onPanResponderRelease: (e, gesture) => {
        if (this.whichField(gesture) == 1) {
          console.log("top left");
          this.state.pan.flattenOffset();
          Animated.spring(this.state.pan, {
            toValue: {
              x: (Screen.width * 0.5 - 90) * -1,
              y: (Screen.height * 0.5 - 120) * -1,
            },
          }).start();
        } else if (this.whichField(gesture) == 2) {
          console.log("top right");
          this.state.pan.flattenOffset();
          Animated.spring(this.state.pan, {
            toValue: {
              x: Screen.width * 0.5 - 90,
              y: (Screen.height * 0.5 - 120) * -1,
            },
          }).start();
        } else if (this.whichField(gesture) == 3) {
          console.log("bottom left");
          this.state.pan.flattenOffset();
          Animated.spring(this.state.pan, {
            toValue: {
              x: (Screen.width * 0.5 - 90) * -1,
              y: Screen.height * 0.5 - 150,
            },
          }).start();
        } else {
          console.log("bottom right");
          this.state.pan.flattenOffset();
          Animated.spring(this.state.pan, {
            toValue: {
              x: Screen.width * 0.5 - 90,
              y: Screen.height * 0.5 - 150,
            },
          }).start();
        }
      },
    });
  }

  whichField(gesture) {
    var sm = this.state.screenMeasurements;
    let field;
    {
      gesture.moveY < sm.height && gesture.moveX < sm.width
        ? (field = 1)
        : gesture.moveY < sm.height && gesture.moveX > sm.width
        ? (field = 2)
        : gesture.moveY > sm.height && gesture.moveX < sm.width
        ? (field = 3)
        : (field = 4);
    }

    return field;
  }

  render() {
    return (
      <View style={styles.draggableContainer}>
        <Animated.View
          style={[this.state.pan.getLayout()]}
          {...this.panResponder.panHandlers}
        >
          <View style={styles.circle} />
        </Animated.View>
      </View>
    );
  }
}

let Screen = Dimensions.get("screen");

let CIRCLE_RADIUS = 45;
const styles = StyleSheet.create({
  text: {
    marginTop: 25,
    marginLeft: 5,
    marginRight: 5,
    textAlign: "center",
    color: "#fff",
  },
  draggableContainer: {
    position: "absolute",
    top: Screen.height / 2 - CIRCLE_RADIUS,
    left: Screen.width / 2 - CIRCLE_RADIUS,
  },
  circle: {
    backgroundColor: "#1abc9c",
    width: CIRCLE_RADIUS * 2,
    height: CIRCLE_RADIUS * 2,
    borderRadius: CIRCLE_RADIUS,
  },
});

Solution

  • Check out this similar animation of badge Above ScrollView.

    You need to place the Image inside an Animated View.

    Example code:

    import React, {Component} from 'react';
    import {
      StyleSheet,
      View,
      PanResponder,
      Animated,
      Dimensions,
      ScrollView,
      Image,
    } from 'react-native';
    
    const {height, width} = Dimensions.get('screen');
    
    export default class SampleApp extends Component {
      constructor() {
        super();
        this._animatedValue = new Animated.ValueXY({x: 20, y: 20});
        this._value = {x: 20, y: 20};
        this._animatedValue.addListener((value) => (this._value = value));
        this._panResponder = PanResponder.create({
          onMoveShouldSetResponderCapture: () => true,
          onMoveShouldSetPanResponderCapture: () => true,
          onPanResponderGrant: (e, gestureState) => {
            this._animatedValue.setOffset({x: this._value.x, y: this._value.y});
            this._animatedValue.setValue({x: 0, y: 0});
          },
          onPanResponderMove: Animated.event([
            null,
            {dx: this._animatedValue.x, dy: this._animatedValue.y},
          ]),
          onPanResponderRelease: (e, gesture) => {
            this._animatedValue.flattenOffset();
            if (this.whichField(gesture) == 1) {
              Animated.spring(this._animatedValue, {
                toValue: {x: 20, y: 20},
              }).start();
            } else if (this.whichField(gesture) == 2) {
              Animated.spring(this._animatedValue, {
                toValue: {x: width - 120, y: 20},
              }).start();
            } else if (this.whichField(gesture) == 3) {
              Animated.spring(this._animatedValue, {
                toValue: {x: 20, y: height - 150},
              }).start();
            } else {
              Animated.spring(this._animatedValue, {
                toValue: {x: width - 120, y: height - 150},
              }).start();
            }
          },
        });
      }
    
      whichField(gesture) {
        var sm = {height, width};
        let field;
        {
          gesture.moveY < sm.height / 2 && gesture.moveX < sm.width / 2
            ? (field = 1)
            : gesture.moveY < sm.height / 2 && gesture.moveX > sm.width / 2
            ? (field = 2)
            : gesture.moveY > sm.height / 2 && gesture.moveX < sm.width / 2
            ? (field = 3)
            : (field = 4);
        }
        return field;
      }
    
      render() {
        return (
          <View style={styles.container}>
            <ScrollView>
              {['', '', '', '', '', ''].map(() => (
                <View style={styles.scrollItem} />
              ))}
            </ScrollView>
            <Animated.View
              style={[
                styles.box,
                {
                  transform: [
                    {translateX: this._animatedValue.x},
                    {translateY: this._animatedValue.y},
                  ],
                },
              ]}
              {...this._panResponder.panHandlers}>
              <Image
                source={{
                  uri:
                    'https://pluspng.com/img-png/user-png-icon-male-user-icon-512.png',
                }}
                style={StyleSheet.absoluteFill}
              />
            </Animated.View>
          </View>
        );
      }
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
      },
      box: {
        width: 100,
        height: 100,
        borderRadius: 50,
        backgroundColor: '#fff',
        position: 'absolute',
      },
      scrollItem: {
        height: 300,
        width: '100%',
        backgroundColor: 'grey',
        marginBottom: 10,
      },
    });
    

    Check in Expo

    I hope it will help you.