ReactNative之结合具体示例来看RN中的的Timing动画

今天继续更新RN相关的博客。上篇博客详细的聊了RN中关于Flex布局的相关东西,具体请参见《ReactNative之参照具体示例来看RN中的FlexBox布局》。本篇博客继续更新RN的动画部分,博客中的内容依然是依托于具体的示例来进行的。

下方是官网对RN动画的的一个综述,意思就是说在RN的组件中View、Text、Image 和ScrollView是支持动画的,不过你可以使用Animated.createAnimatedComponent()这个方法来创建一个支持动画的组件稍后会介绍到。这些支持动画的组件在使用动画是都差不多,本篇博客中的示例主要以View为主,也会有Text、Image的部分动画。

Animated exports four animatable component types: ViewTextImage, and ScrollView, but you can also create your own using Animated.createAnimatedComponent().

 

一、一个简单的Moving动画

第一部分我们先从一个简单的Moving动画来入手。这个Moving动画是非常简单的,就是一个View,然后点击这个View,会从一个地方移动到另一个地方。然后再次点击会回归到原位。下方是效果实现的分析:

  • 首先我们会为View添加一个点击事件,点击View时,从一个位置移到另一个位置。
  • 再次点击时,会回到上次的一个位置。
  • 在移动时我们加了一个Bounce的一个动画效果。

  

 

下方代码段是上述动画效果的部分代码。代码比较简单:

  • 首先在State中定义了一个类型为 Animated.Value 的动画值,该值就负责来记录动画路径的值。该值在组件的构造器中进行了初始化,其初始值为零。
  • 然后就是 pressView 方法了,该方法就是上述红色View点击时所执行的方法。为了点击反复移动,我们使用了 toValue来记录下次要运动的重点值。这个toValue值 0 和 1稍后会详细介绍。
  • 然后就是关键了,调用了Animated 的timing 方法,该方法是用来配置一些动画效果的,比如设置动画执行时间的duration(单位为ms)、设置目标值的 toValue属性,以及指定缓动效果的熟悉 easing。关于这个easing下方会有详细的Demo介绍到。
  • 设置完动画所执行的参数后,就调用了start() 方法来执行这个动画了。

  

 

上面这段代码是动画设置的相关代码,下方这块代码是动画使用的相关代码片段。下方是对这段代码的解析:

  • 首先是从state中取出了动画值,我们将该值付给了moveValue。
  • 然后我们是根据这个 moveValue 通过差值函数创建了一个 toValue的值,这个值就是我们动画的目标值,也是我们真正要使用的值。
  • 这个 interpolate() 差值函数负责用来指定 toValue的生成规则, 该函数可以把这个

  

 完整代码片段:

 1 type States = {
 2   moveValue: Animated.Value  // 存储动画的值
 3 }
 4 
 5 class MoveViewTestComponent extends Component<null, States> {
 6   toValue = 0
 7   constructor (props) {
 8     super(props)
 9     this.state = {
10       moveValue: new Animated.Value(0)
11     }
12   }
13 
14   pressView = () => {
15     this.toValue = this.toValue === 0 ? 1 : 0
16     Animated.timing(
17       this.state.moveValue,  // 初始化从0开始
18       {
19         toValue: this.toValue, // 目标值
20         duration: 1000,         // 时间间隔
21         easing: Easing.bounce // 缓动函数
22       }
23     ).start()
24   }
25 
26   render () {
27     let { moveValue } = this.state
28 
29     let toValue = moveValue.interpolate({
30       inputRange: [0,1],
31       outputRange: ['10%', '60%']
32     })
33     return (
34       <TouchableOpacity onPress={this.pressView}>
35         <Animated.View                 // 使用专门的可动画化的View组件
36           style={{
37             width: 100,
38             height: 50,
39             backgroundColor: 'red',
40             left: toValue,
41           }}
42         >
43           <Text style={{ color: '#fff' }}> Tap Me Move </Text>
44         </Animated.View>
45       </TouchableOpacity>
46     )
47   }
48 }
View Code

 

上面设设置的left属性,我们还可以对上述代码进行修改,使用动画来改变每个角的弧度,具体动画效果如下所示:

 

代码就比较简单了,就是把动画的值直接赋值给我们的 borderRadius 属性即可。

  

 

 

二、使用Easing函数指定相关的动画效果

