reactjsreact-nativereact-native-reanimated

React-native Animate between a number and a string value


I'm trying to update an old code. Something is going wrong in the code I shared, and I can't figure it out because I'm not fully proficient in the reanimated library. If anyone can help, I would appreciate it. I'm creating a voting screen, and based on the touched stars on this screen, I will change the visible facial expression in the photo I share.

RatingScreen.js

import React from 'react';
import { TouchableOpacity, StyleSheet, Text, View, Dimensions } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import Animated, { Easing, useSharedValue, withSpring, useAnimatedStyle } from 'react-native-reanimated';
import * as flubber from 'flubber';
import Svg, { G, Path } from 'react-native-svg';
import Icon1 from 'react-native-vector-icons/Feather';

const { width, height } = Dimensions.get('screen');

const fill = "#333";
const types = ['upset', 'sad', 'neutral', 'smile', 'excited'];
const PATHS = {
  "upset": "M141.5 132.55C140.92 75.87 120.92 48.22 81.5 49.63C42.09 51.03 22.09 78.67 21.5 132.55L141.5 132.55Z",
  "sad": "M122.32 87.65C121.94 68.08 108.83 58.53 83 59.02C57.17 59.5 44.06 69.04 43.68 87.65L122.32 87.65Z",
  "neutral": "M38.02 58.05L99.77 40.83L102.99 52.35L41.23 69.57L38.02 58.05Z",
  "smile": "M122.32 64.68C121.94 84.25 108.83 93.79 83 93.31C57.17 92.82 44.06 83.28 43.68 64.68L122.32 64.68Z",
  "excited": "M142.99 49.74C142.4 106.42 122.4 134.06 82.99 132.66C43.57 131.26 23.57 103.62 22.99 49.74L142.99 49.74Z",
  "left-eye": "M30.43 16.78C30.43 24.39 24.29 30.57 16.72 30.57C9.15 30.57 3 24.39 3 16.78C3 9.18 9.15 3 16.72 3C24.29 3 30.43 9.18 30.43 16.78Z",
  "right-eye": "M162.99 16.79C162.99 24.4 156.84 30.57 149.27 30.57C141.7 30.57 135.56 24.4 135.56 16.79C135.56 9.18 141.7 3.01 149.27 3.01C156.84 3.01 162.99 9.18 162.99 16.79Z"
};

const GRADIENTS = {
  "upset": ["rgb(231, 97, 97)", "rgb(236, 49, 49)"],
  "sad": ["rgb(247,152,48)", "rgb(231, 97, 97)"],
  "neutral": ["rgb(243, 189, 67)", "rgb(203,96,32)"],
  "smile": ["rgb(238,172,77)", "rgb(187, 230, 95)"],
  "excited": ["rgb(95,230,118)", "rgb(46, 232, 78)"],
};

class RatingScreen extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      path: PATHS.neutral,
      background: GRADIENTS.neutral,
      type: "neutral",
      index: -1,
      progress: new Animated.Value(0),
    };
  }

  interpolatePaths = (type, index) => {
    const interpolator = flubber.interpolate(this.state.path, PATHS[type], { maxSegmentLength: 2 });
    Animated.timing(this.state.progress, {
      toValue: 1,
      duration: 400,
      easing: Easing.inOut(Easing.ease),
      useNativeDriver: false,
    }).start(() => {
      this.setState({
        path: interpolator(1),
        background: GRADIENTS[type],
        type,
        index,
      });
      this.state.progress.setValue(0);
    });
  };

  render() {
    const animatedStyle = {
      d: this.state.path,
      background: this.state.background,
    };

    return (
      <LinearGradient colors={this.state.background} style={styles.gradient}>
        <View style={styles.headings}>
          <Text style={styles.heading}>Please rate your feedback</Text>
        </View>
        <View style={styles.svgWrapper}>
          <Svg width={width} height={height / 3} viewBox="0 0 166 136" style={styles.svgContainer}>
            <G>
              <Path d={PATHS["left-eye"]} fill={fill} />
              <Animated.View style={animatedStyle}>
                <Path d={PATHS["right-eye"]} fill={fill} />
              </Animated.View>
            </G>
          </Svg>
          <View style={styles.feedbackWrapper}>
            {types.map((type, index) => (
              <TouchableOpacity key={type} onPress={() => this.interpolatePaths(type, index)}>
                <Icon1 name={this.state.index >= index ? "star" : "star"} size={32} color="#fff" />
              </TouchableOpacity>
            ))}
          </View>
        </View>
      </LinearGradient>
    );
  }
}

