本次只记录用到的 API。

timing( )

图片由小变大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// js
this.state = {
hair1AnimateOpacity: new Animated.Value(0), // 声明动画初始状态
}

this.sequenceAnimated = Animated.timing(this.hair1AnimateOpacity, {
toValue: 1, //动画最终值
duration: 200, //动画时长3000毫秒
easing: Easing.linear,
}),

// react native
<Animated.View
style={{
width: 126,
height: 126,
transform: [{ scale: this.state.hair1AnimateOpacity }],
}}
>
<Image
ref="image"
style={{
width: 126,
height: 126,
}}
source={require('../../assets/image/hair1.png')}
></Image>
</Animated.View>

spring()

根据基于阻尼谐振动 damped harmonic oscillation的弹性模型生成一个动画值。它会在toValue值更新的同时跟踪当前的速度状态,以确保动画连贯。可以链式调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 // js
this.state = {
hair1AnimateOpacity: new Animated.Value(0), // 声明动画初始状态
}

Animated.spring(this.state.hair1AnimateOpacity, {
toValue: 1,
friction: 3, //弹跳系数
tension: 10, // 控制速度
}),

// react native
<Animated.View
style={{
width: 126,
height: 126,
transform: [{ scale: this.state.hair1AnimateOpacity }],
}}
>
<Image
ref="image"
style={{
width: 126,
height: 126,
}}
source={require('../../assets/image/hair1.png')}
></Image>
</Animated.View>

sequence()

按顺序执行一个动画数组里的动画,等待一个完成后再执行下一个。如果当前的动画被中止,后面的动画则不会继续执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import React from 'react'
import {
Image,
View,
StyleSheet,
Text,
Animated,
TouchableOpacity,
Easing,
} from '@mrn/react-native'

export interface OpacityProps {
isLoading: boolean
}
export interface OpacityStates {
hair1AnimateOpacity: any
hair2AnimateOpacity: any
hair3AnimateOpacity: any
hair4AnimateOpacity: any
}

export default class LoadAnimation extends React.Component<
OpacityProps,
OpacityStates
> {
sequenceAnimated: Animated.CompositeAnimation
constructor(props) {
super(props)

// 声明动画初始值
this.state = {
hair1AnimateOpacity: new Animated.Value(0),
hair2AnimateOpacity: new Animated.Value(0),
hair3AnimateOpacity: new Animated.Value(0),
hair4AnimateOpacity: new Animated.Value(0),
}

// 动画队列,会依次执行动画
this.sequenceAnimated = Animated.sequence([
Animated.spring(this.state.hair1AnimateOpacity, {
toValue: 1,
friction: 3, //弹跳系数
tension: 10, // 控制速度
}),
Animated.timing(this.state.hair1AnimateOpacity, {
toValue: 0, //透明度动画最终值
duration: 200, //动画时长3000毫秒
easing: Easing.linear,
}),
Animated.spring(this.state.hair2AnimateOpacity, {
toValue: 1,
friction: 3, //弹跳系数
tension: 10, // 控制速度
}),
Animated.timing(this.state.hair2AnimateOpacity, {
toValue: 0, //透明度动画最终值
duration: 200, //动画时长3000毫秒
easing: Easing.linear,
}),
Animated.spring(this.state.hair3AnimateOpacity, {
toValue: 1,
friction: 3, //弹跳系数
tension: 10, // 控制速度
}),
Animated.timing(this.state.hair3AnimateOpacity, {
toValue: 0, //透明度动画最终值
duration: 200, //动画时长3000毫秒
easing: Easing.linear,
}),
Animated.spring(this.state.hair4AnimateOpacity, {
toValue: 1,
friction: 3, //弹跳系数
tension: 10, // 控制速度
}),
Animated.timing(this.state.hair4AnimateOpacity, {
toValue: 0, //透明度动画最终值
duration: 200, //动画时长3000毫秒
easing: Easing.linear,
}),
])
}

// 调用动画
_startAnimated() {
this.sequenceAnimated.start()
}

render() {
return (
<View style={styles.mainStyle}>
<Animated.View
style={{
width: 126,
height: 126,
transform: [{ scale: this.state.hair1AnimateOpacity }],
}}
>
<Image
ref="image"
style={{
width: 126,
height: 126,
}}
source={require('../../assets/image/hair1.png')}
></Image>
</Animated.View>
<Animated.View
style={{
position: 'absolute',
width: 126,
height: 126,
transform: [{ scale: this.state.hair2AnimateOpacity }],
}}
>
<Image
ref="image"
style={{
width: 126,
height: 126,
}}
source={require('../../assets/image/hair2.png')}
></Image>
</Animated.View>
<Animated.View
style={{
position: 'absolute',
width: 126,
height: 126,
transform: [{ scale: this.state.hair3AnimateOpacity }],
}}
>
<Image
ref="image"
style={{
width: 126,
height: 126,
}}
source={require('../../assets/image/hair3.png')}
></Image>
</Animated.View>
<Animated.View
style={{
position: 'absolute',
width: 126,
height: 126,
transform: [{ scale: this.state.hair4AnimateOpacity }],
}}
>
<Image
ref="image"
style={{
width: 126,
height: 126,
}}
source={require('../../assets/image/hair4.png')}
></Image>
</Animated.View>
<TouchableOpacity
style={styles.touchStyle}
onPress={this._startAnimated.bind(this)}
>
<Text
style={{
width: 200,
height: 100,
textAlign: 'center',
lineHeight: 100,
}}
>
点击开始动画
</Text>
</TouchableOpacity>
</View>
)
}
}

