1.前言

动画是移动应用中的一个相当重要的组成部分,一个用户体验良好的应用通常都具有流畅、有意义的动画。类似原生平台,React Native也为我们提供了丰富的动画API:requestAnimationFrame、LayoutAnimation、Animated。

  • requestAnimationFrame:帧动画,是最容易实现的一种动画,通过不断改变组件的state值,从而在视觉上产生一种动画的效果,类似于gif动画的方式。
  • LayoutAnimation:布局动画,当布局发生改变时的动画模块,允许在全局范围内创建和更新动画,这些动画会在下一次渲染或布局周期运行,实现单个动画非常简洁,体验和性能良好。
  • Animated:用于创建更精细的交互控制的动画,可进行多个动画的组合动画,具备极高的性能,是功能最强大的动画API。

本节我们先介绍requestAnimationFrame。

2.requestAnimationFrame帧动画的实现

requestAnimationFrame实现帧动画的原理非常粗暴简洁,即通过修改state值来不断得改变视图上的样式,从而在视觉上产生一种动画的效果。

import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    TouchableOpacity,
    Platform,
} from 'react-native';

export default class FrameAnimationDemo extends Component {
    constructor(props){
        super(props);
        this.state = {
            width:200,
            height: 20,
        };
    }

    _onPress() {
        //每按一次增加近30宽高
        var count = 0;
        while(++count<30){
            requestAnimationFrame(()=>{
                this.setState({
                    width: this.state.width + 1,
                    height: this.state.height + 1
                });
            })
        }
    }

    render() {
        return (
            <View style={styles.container}>
                <View style={[styles.content, {width: this.state.width, height: this.state.height}]}>
                    <Text style={[{textAlign: 'center'}]}>Hello World!</Text>
                </View>
                <TouchableOpacity style={styles.content} onPress={this._onPress.bind(this)}>
                    <View style={styles.button}>
                        <Text style={styles.buttonText}>Press me!</Text>
                    </View>
                </TouchableOpacity>
            </View>
        );
    }
}
const styles = StyleSheet.create({
    container: {
        marginTop:25,
        flex: 1,
    },
    content: {
        backgroundColor: 'rgba(200, 230, 255, 0.8)',
        marginBottom:10,
        justifyContent:"center",
        alignSelf:"center",
    },
    button: Platform.select({
        ios: {},
        android: {
            elevation: 4,
            // Material design blue from https://material.google.com/style/color.html#color-color-palette
            backgroundColor: '#2196F3',
            borderRadius: 2,
            width:100,
            height:30,
        },
        justifyContent:"center",
        alignSelf:"center",
    }),
    buttonText: {
        alignSelf:"center",
    }
});

效果如下:

从效果上看动画有种一顿一顿的感觉。这是由于通过修改state值,导致频繁地销毁、重绘视图,内存开销大,从而使得动画卡顿明显。另外对于帧动画而言,如果帧数较少,动画的效果会比较生硬,帧数过多又会引发性能问题。

3.优化

如果帧动画的方式更符合当前对动画的控制方式,我们可以对上述方法做一点优化,在requestAnimationFrame中采用setNativeProps直接修改组件的属性并触发局部刷新,不会导致重绘组件,因此在性能上优于直接修改state的方法。
修改_onPress方法,将对this.setState的直接修改改为对”Hello World”按钮的属性修改this.refs.view1.setNativeProps。

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

        }
    }

this.refs.view1指向的是”Hello World”文字的父视图。

<View ref="view1" style={[styles.content, {width: this.state.width, height: this.state.height}]}>
    <Text style={[{textAlign: 'center'}]}>Hello World!</Text>
</View>

优化后效果如下:

通过对比可以看出流畅顺滑多了。
每个动画API都有其适应和不适应的场景,如果要实现“弹性动画”,“缓入缓出”等效果,使用requestAnimationFrame还是比较难的,需要辅助各种函数。下一节将介绍另一种动画API——LayoutAnimation。