const styles = StyleSheet.create({
  gradient: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  feedbackWrapper: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-around',
    height: 60,
    borderRadius: 30,
    backgroundColor: "rgba(0, 0, 0, 0.05)",
    width: width * .9
  },
  headings: {
    flex: .3,
    justifyContent: "center"
  },
  heading: {
    color: "#fff",
    fontSize: 42,
    lineHeight: 42,
    fontWeight: '700'
  },
  body: {
    color: "#fff",
    fontFamily: "Menlo"
  },
  svgContainer: {
    marginBottom: 40
  },
  svgWrapper: {
    flex: .6,
    alignItems: "center",
    justifyContent: "center"
  }
});

export default RatingScreen;

old code

import React from 'react';
import { TouchableOpacity, StyleSheet, Text, View, Dimensions } from 'react-native';
import Gradient from 'react-native-css-gradient';
import { interpolate } from 'flubber';
import { tween, easing } from 'popmotion';
import { Svg } from 'expo'
import { AntDesign } from '@expo/vector-icons';

const { width, height } = Dimensions.get('screen');
const { Path, G } = Svg;

const fill = "#333";
const types = ['upset', 'sad', 'neutral', 'smile', 'excited'];
const PATHS = {
  "upset": "M141.5 132.55C140.92 75.87 120.92 48.22 81.5 49.63C42.09 51.03 22.09 78.67 21.5 132.55L141.5 132.55Z",
  "sad": "M122.32 87.65C121.94 68.08 108.83 58.53 83 59.02C57.17 59.5 44.06 69.04 43.68 87.65L122.32 87.65Z",
  "neutral": "M38.02 58.05L99.77 40.83L102.99 52.35L41.23 69.57L38.02 58.05Z",
  "smile": "M122.32 64.68C121.94 84.25 108.83 93.79 83 93.31C57.17 92.82 44.06 83.28 43.68 64.68L122.32 64.68Z",
  "excited": "M142.99 49.74C142.4 106.42 122.4 134.06 82.99 132.66C43.57 131.26 23.57 103.62 22.99 49.74L142.99 49.74Z",
  "left-eye": "M30.43 16.78C30.43 24.39 24.29 30.57 16.72 30.57C9.15 30.57 3 24.39 3 16.78C3 9.18 9.15 3 16.72 3C24.29 3 30.43 9.18 30.43 16.78Z",
  "right-eye": "M162.99 16.79C162.99 24.4 156.84 30.57 149.27 30.57C141.7 30.57 135.56 24.4 135.56 16.79C135.56 9.18 141.7 3.01 149.27 3.01C156.84 3.01 162.99 9.18 162.99 16.79Z"
};
const GRADIENTS = {
  "upset": "linear-gradient(to bottom, rgb(231, 97, 97), rgb(236, 49, 49))",
  "sad": "linear-gradient(to bottom, rgb(247,152,48), rgb(231, 97, 97))",
  "neutral": "linear-gradient(to bottom, rgb(243, 189, 67), rgb(203,96,32))",
  "smile": "linear-gradient(to bottom, rgb(238,172,77), rgb(187, 230, 95))",
  "excited": "linear-gradient(to bottom, rgb(95,230,118), rgb(46, 232, 78))",
};

