react native动画--金币落下&数字滚动效果实现

2,871 阅读1分钟

最近写rn项目遇到动画需求,分享下代码供有需求的前端er参考~
先上最终效果图,如下

开始分解动画!

第一步:获取动画起点位置,首先点击时获取动画开始位置,利用mesure方法获取元素距离屏幕左上角位置;

task-list.js文件

方法参数:task-此条任务信息,object;mount-可领取金币数额 number

  doneCallBack=(task, mount)=>{
    let refname = `goldimg${task.task_id}`;
    let X,Y;
    this.refs[refname].measure((frameX, frameY, frameWidth, frameHeight, pageX, pageY)=>{
      X=pageX;
      Y=pageY;
      this.props._showAnimate(true, pageX, pageY, mount); 
    });
  };

第二步:task-list.js父组件main.js中执行三部分动画;

1 清除上一次动画定时器,并不可见动画;
2 存储位置信息,将金币 如60,拆分成[6, 0],以创建数字滚动动画;
3 执行金币动画,金币背景动画,数字动画

方法参数:status- boolean 是否需要执行动画;X-number 距离屏幕左侧距离;Y-number 距离屏幕顶部距离;mount-number 可领取金币数额;

_showAnimate=(status, X, Y ,mount)=>{
    this.timer && clearTimeout(this.timer); 
    this.state.isShowAnim &&         this.setState({isShowAnim:false}); //上两行防止多次点击出现混乱
    this.setState({
      clickedPositionX:X,
      clickedPositionY:Y,
      mount:mount.toString().split(''),
      isShowAnim:true,
    }, ()=> {
      if(status){ 
      this.goldAnimate._startGoldAnimate();// 执行金币动画
        this.goldBgAnimate._startGoldAnimate(); // 执行金币背景动画
        this.goldnumRunAnimate._startAnimate(); // 执行数字滚动动画
        this._getTaskList(); // 重新获取任务数据
      }
    });
    // 4s后去掉领取奖励动画弹窗
    this.timer = setTimeout(()=>{
      this.setState({
        isShowAnim:false
      })
    }, 4000)
  };

上金币动画代码:gold-animate.js

使用rn内置Animated模块,
三种动画效果
1 spring - 弹簧物理模型,
2 timing - easing函数(大多数情况下使用);
3 decay - 指定的初始速度开始,然后速度变慢至停下;
动画先向上(x)弹40,然后向右下(x,y)同时移动
translateYVal是new Animated.Value(0)动画初始值,为0;
useNativeDriver:true 使用原生驱动加载动画,可以提高动画流畅,但是对css动画部分支持;
interpolate插值函数,具体参考官方文档;
clickedPositionX 点击发生时距离屏幕左侧;
finalPosY 金币背景的Y位置

js部分

_startGoldAnimate=()=>{ 
    let spring = Animated.spring, timing = Animated.timing, parallel = Animated.parallel;
    spring(
        this.state.translateYVal,
        {
          toValue:1,
          duration:500,
          friction:3, 
          tension:50,
          useNativeDriver:true
        }
    ).start();
    parallel([
      timing(
          this.state.translateXVal,
          {
            toValue:1,
            duration:300,
            useNativeDriver:true,
            delay:600
          }
      ),
      timing(
          this.state.translateSecYVal,
          {
            toValue:1,
            duration:300,
            useNativeDriver:true,
            delay:600
          }
      )
    ]).start();
  };

react部分

return (
        <Animated.View
            style={{
              position:'absolute',
              zIndex:10000,
              height:'100%',
              width:'100%',
              left:clickedPositionX - 5,
              top:clickedPositionY - 5,     
              transform:[
                {
                  translateY:translateYVal.interpolate({
                    inputRange: [0, 0.7, 1],
                    outputRange: [0, -40, 0]
                  })
                },
              ]
            }}
        >
          <Animated.Image
              style={{
                height:22,
                width:22,
                transform:[
                  {
   translateX:translateXVal.interpolate({
                      inputRange: [0, 1],
                      outputRange: [0, Utils.windowSize.width - clickedPositionX - 89]
                    })
                  },
                  {      translateY:translateSecYVal.interpolate({
                      inputRange: [0, 1],
                      outputRange: [0, finalPosY - clickedPositionY + 15 ]
                    })
                  },     
                ],
              }}
              source={require('resource/newtask/animategold.png')}
          >
          </Animated.Image>
        </Animated.View>
    )

