3

I am making a quiz. And all options will render in for loop.

Expected Behaviour:

When I click on an option, if it is the wrong answer then it should change the background color to red and it should shake.

Below is the code I am trying.

import React, { Component } from "react";
import {
  View,
  Text,
  TouchableWithoutFeedback,
  Animated,
  Easing
} from "react-native";

class MCQOptions extends Component {
  state = {
    optionSelectedStatus: 0 // 0: unselected, 1: correct, -1: wrong
  };
  constructor() {
    super();
    this.animatedValue = new Animated.Value(0);
    this.shakeAnimValue = new Animated.Value(0);
  }

  onOptionSelected(i) {

    // this.props.showNextQuestion();

    var answer = this.props.answer;
    if (answer == i) {
      this.setState({ optionSelectedStatus: 1 });
      this.showCorrectAnimation();
    } else {
      this.setState({ optionSelectedStatus: -1 });
      this.showErrorAnimation();
    }
  }

  showErrorAnimation() {
    this.shakeAnimValue.setValue(0);
    Animated.timing(this.shakeAnimValue, {
      toValue: 1,
      duration: 300,
      easing: Easing.linear
    }).start();
  }
  showCorrectAnimation() {}

  getOptions() {
    var options = [];
    var optionSelectedStyle = styles.optionUnselected;
    var optionShadowStyle = styles.optionShadow;
    if (this.state.optionSelectedStatus == 1) {
      optionSelectedStyle = styles.optionCorrect;
      optionShadowStyle = null;
    } else if (this.state.optionSelectedStatus == -1) {
      optionSelectedStyle = styles.optionWrong;
      optionShadowStyle = null;
    }

    const marginLeft = this.shakeAnimValue.interpolate({
      inputRange: [0, 0.2, 0.4, 0.6, 0.8, 0.9, 1],
      outputRange: [0, -10, 10, -10, 10, -10, 0]
    });

    for (var i = 0; i < this.props.options.length; i++) {
      options.push(
        <TouchableWithoutFeedback
          onPress={this.onOptionSelected.bind(this, this.props.indexes[i])}
          key={"options_" + i}
        >
          <View style={styles.optionBox}>
            <View style={optionShadowStyle} />
            <Animated.Text
              style={[
                styles.option,
                optionSelectedStyle,
                { marginLeft: marginLeft }
              ]}
              key={"option" + i}
            >
              {this.props.options[i]}
            </Animated.Text>
          </View>
        </TouchableWithoutFeedback>
      );
    }
    return options;
  }

  render() {
    const marginTop = this.animatedValue.interpolate({
      inputRange: [0, 1],
      outputRange: [100, 0]
    });
    const opacity = this.animatedValue.interpolate({
      inputRange: [0, 1],
      outputRange: [0, 1]
    });

    return (
      <Animated.View style={{ marginTop: marginTop, opacity: opacity }}>
        {this.getOptions()}
      </Animated.View>
    );
  }

  // Animations
  componentDidMount() {
    this.slideUpOptionsContainer();
  }
  componentWillReceiveProps() {
    this.slideUpOptionsContainer();
    this.setState({ optionSelectedStatus: 0 });
  }
  slideUpOptionsContainer() {
    this.animatedValue.setValue(0);
    Animated.timing(this.animatedValue, {
      toValue: 1,
      duration: 300,
      easing: Easing.linear
    }).start();
  }
}

const styles = {
  optionBox: {
    margin: 5
  },
  optionsContainer: {
    marginTop: 100
  },
  option: {
    padding: 10,
    textAlign: "center",
    borderRadius: 10,
    overflow: "hidden",
    width: "100%"
  },
  optionUnselected: {
    backgroundColor: "#FFF"
  },
  optionWrong: {
    backgroundColor: "red"
  },
  optionCorrect: {
    backgroundColor: "green"
  },
  optionShadow: {
    backgroundColor: "rgba(255,255,255,0.85)",
    position: "absolute",
    width: "100%",
    height: "100%",
    left: -5,
    top: 5,
    borderRadius: 10
  }
};

export default MCQOptions;

The above code animating(shake) all the options (Which is proper according to the login written), and I am stuck how to make only the clicked option get animated instead all?

Edited:

Parent component with props feed:

