[React Native]动画-LayoutAnimation

Posted by zhuhf on July 5, 2016

实现动画的几种方式:

  • requestAnimationFrame
  • setNativeProps
  • LayoutAnimation
  • Animated

本篇文章我们会介绍前三种方式,以及它们的区别。下一篇文章会介绍高级API—Animated的用法。

requestAnimationFrame 如果不使用任何动画API,我会想到一种简单粗暴的方式来实现动画效果—通过修改state不断改变视图的样式。

我们来个简单的示例:

class Demo9 extends Component {
  constructor(props) {
      super(props);
      this.state = {
          width: 100,
          height: 100
      };
  }
  startAnimation() {
      var count = 0;
      while (++count < 50) {
          requestAnimationFrame(() = >{
              this.setState({
                  width: this.state.width + 1,
                  height: this.state.height + 1
              });
          });
      }
  }
  render() {
      return (
         <View style = {styles.container}>
           <Image source = { require('./icon.jpg') }
              style=/>
           <TouchableOpacity style={styles.instructions} onPress={()=>this.startAnimation()}>
              <Text style=>Press me!</Text>
           </TouchableOpacity>
          </View>
      );
  }
}

效果图如下:

preview1.gif

这个方式实现的动画有几个问题:

1、实现方式是通过不断销毁、创建视图来完成,一方面如果你的视图的数据是动态获取的,那么就需要以合适的方式恢复数据;另外一方面,这种方式必然造成性能和内存开销的问题。

2、如果需要刷新的View的层级比较深,那么这种方式会带来严重的性能问题。

3、requestAnimationFrame毕竟是webcss的用法,在手机上,动画的效果比较生硬,如果需要‘弹性动画’,‘淡入淡出’等效果,则是比较难以实现的(需要辅助各种函数)。

setNativeProps 如果执意使用修改state的方式,觉得这种方式更符合当前需求对动画的控制,那么则应当使用原生组件的setNativeProps方法来做对应实现,它会直接修改组件底层特性,不会重绘组件,因此性能也远胜动态修改组件内联style的方法。

我们稍微修改下startAnimation方法:

startAnimation() {
  var count = 0;
  while (++count < 50) {
      requestAnimationFrame(() = >{
          this.refs.image.setNativeProps({
              style: {
                  width: this.state.width++,
                  height: this.state.height++
              }
          });
      });
  }
}

其中this.refs.image指向的是Image视图,效果图如下(比上一种方式流畅多了~):

preview2.gif 优点:

setNativeProps直接修改组件底层特性,不会重绘组件,因此性能也远胜动态修改组件内联style的方法。 缺点:

1、setNativeProps属于原生视图的方法,如果我们使用一个动画,单纯只是为了跟踪它的值,那么这么方法有点不合时宜。

2、还是和上面一种方式一样,如果需要实现‘弹性动画’,‘淡入淡出’等效果,则还是比较麻烦的。


重点,本篇文章的主角要登场了(此处有掌声~)

LayoutAnimation 当布局变化时,自动将视图运动到它们新的位置上。 一个常用的调用此API的办法是调用LayoutAnimation.configureNext(config),然后调用setState

建议大家阅读的时候打开LayoutAnimation源码,路径为: xxx/node_modules/react-native/Libraries/LayoutAnimation/LayoutAnimation.js,其中‘xxx’为当前项目文件夹。

一个标准的config格式如下:(createupdatedelete,分别表示视图创建更新删除时候的动画)

Config.png 示例:

{   
  duration: 700,   //持续时间   
  create: {   // 视图创建         
    type: LayoutAnimation.Types.linear,      
    property: LayoutAnimation.Properties.scaleXY // opacity、scaleXY   
  },   
  update: { // 视图更新      
    type: LayoutAnimation.Types.spring,      
    springDamping: 0.4   
  },
}

其中createupdatedeleteAnim格式如下:

Anim.png

delay:延迟指定时间(单位:毫秒) springDamping:弹跳动画阻尼系数(配合spring使用) initialVelocity:初始速度 type:类型定义在LayoutAnimation.Types中:

  • spring:弹跳
  • linear:线性
  • easeInEaseOut:缓入缓出
  • easeIn:缓入
  • easeOut:缓出

property:类型定义在LayoutAnimation.Properties中:

  • opacity:透明度
  • scaleXY:缩放

让我们来看一段示例代码,同样是修改startAnimation方法:

startAnimation() {
  LayoutAnimation.configureNext({
    duration: 700, //持续时间
    create: { // 视图创建
        type: LayoutAnimation.Types.spring,
        property: LayoutAnimation.Properties.scaleXY,// opacity、scaleXY
    },
    update: { // 视图更新
        type: LayoutAnimation.Types.spring,
    },
  });
  this.setState({width: this.state.width + 10, height: this.state.height + 10});
}

效果图如下: config1.gif

观察下这个动画: 视图创建的时候是缩放动画,点击‘Press Me!’,图片也会有个缩放动画。

我们还可以通过LayoutAnimation.create这个函数更简单的创建一个config,同样可以实现和上图一样的效果。

startAnimation() {
  LayoutAnimation.configureNext(LayoutAnimation.create(700,
             LayoutAnimation.Types.spring,
             LayoutAnimation.Properties.scaleXY));
  this.setState({width: this.state.width + 10, height: this.state.height + 10});
}

create函数接受三个参数:

  • duration:动画持续时间。
  • type:createupdate时的动画类型,定义在* LayoutAnimation.Types
  • creationProp:create时的动画属性,定义在LayoutAnimation.Properties

create的源码:

create.png 效果图和上图一样,这里不在贴出来了。

实际上,系统已经为我们提供了3个默认的动画,定义在LayoutAnimation.Presets中:

  • easeInEaseOut:缓入缓出
  • linear:线性
  • spring:弹性

一个简单的示例:

startAnimation() {
  LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
  this.setState({width: this.state.width + 10, height: this.state.height + 10});
}

具体参数配置,请查看Presets源码:

Presets.png

我们来分析下LayoutAnimation的优缺点:

优点:

1、效果非常的流畅,而且动画的过程很柔和,丝毫没有生硬的感觉。

2、可以灵活的配置动画参数,基本能满足单个动画的需求。

缺点:

1、如果要实现‘组合顺序’动画,比如先缩小50%、再向左平移100像素,那么就比较麻烦了,需要监听上一个动画的结束事件,再进行下一个。那么问题来了,configureNext第二个参数是可以监听动画结束的,但是只在IOS平台有效!

2、如果动画的效果更为复杂,比如同时执行动画,再顺序执行,对于编程来讲,需要做的事情很多,复杂度也大大提升。

那么如何定制更灵活丰富的动画效果呢,这就需要使用到高级动画API Animated。下一篇文章我们会介绍高级动画API—Animated的使用,感兴趣的朋友请继续阅读[React Native]动画-Animated

本文的源码地址Demo9