在上面的示例中我们指定的移动动画的Bounce效果,下方我们将通过一个示例来看一下Easing中所有的效果,具体动画效果如下所示。从下方的示例中我们不难看出,每种效果的动画运动轨迹都不同,我们在平时开发中可以根据自己的需要来选择相关的效果。当然我们还可以通过矩阵来定义动画的变换路径,在此就不做过多赘述了。

  

 

接下来我们来看一下上述动画实现的一个Demo的设计与实现。

首先我们定义了一个MoveView组件,也就是对应着上述Demo中的每个Button,上面可点击可移动的每个View就是一个MoveView。该View会从外部接收一个easing参数,该参数会被设置为该View的动画效果。具体代码如下所示:

  

 

 然后我们创建了一个 TestMoveView 的一个组件,这个组件就是上述演示的内容。在 TestMoveView 中我们定义了两个数组,第一个数组用来存放每个按钮的Title, 第二个数组则用来存放相关按钮的动画类型。稍后会用到下方的这两个数组。

  

 

下方就是两个比较核心的方法了, 下方是对这两个方法的介绍

  • 第一个 item 方法用来创建一个 MoveView,该View对应的就是上述一个按钮。第一个参数Title就是按钮上的title, 二后边的easing则是动画效果。
  • 在Item方法中我们给 MoveView 设置了一个ref的属性,该属性的Value值我们用的是按钮上的Title。设置完这个ref值后,我们可以使用 this.refs 来获取相关key值的对象。稍后我们会用到。 
  • 然后就是 createItem 方法了,该方法负责调用 上面我们事先创建好的数组,从数组中取出相关的值,然后调用 item 方法创建一系列的 MoveView 放到相关的数组里并返回。
  • 在 Render 方法中我们就可以调用下方的这个 createItem 方法来创建相关的按钮了。上的图片中能动的按钮都是通过这个 CreateItem 方法来创建的。

  

 

最后部分我们就来看一下 点击Tap Me 按钮所执行的相关方法了,下方的Click就是该方法。 在Click方法中主要做的就是调用 this.refs 方法获取所有可移动的MoveView的对象,然后调用该对象的pressView方法执行相关的动画。所有点击 Tap Me 按钮,下方所有的按钮都会运动。

   

完整代码:

  1 import React,{ Component } from 'react'
  2 // import MoveView from './MoveView'
  3 import { Animated, Easing, Text, TouchableOpacity, View } from 'react-native'
  4 
  5 type EasingType = (value: number) => number
  6 
  7 export default class TestMoveView extends Component {
  8   animatedKey: string[] = [
  9     'linear',
 10     'quad',
 11     'cubic',
 12     'sin',
 13     'exp',
 14     'bounce',
 15     'poly-5',
 16     'elastic-2',
 17     'back-2',
 18     'bezier'
 19   ]
 20 
 21   animatedEasingType: EasingType[] = [
 22     Easing.linear,
 23     Easing.quad,
 24     Easing.cubic,
 25     Easing.sin,
 26     Easing.exp,
 27     Easing.bounce,
 28     Easing.poly(5),
 29     Easing.elastic(2),
 30     Easing.back(2),
 31     Easing.bezier(0,1.6, 1,-0.67)
 32   ]
 33 
 34   click = () => {
 35     for (let i = 0; i < this.animatedKey.length; i++) {
 36       this.refs[this.animatedKey[i]].pressView()
 37     }
 38   }
 39 
 40   item = (title: string, easing: EasingType) => {
 41     return (
 42       <MoveView
 43         easing= {easing}
 44         ref = {title}>
 45         <Text style={{ fontSize: 17, textAlign: 'center' }}>
 46           {title}
 47         </Text>
 48       </MoveView>
 49     )
 50   }
 51 
 52   createItems = () => {
 53     let items = []
 54     for (let i = 0; i < this.animatedKey.length; i++) {
 55       items.push(this.item(this.animatedKey[i], this.animatedEasingType[i]))
 56     }
 57     return items
 58   }
 59 
 60   render () {
 61     console.log('lizelu')
 62     return (
 63       <View style={{ flex: 1, justifyContent: 'center' }}>
 64         <TouchableOpacity onPress={this.click}>
 65           <Text>Tap Me</Text>
 66         </TouchableOpacity>
 67         { this.createItems() }
 68       </View>
 69     )
 70   }
 71 }
 72 
 73 // MoveView
 74 
 75 type MoveViewProps = {
 76   easing?: (value: number) => number
 77 }
 78 
 79 class MoveView extends Component<MoveViewProps> {
 80   toValue = 0
 81   state = {
 82     moveValue: new Animated.Value(0)
 83   }
 84 
 85   pressView = () => {
 86     this.toValue = this.toValue === 0 ? 1 : 0
 87     Animated.timing(
 88       this.state.moveValue,  // 初始化从0开始
 89       {
 90         toValue: this.toValue, // 目标值
 91         duration: 1000,         // 时间间隔
 92         easing: this.props.easing // 动画效果
 93       }
 94     ).start()
 95   }
 96 
 97   render () {
 98     let { moveValue } = this.state
 99     let toValue = moveValue.interpolate({
100       inputRange: [0,1],
101       outputRange: ['10%', '70%']
102     })
103     return (
104       <TouchableOpacity onPress={this.pressView}>
105         <Animated.View                 // 使用专门的可动画化的View组件
106           style={[{
107             width: 80,
108             height: 30,
109             backgroundColor: 'powderblue',
110             margin: 2,
111             left: toValue   // 动画的目标值
112           }]}
113         >
114           {this.props.children}
115         </Animated.View>
116       </TouchableOpacity>
117     )
118   }
119 }
Easing

 

 

