react native 动画组件(Animated)浅析

简述

react native封装了两个易于使用的动画组件。用于全局的布局动画LayoutAnimation,和用于创建更精细的交互控制的动画Animated。本章主要对Animated组件进行简单的学习。

动画类型

Animated提供了三种动画类型。每种动画类型都提供了特定的函数曲线,用于控制动画值从初始值变化到最终值的变化过程:

  • Animated.decay()以指定的初始速度开始变化,然后变化速度越来越慢直至停下。
  • Animated.spring()提供一个简单的弹簧物理模型
  • Animated.timing()使用easing函数让数据随时间动起来。

大多数情况下你应该使用timing()。默认情况下,它使用对称的 easeInOut曲线,将对象逐渐加速到全速,然后通过逐渐减速停止结束。

以下是三种动画类型的效果对比:

三种类型的动画对比

方法详解

1. Animated.decay(animateValue, conf)

推动一个值以一个初始的速度和一个衰减系数逐渐变为0。conf参数有以下这些属性:

  • velocity: 初始速度。必填。
  • deceleration: 衰减系数。默认值0.997。
  • useNativeDriver: 使用原生动画驱动。默认不启用(false)。
2. Animated.spring(animateValue, conf)

基础的单次弹跳物理模型,产生一个基于ReboundOrigami实现的Spring动画。它会在toValue值更新的同时跟踪当前的速度状态,以确保动画连贯。可以链式调用。

conf参数有以下这些属性(注意你不能同时定义bounciness/speedtension/friction这两组,只能指定其中一组):

  • friction: 控制“弹跳系数”、夸张系数,默认为7.
  • tension: 控制速度,默认40。
  • speed: Controls speed of the animation. Default 12.
  • bounciness: Controls bounciness. Default 8.
  • useNativeDriver: 使用原生动画驱动。默认不启用(false)。
3. Animated.timing(animateValue, conf)

推动一个值按照一个过渡曲线而随时间变化。conf参数有以下这些属性:

  • duration: 动画的持续时间(毫秒)。默认值为500.
  • easing: 一个用于定义曲线的渐变函数。阅读Easing模块可以找到许多预定义的函数。默认值为Easing.inOut(Easing.ease).
  • delay: 开始动画前的延迟时间(毫秒)。默认为0.
  • useNativeDriver: 使用原生动画驱动。默认不启用(false)。

这里提及的 Easing 动画函数模块在 react-native/Libraries/Animated/src/ 目录下,该模块预置了 easebezier 等诸多缓动特性,有兴趣可以去了解。

代码

import React from 'react';
import {
    View,
    Text,
    Easing,
    Animated,
} from 'react-native';