代码继续,按顺序金币背景动画
gold-bg-animate.js
动画部分:背景向上Y,移动60

_startGoldAnimate = () => {
    Animated.timing(
        this.state.animValue,
        {
          toValue:1,
          duration:1000,
          useNativeDriver:true
        }
    ).start();
  }

react部分

return (
      <View
          ref={(view) => this.goldNumBg = view}
          style={styles.container}
      >
        <Animated.Image
            style={{
              position:'absolute',         
              bottom:-60,
              right:0,
              height:55,
              width:104,
              transform:[
                {
                  translateY:animValue.interpolate({
                    inputRange: [0, 1],
                    outputRange: [0,-60]
                  })
                }
              ],
            }}
            source={require('resource/newtask/animate_gold_bg.png')}
        >
        </Animated.Image>
      </View>
    )

接下来最后一个动画,数字滚动动画
文件goldnum-run-animate.js
动画部分,第一个透明度从0到1;每个数字依次开始动画,注意里面的delay属性;

_startAnimate = () => {   
    Animated.timing(
        this.state.opacityAnim,
        {
          toValue:1,
          duration:1000,
        }
    ).start();
    this.state.animAry.forEach((item, index) => {
      Animated.timing(
          item.value,
          {
            toValue:1,
            duration:(Number(item.num)+1) * 200,
            delay:index * 400
          }
      ).start();
    });
  };

animAry为创建出数组,为每个数字添加动画初始值;

let numAry = this.state.mount;
    let animAry = [];
    for (let i = 0; i < numAry.length; i++) {
      animAry.push({
        num: numAry[i],
        name: `animValue${i}`,
        value: new Animated.Value(0),
      })
    }

react部分:mount就是上文提到过的数字改造的数组,60=》[6, 0]

return (
        <View style={styles.numContainer}>
          <Animated.Text
              style={[
                styles.goldText,
                styles.addMark,
                {
                  opacity:opacityAnim.interpolate({
                    inputRange:[0, 1],
                    outputRange:[0, 1]
                  })
                }
              ]}
          >
            +
          </Animated.Text>
          {
            mount.map((item,index) => {
              return (
                  <Animated.View
                      key={index}
                      style={[styles.textcon,{
                        height:(Number(item)+1) * 30,
                        top:this.createAnimate(item, index)
                      }]}>
                    {
                      this._createSingleNum(item)
                    }
                  </Animated.View>
              )
            })
          }
        </View>
    )

_createSingleNum(item)方法如下:

参数 item-传入的数字,number;

_createSingleNum = (num) => {
    let ary =[];
    for (let i =0; i <= num; i++) {
      ary.push(i)
    }
    return (
        <View>
          {
            ary.map((item,index)=>{
                return (<Text key={index} style={[styles.goldText]}>{item}</Text>)
            })
          }
        </View>
    )
  };

创建的结果如下,每个数字列有个动画,从0-5滚动;

<View>
  <Text>0<Text>   
  <Text>1<Text>
  <Text>2<Text>
  <Text>3<Text>
  <Text>4<Text>
  <Text>5<Text>
<View>

createAnimate()方法,
参数:it-数字,number 例如:5 ind-索引值,number,如 0;
为数组中每个数字,创建动画,最终通过top值改变行程动画

createAnimate = (it, ind) => {
    let propName = `animValue${ind}`;
    return (
 this.state.animAry[ind].value.interpolate({
          inputRange: [0, 1],
          outputRange: [30,-30 * it]
        })
    )
  };

最终三个动画实现文中开头动画。就酱吧~