class MCQ extends Component<{}> {

render() {
var options = ["yes", "no", "can't define"];
var indexes = [1,2,3];
var answer = 1;

optionsObj = <MCQOptions
        options={options}
        indexes={indexes}
        answer={answer}/>;

return (
      <View style={styles.container} >
        <View style={styles.optionsContainer}>
          {optionsObj}
        </View>
      </View>
    );
}
}
const styles = {
  container: {
    flex: 1,
    backgroundColor: "blue",
    paddingTop: 20,
    justifyContent: 'flex-start',
    padding: 20
  },

};

export default MCQ;

Second EDIT: Trying to simplify problem.

Below is the simplified code with zero props. I want to animate clicked element only.

import React, { Component } from "react";
import {
  View,
  Text,
  TouchableWithoutFeedback,
  Animated,
  Easing
} from "react-native";

class MCQOptions extends Component {
  constructor() {
    super();
    this.shakeAnimValue = new Animated.Value(0);
  }
  showErrorAnimation() {
    this.shakeAnimValue.setValue(0);
    Animated.timing(this.shakeAnimValue, {
      toValue: 1,
      duration: 300,
      easing: Easing.linear
    }).start();
  }

  getOptions() {

    const marginLeft = this.shakeAnimValue.interpolate({
      inputRange: [0, 0.2, 0.4, 0.6, 0.8, 0.9, 1],
      outputRange: [0, -10, 10, -10, 10, -10, 0]
    });
    var options = [];
    for (var i = 0; i < 4; i++) {
      options.push(
        <TouchableWithoutFeedback
          onPress={this.showErrorAnimation.bind(this)}
          key={"options_" + i}
        >
          <View style={styles.optionBox}>
            <Animated.Text style={[
                styles.option,
                { marginLeft: marginLeft }
              ]}
              key={"option" + i}
            >
              {"Option "+i}
            </Animated.Text>
          </View>
        </TouchableWithoutFeedback>
      );
    }
    return options;
  }

  render() {
    return (
      <View style={{ marginTop: 100}}>
        {this.getOptions()}
      </View>
    );
  }

}

const styles = {
  optionBox: {
    margin: 5
  },
  optionsContainer: {
    marginTop: 100
  },
  option: {
    padding: 10,
    textAlign: "center",
    borderRadius: 10,
    overflow: "hidden",
    width: "100%"
  },
  optionUnselected: {
    backgroundColor: "#FFF"
  },
  optionWrong: {
    backgroundColor: "red"
  },
};

export default MCQOptions;
4

1 回答 1

6

由于您想分别为它们设置动画,因此它们不能绑定到同一个Animated对象。您必须使它们成为多个,例如:

例子:

export class App extends Component {
    constructor() {
      super();
      this.getOptions = this.getOptions.bind(this);
      this.originalOptions = [0,1,2,3];
      this.shakeAnimations = this.originalOptions.map( (i) => new Animated.Value(0) );
    }
    showErrorAnimation(i) {
      this.shakeAnimations[i].setValue(0);
      Animated.timing(this.shakeAnimations[i], {
        toValue: 1,
        duration: 300,
        easing: Easing.linear
      }).start();
    }

    getOptions() {

      var options = this.originalOptions.map( (i) => {
            const marginLeft = this.shakeAnimations[i].interpolate({
                inputRange: [0, 0.2, 0.4, 0.6, 0.8, 0.9, 1],
                outputRange: [0, -10, 10, -10, 10, -10, 0]
            });
            return (
            <TouchableWithoutFeedback
                onPress={() => this.showErrorAnimation(i)}
                key={"options_" + i}
            >
                <View style={styles.optionBox}>
                <Animated.Text style={[
                    styles.option,
                    { marginLeft: marginLeft }
                    ]}
                    key={"option" + i}
                >
                    {"Option "+i}
                </Animated.Text>
                </View>
            </TouchableWithoutFeedback>              
          )
      });
      return options;
    }

    render() {
      return (
        <View style={{ marginTop: 100}}>
          {this.getOptions()}
        </View>
      );
    }

  }

  const styles = {
    optionBox: {
      margin: 5
    },
    optionsContainer: {
      marginTop: 100
    },
    option: {
      padding: 10,
      textAlign: "center",
      borderRadius: 10,
      overflow: "hidden",
      width: "100%"
    },
    optionUnselected: {
      backgroundColor: "#FFF"
    },
    optionWrong: {
      backgroundColor: "red"
    },
  };

结果:

在此处输入图像描述

于 2017-12-21T11:29:34.120 回答