export default class FadeInView extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            fadeAnim: new Animated.Value(0),           // 透明度初始值设为0
        };
    }

    componentDidMount() {}

    startAnimated(type){
        if (type==0) {
            Animated.decay(                             // 推动一个值以一个初始的速度和一个衰减系数逐渐变为0。
                this.state.fadeAnim,                    
                {
                    toValue: 1,                         // 透明度最终变为1,即完全不透明
                    velocity: 0.1                       // 初始速度
                }
            ).start();                                  // 开始执行动画
        }else if (type==1) {
            Animated.spring(                            // 基础的单次弹跳物理模型
                this.state.fadeAnim,                    
                {
                    toValue: 1,                         // 透明度最终变为1,即完全不透明
                    friction: 8,                        // 弹性系数
                    tension: 35                         // 速度
                }
            ).start();                                  // 开始执行动画
        }else{
            Animated.timing(                            // 随时间变化而执行的动画类型
                this.state.fadeAnim,                    
                {
                    toValue: 1,                         // 透明度最终变为1,即完全不透明
                    duration: 2500,                     // 动画时间
                    easing: Easing.bezier(0.15, 0.73, 0.37, 1.2) // 曲线的渐变函数。
                }
            ).start((res)=>{                            // 开始执行动画
                console.log(this.TAG,'res',JSON.stringify(res))
            });                                              
        }
    }

    render() {
        return (
            <View>
                <Animated.View                      
                    style={{
                        style={this.props.style,{
                        backgroundColor: 'powderblue',
                        opacity: this.state.fadeAnim,   // 将透明度指定为动画变量值
                    }}>
                    {this.props.children}
                </Animated.View>
            </View>
        );
    }
}
'use strict';
import React from 'react';
import {
    StyleSheet,
    View,
    Text,
    TouchableOpacity
} from 'react-native';

import FadeInView from './fadeInView';

export default class TestAnimated extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
        }
    }

    startAnimated(){
        this.animatedOfDecay&&this.animatedOfDecay.startAnimated(0);
        this.animatedOfSpring&&this.animatedOfSpring.startAnimated(1);
        this.animatedOfTiming&&this.animatedOfTiming.startAnimated(2);
    }

    render() {
        return (
            <View style={{flex:1,alignItems:'center'}}>
                <FadeInView style={styles.fadeView} ref={(o)=>this.animatedOfDecay=o}>
                    <Text style={styles.fadeInText}>decay</Text>
                </FadeInView>
                <FadeInView style={styles.fadeView} ref={(o)=>this.animatedOfSpring=o}>
                    <Text style={styles.fadeInText}>spring</Text>
                </FadeInView>
                <FadeInView style={styles.fadeView} ref={(o)=>this.animatedOfTiming=o}>
                    <Text style={styles.fadeInText}>timing</Text>
                </FadeInView>
                <TouchableOpacity onPress={()=>this.startAnimated()}>
                    <Text style={{fontSize:Size(16),padding:10}}>start</Text>
                </TouchableOpacity>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    fadeInView:{
        width:250, 
        height:50, 
        justifyContent:'center',
        alignItems:'center',
    },
    fadeInText:{
        fontSize:28, 
        textAlign:'center', 
        margin:10,
    },
});

使用动画

通过在动画上调用start()来启动动画。 start()需要一个 完成 回调函数,当动画完成时将会调用它。如果动画运行正常,则将通过{finished:true}触发回调。如果动画是因为调用了stop()而结束(例如,因为它被手势或其他动画中断),则它会收到{finished:false}

Animated.timing(// 随时间变化而执行的动画类型
    this.state.fadeAnim, // 动画中的变量值
    {
        toValue: 1, // 透明度最终变为1,即完全不透明
        duration: 2500, // 动画时间
    }
).start((res)=>{
    console.log(this.TAG,'res',JSON.stringify(res)); //FadeInView res {"finished":true}
    // todo 做一些事情
});                                                  

使用原生动画驱动

使用原生动画,我们会在开始动画之前将所有关于动画的内容发送到原生代码,从而使用原生代码在UI线程上执行动画,而不是通过对每一帧的桥接去执行动画。一旦动画开始,JS线程就可以在不影响动画效果的情况下阻塞(去执行其他任务)掉了。

你可以通过在动画配置中指定useNativeDriver:true 来使用原生动画驱动。你可以在官方的 动画文档 中看到更详细的解释。

Animated.timing(this.state.animatedValue, {
  toValue: 1,
  duration: 500,
  useNativeDriver: true, // <-- 加上这一行
}).start();

动画值在不同的驱动方式之间是不能兼容的。因此如果你在某个动画中启用了原生驱动,那么所有和此动画依赖相同动画值的其他动画也必须启用原生驱动。

自定义动画组件

Animated仅封装了四个可以动画化的组件:ViewTextImageScrollView,不过你也可以使用Animated.createAnimatedComponent()来封装你自己的组件。

组合动画

动画还可以使用组合函数以复杂的方式进行组合:

  • Animated.delay() 在给定延迟后开始动画。
  • Animated.parallel() 同时启动多个动画。
  • Animated.sequence() 按顺序启动动画,等待每一个动画完成后再开始下一个动画。
  • Animated.stagger() 按照给定的延时间隔,顺序并行的启动动画。

动画也可以通过将toValue设置为另一个动画的Animated.Value来简单的链接在一起。

默认情况下,如果一个动画停止或中断,则组中的所有其他动画也会停止。

猜你喜欢

转载自blog.csdn.net/r122555/article/details/80840808