动画循环

动画循环是最常用的一个场景,本次需求就要求对动画做一个循环效果。

查阅了官方文档 和网上的一些资料,总结了两类实现动画循环的效果。

1)单动画循环 loop( )

这个不多说,可以直接看官方文档

1
2
3
4
5
6
7
Animate.loop(
Animated.spring(this.state.hair1AnimateOpacity, {
toValue: 1,
friction: 3, //弹跳系数
tension: 10, // 控制速度
})
)

2)动画队列循环

动画的 start( ) 方法可以传递回调函数,因此在动画结束时再次回调就可以很方便的实现动画循环了。

1
2
3
4

_startAnimated() {
Animated.sequence(animations).start(() => this._startAnimated())
}

最后贴出完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import React from 'react'
import {
Image,
View,
StyleSheet,
Text,
Animated,
TouchableOpacity,
Easing,
} from '@mrn/react-native'

export interface OpacityProps {
isLoading: boolean
}
export interface OpacityStates {
hair1AnimateOpacity: any
hair2AnimateOpacity: any
hair3AnimateOpacity: any
hair4AnimateOpacity: any
}

export default class LoadAnimation extends React.Component<
OpacityProps,
OpacityStates
> {
// hair1AnimateOpacity: Animated.Value
// hair2AnimateOpacity: Animated.Value
// hair3AnimateOpacity: Animated.Value
// hair4AnimateOpacity: Animated.Value
sequenceAnimated: Animated.CompositeAnimation
constructor(props) {
super(props)

this.state = {
hair1AnimateOpacity: new Animated.Value(0),
hair2AnimateOpacity: new Animated.Value(0),
hair3AnimateOpacity: new Animated.Value(0),
hair4AnimateOpacity: new Animated.Value(0),
}
}
onSpringCompletion = () => {
// TODO
if (this.props.isLoading || 1) {
this._startAnimated()
}
}

_startAnimated() {
Animated.sequence([
Animated.spring(this.state.hair1AnimateOpacity, {
toValue: 1,
friction: 3, //弹跳系数
tension: 10, // 控制速度
}),
Animated.timing(this.state.hair1AnimateOpacity, {
toValue: 0, //透明度动画最终值
duration: 200, //动画时长3000毫秒
easing: Easing.linear,
}),
Animated.spring(this.state.hair2AnimateOpacity, {
toValue: 1,
friction: 3, //弹跳系数
tension: 10, // 控制速度
}),
Animated.timing(this.state.hair2AnimateOpacity, {
toValue: 0, //透明度动画最终值
duration: 200, //动画时长3000毫秒
easing: Easing.linear,
}),
Animated.spring(this.state.hair3AnimateOpacity, {
toValue: 1,
friction: 3, //弹跳系数
tension: 10, // 控制速度
}),
Animated.timing(this.state.hair3AnimateOpacity, {
toValue: 0, //透明度动画最终值
duration: 200, //动画时长3000毫秒
easing: Easing.linear,
}),
Animated.spring(this.state.hair4AnimateOpacity, {
toValue: 1,
friction: 3, //弹跳系数
tension: 10, // 控制速度
}),
Animated.timing(this.state.hair4AnimateOpacity, {
toValue: 0, //透明度动画最终值
duration: 200, //动画时长3000毫秒
easing: Easing.linear,
}),
]).start(() => this.onSpringCompletion())
}

render() {
return (
<View style={styles.mainStyle}>
<Animated.View
style={{
width: 126,
height: 126,
transform: [{ scale: this.state.hair1AnimateOpacity }],
}}
>
<Image
ref="image"
style={{
width: 126,
height: 126,
}}
source={require('../../assets/image/hair1.png')}
></Image>
</Animated.View>
<Animated.View
style={{
position: 'absolute',
width: 126,
height: 126,
transform: [{ scale: this.state.hair2AnimateOpacity }],
}}
>
<Image
ref="image"
style={{
width: 126,
height: 126,
}}
source={require('../../assets/image/hair2.png')}
></Image>
</Animated.View>
<Animated.View
style={{
position: 'absolute',
width: 126,
height: 126,
transform: [{ scale: this.state.hair3AnimateOpacity }],
}}
>
<Image
ref="image"
style={{
width: 126,
height: 126,
}}
source={require('../../assets/image/hair3.png')}
></Image>
</Animated.View>
<Animated.View
style={{
position: 'absolute',
width: 126,
height: 126,
transform: [{ scale: this.state.hair4AnimateOpacity }],
}}
>
<Image
ref="image"
style={{
width: 126,
height: 126,
}}
source={require('../../assets/image/hair4.png')}
></Image>
</Animated.View>
<TouchableOpacity
style={styles.touchStyle}
onPress={this._startAnimated.bind(this)}
>
<Text
style={{
width: 200,
height: 100,
textAlign: 'center',
lineHeight: 100,
}}
>
点击开始动画
</Text>
</TouchableOpacity>
</View>
)
}
}

const styles = StyleSheet.create({
mainStyle: {},
touchStyle: {},
})

参考文章

React Native 动画(Animated)

ReactNative Animated 动画详解

官方文档-动画

官方文档—API