export default class App extends React.Component {
  state = {
    path: PATHS.neutral,
    background: GRADIENTS.neutral,
    type: "neutral",
    index: -1
  }

  interpolatePaths = (type, index) => {
    const interpolator = interpolate(this.state.path, PATHS[type], { maxSegmentLength: 2 });
    tween({
      duration: 400,
      ease: easing.easeInOut,
      from: { i: 0, background: this.state.background },
      to: { i: 1, background: GRADIENTS[type] }
    })
      .pipe(({ i, background }) => ({ path: interpolator(i), background }))
      .start(({ path, background }) => {
        this.setState({
          path, background, type, index
        })
      })
  }

  render() {
    return (
      <Gradient gradient={this.state.background} style={styles.gradient}>
        <View style={styles.headings}>
          <Text style={styles.heading}>Please rate your feedback</Text>
          <Text style={styles.body}>Do let us know your thoughts.</Text>
          <Text style={styles.body}>Your feedback matters!</Text>
        </View>
        <View style={styles.svgWrapper}>
          <Svg width={width} height={height / 3} viewBox="0 0 166 136" style={styles.svgContainer}>
            <G>
              <Path d={PATHS["left-eye"]} fill={fill} />
              <Path d={this.state.path} fill={fill} />
              <Path d={PATHS["right-eye"]} fill={fill} />
            </G>
          </Svg>
          <View style={styles.feedbackWrapper}>
            {types.map((type, index) => (
              <TouchableOpacity key={type} onPress={() => this.interpolatePaths(type, index)}>
                <AntDesign name={this.state.index >= index ? "star" : "staro"} size={32} color="#fff" />
              </TouchableOpacity>
            ))}
          </View>
        </View>
      </Gradient>
    );
  }
}

const styles = StyleSheet.create({
  gradient: {
    width,
    height,
    alignItems: "center",
  },
  feedbackWrapper: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-around',
    height: 60,
    borderRadius: 30,
    backgroundColor: "rgba(0, 0, 0, 0.05)",
    width: width * .9
  },
  headings: {
    flex: .4,
    justifyContent: "center"
  },
  heading: {
    color: "#fff",
    fontSize: 42,
    lineHeight: 42,
    fontWeight: '700'
  },
  body: {
    color: "#fff",
    fontFamily: "Menlo"
  },
  svgContainer: {
    marginBottom: 40
  },
  svgWrapper: {
    flex: .6,
    alignItems: "center",
    justifyContent: "center"
  }
});

I tried old libraries but it didn't work

I tried like this but it didn't work and i don't know how can i use popmotion

import React from 'react';
import { TouchableOpacity, StyleSheet, Text, View, Dimensions } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import * as flubber from 'flubber';
import { animate, easeInOut } from 'popmotion';
import Svg, { G, Path } from 'react-native-svg';
import Icon1 from 'react-native-vector-icons/Feather';

const { width, height } = Dimensions.get('screen');

const fill = "#333";
const types = ['upset', 'sad', 'neutral', 'smile', 'excited'];
const PATHS = {
  "upset": "M141.5 132.55C140.92 75.87 120.92 48.22 81.5 49.63C42.09 51.03 22.09 78.67 21.5 132.55L141.5 132.55Z",
  "sad": "M122.32 87.65C121.94 68.08 108.83 58.53 83 59.02C57.17 59.5 44.06 69.04 43.68 87.65L122.32 87.65Z",
  "neutral": "M38.02 58.05L99.77 40.83L102.99 52.35L41.23 69.57L38.02 58.05Z",
  "smile": "M122.32 64.68C121.94 84.25 108.83 93.79 83 93.31C57.17 92.82 44.06 83.28 43.68 64.68L122.32 64.68Z",
  "excited": "M142.99 49.74C142.4 106.42 122.4 134.06 82.99 132.66C43.57 131.26 23.57 103.62 22.99 49.74L142.99 49.74Z",
  "left-eye": "M30.43 16.78C30.43 24.39 24.29 30.57 16.72 30.57C9.15 30.57 3 24.39 3 16.78C3 9.18 9.15 3 16.72 3C24.29 3 30.43 9.18 30.43 16.78Z",
  "right-eye": "M162.99 16.79C162.99 24.4 156.84 30.57 149.27 30.57C141.7 30.57 135.56 24.4 135.56 16.79C135.56 9.18 141.7 3.01 149.27 3.01C156.84 3.01 162.99 9.18 162.99 16.79Z"
};