三、动画的插值函数及transform

1、插值函数

接下来我们通过一个Loading中经常使用的旋转动画,来看一下RN动画中的插值函数。下方的Loading动画本质上就是一张图片在不停的转圈,不过在转圈的时候我们设置了一个Circle的动画效果。

  

需要实现的效果就是上面这个效果,然后我们看一下代码实现以及插值函数的使用。首先我们来看一下上述动画启动时的相关代码:

  • 首先在 ComponentDidMount 方法中调用了启动方法的函数 startAnimation
  • startAnimation 函数中,我们通过 Animation的 loop 方法来执行循环动画动画的值从 0 到 1
  • 并且我们设置了动画效果为 circle
  • 最后就是调用start方法启动动画了。

  

 

然后就是Render方法中获取动画值,给相关的组件设置动画了,具体代码如下所示:

  • 首先我们从state中获取到相关的动画值 animationValue
  • 然后调用该动画值的插值函数 interpolate,将动画值中的 0~1的范围映射成角度 0deg ~ 360deg
  • 最后就是将这个插值函数生成的值 rotateZValue设置给 Image的transform即可。

  

 

2、Transform

经过上述步骤我们的图片就转起来了。插值函数在动画中还是比较常用的,上面是把 0 ~ 1映射成角度,我们还可以将该值映射成透明度、颜色等等,总之插值函数是RN动画中比较重要的角色。而且我们可以给一个RN元素设置多个插值动画,这样这个元素就会有多个动画效果。

下方这个动画就由多个插值函数结合着多种变换方式组合而成的,分别是移动、旋转、放大这三种变换同时作用到一个View上所展示的效果,具体效果如下所示:

  

上述效果是在第一个转圈的动画中丰富了一下而形成的,具体代码如下:

  • 前两个负责生成移动和缩放效果使用的值的插值函数和上面那个转圈的比较一致,只不过映射的值不同。
  • 然后看第三个旋转使用的插值函数就稍微有点不同了,该插值函数可以将 0 ~ 1 不同的区间的值映射成不同范围的值,从这个旋转的插值函数的映射关系不难看出,上述View的旋转路径是先快后慢的,这一点在插值函数中也是比较常用的。
  • 最后就是将这三个插值函数所生成的结果设置在View的 transform 的各个key中就可以了。

  

 

上面是缩放、移动、旋转的变换。下面我们来看一下斜切的变换。下方第一个是X方向上的斜切,第二个是Y轴方向上的斜切。

 

    

代码也比较简单,下方是设置斜切的相关代码:

  

  

天不早了,今天博客就先到这儿,下篇博客继续RN动画的相关内容。下篇博客我们会通过一系列的“拉皮条”操作来看一下RN中的Spring动画。下篇的“拉皮条”的示例还是比较有意思的。稍后会更新。

posted @ 2018-12-04 01:15  青玉伏案  阅读(4635)  评论(0编辑  收藏  举报