const GRADIENTS = {
  "upset": ["rgb(231, 97, 97)", "rgb(236, 49, 49)"],
  "sad": ["rgb(247,152,48)", "rgb(231, 97, 97)"],
  "neutral": ["rgb(243, 189, 67)", "rgb(203,96,32)"],
  "smile": ["rgb(238,172,77)", "rgb(187, 230, 95)"],
  "excited": ["rgb(95,230,118)", "rgb(46, 232, 78)"],
};



export default class RatingScreen extends React.Component {
  state = {
    path: PATHS.neutral,
    background: GRADIENTS.neutral,
    type: "neutral",
    index: -1
  }

  interpolatePaths = (type, index) => {
    const interpolator = flubber.interpolate(this.state.path, PATHS[type], { maxSegmentLength: 2 });
    animate({
      duration: 400,
      ease: easeInOut,
      from: { i: 0, background: this.state.background },
      to: { i: 1, background: GRADIENTS[type] }
    })
      .pipe(({ i, background }) => ({ path: interpolator(i), background }))
      .start(({ path, background }) => {
        this.setState({
          path, background, type, index
        })
      })
  }

  render() {
    return (
      <LinearGradient colors={this.state.background} style={styles.gradient}>
        <View style={styles.headings}>
          <Text style={styles.heading}>Please rate your feedback</Text>
        </View>
        <View style={styles.svgWrapper}>
          <Svg width={width} height={height / 3} viewBox="0 0 166 136" style={styles.svgContainer}>
            <G>
              <Path d={PATHS["left-eye"]} fill={fill} />
              <Path d={this.state.path} fill={fill} />
              <Path d={PATHS["right-eye"]} fill={fill} />
            </G>
          </Svg>
          <View style={styles.feedbackWrapper}>
            {types.map((type, index) => (
              <TouchableOpacity key={type} onPress={() => this.interpolatePaths(type, index)}>
                <Icon1 name={this.state.index >= index ? "star" : "star"} size={32} color="#fff" />
              </TouchableOpacity>
            ))}
          </View>
        </View>
      </LinearGradient>
    );
  }
}

const styles = StyleSheet.create({
  gradient: {
    width,
    height,
    alignItems: "center",
  },
  feedbackWrapper: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-around',
    height: 60,
    borderRadius: 30,
    backgroundColor: "rgba(0, 0, 0, 0.05)",
    width: width * .9
  },
  headings: {
    flex: .3,
    justifyContent: "center"
  },
  heading: {
    color: "#fff",
    fontSize: 42,
    lineHeight: 42,
    fontWeight: '700'
  },
  body: {
    color: "#fff",
    fontFamily: "Menlo"
  },
  svgContainer: {
    marginBottom: 40
  },
  svgWrapper: {
    flex: .6,
    alignItems: "center",
    justifyContent: "center"
  }
});

Solution

  • Due to issues with the libraries I was using, I switched to different libraries and abandoned changing background colors with linear. By utilizing some simple Reanimated functions, I achieved success with a straightforward code.

    import React from 'react';
    import { TouchableOpacity, StyleSheet, View, Dimensions, Text, TouchableWithoutFeedback } from 'react-native';
    import Svg, { G, Path } from 'react-native-svg';
    import Icon from 'react-native-vector-icons/AntDesign';
    import Animated, { useSharedValue, useAnimatedProps, withTiming, Easing } from 'react-native-reanimated';
    import { parse, interpolatePath } from 'react-native-redash';
    import LinearGradient from 'react-native-linear-gradient';
    
    const { width, height } = Dimensions.get('screen');
    const fill = "white";
    const lefteye = "M30.43 16.78C30.43 24.39 24.29 30.57 16.72 30.57C9.15 30.57 3 24.39 3 16.78C3 9.18 9.15 3 16.72 3C24.29 3 30.43 9.18 30.43 16.78Z";
    const righteye = "M162.99 16.79C162.99 24.4 156.84 30.57 149.27 30.57C141.7 30.57 135.56 24.4 135.56 16.79C135.56 9.18 141.7 3.01 149.27 3.01C156.84 3.01 162.99 9.18 162.99 16.79Z";
    
    const App = ({ }) => {
      const AnimatedPath = Animated.createAnimatedComponent(Path);
      const progress = useSharedValue(0);
      const [rating, setRating] = React.useState(0);
    
      const expressions = [
        parse("M141.5 132.55C140.92 75.87 120.92 48.22 81.5 49.63C42.09 51.03 22.09 78.67 21.5 132.55L141.5 132.55Z"),
        parse("M122.32 87.65C121.94 68.08 108.83 58.53 83 59.02C57.17 59.5 44.06 69.04 43.68 87.65L122.32 87.65Z"),
        parse("M38.02 58.05L99.77 40.83L102.99 52.35L41.23 69.57L38.02 58.05Z"),
        parse("M122.32 64.68C121.94 84.25 108.83 93.79 83 93.31C57.17 92.82 44.06 83.28 43.68 64.68L122.32 64.68Z"),
        parse("M142.99 49.74C142.4 106.42 122.4 134.06 82.99 132.66C43.57 131.26 23.57 103.62 22.99 49.74L142.99 49.74Z"),
      ];
    
      const animatedProps = useAnimatedProps(() => {
        return {
          d: interpolatePath(progress.value, [0, 1, 2, 3, 4], expressions),
        };
      });
    
      const handleStarPress = (value) => {
        setRating(value);
        progress.value = withTiming(value, { duration: 530, easing: Easing.bezier(0.33, 1, 0.68, 1) });
      };
    
      const renderStar = (value) => (
        <TouchableOpacity onPress={() => handleStarPress(value)}>
          <Icon name={rating >= value ? "star" : "staro"} size={32} color={fill} />
        </TouchableOpacity>
      );
    
      return (
        <LinearGradient style={styles.container} colors={['#1d4c1e', '#151618']}>
          <View style={styles.svgWrapper}>
            <View style={{ width: width, alignItems: 'center' }}>
              <Svg width={width} height={height / 4} viewBox="0 0 166 136" style={styles.svgContainer}>
                <G>
                  <Path d={lefteye} fill={fill} />
                  <AnimatedPath animatedProps={animatedProps} fill={fill} />
                  <Path d={righteye} fill={fill} />
                </G>
              </Svg>
            </View>
          </View>
    
          <View style={styles.feedbackWrapper}>
            {renderStar(0)}
            {renderStar(1)}
            {renderStar(2)}
            {renderStar(3)}
            {renderStar(4)}
          </View>
        </LinearGradient>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        alignItems: "center",
        justifyContent: 'center',
        paddingBottom: 40,
        backgroundColor: 'white'
      },
      feedbackWrapper: {
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-around',
        width: width * .9,
        position: 'absolute',
        bottom: 50,
      },
      headings: {
        justifyContent: "center"
      },
      heading: {
        color: "white",
        lineHeight: 20,
        fontFamily: "Poppins-SemiBold",
        fontSize: 16,
      },
      svgContainer: {
        marginBottom: 40
      },
      svgWrapper: {
        alignItems: "center",
        justifyContent: "center"
      },
    });
    
    export default App;