1. 首先对动画的内容进项拆分:icon、光环、发光效果、+1四部分,先看下布局:(从上到下依次是+一、光线、icon、光环)javascript
render() {
return (
<View style={[styles.container]}>
//+1
<Animated.Text
style={[
styles.txt,
{
opacity: this.animate.txtOpacityAnim,
transform: [
{
translateY: this.animate.txtMoveAnim
}
]
},
]}
>+1</Animated.Text>
//光线1
<Animated.View
style={[
styles.ray,
styles.ray1,
{
transform: [
{
rotate: '-60deg'
},
{
translateY: this.animate.lightMoveAnim
},
{
scaleY: this.animate.lightScaleAnim
}
],
opacity: this.animate.lightOpacityAnim,
height: this.animate.lightHeightAnim
}
]}
/>
//光线2
<Animated.View
style={[
styles.ray,
styles.ray2,
{
transform: [
{ rotate: '-30deg' },
{
translateY: this.animate.lightMoveAnim
},
{
scaleY: this.animate.lightScaleAnim
}
],
opacity: this.animate.lightOpacityAnim,
height: this.animate.lightHeightAnim
}
]}
/>
//光线3
<Animated.View
style={[
styles.ray,
styles.ray3,
{
transform: [
{ rotate: '30deg' },
{
translateY: this.animate.lightMoveAnim
},
{
scaleY: this.animate.lightScaleAnim
}
],
opacity: this.animate.lightOpacityAnim,
height: this.animate.lightHeightAnim
}
]}
/>
//光线4
<Animated.View
style={[
styles.ray,
styles.ray4,
{
transform: [
{ rotate: '60deg' },
{
translateY: this.animate.lightMoveAnim
},
{
scaleY: this.animate.lightScaleAnim
}
],
opacity: this.animate.lightOpacityAnim,
height: this.animate.lightHeightAnim
}
]}
/>
//手icon
<Animated.Image
style={[
styles.icon,
{
transform: [
{
rotate: this.animate.iconRotateAnim.interpolate({
inputRange: [-6, 6],
outputRange: ['-6deg', '6deg']
})
},
{ scale: this.animate.iconScaleAnim }
]
}
]}
source={{
uri: 'https://img.58cdn.com.cn/newsfe/toutiao/icon_praise_big.png'
}}
/>
//光环
<Animated.View
style={[
styles.circle,
{
borderWidth: this.animate.circleBorderWidthAnim,
opacity: this.animate.circleOpacityAnim,
transform: [{ scale: this.animate.circleScaleAnim }]
}
]}
/>
</View>
);
}复制代码
2. 接下来是初始化动画参数:
css
constructor(props) {
super(props);
this.animate = {
txtMoveAnim: new Animated.Value(0),
txtScaleAnim: new Animated.Value(0.01), // set scale0.01 for android bug
txtOpacityAnim: new Animated.Value(1),
iconRotateAnim: new Animated.Value(6),
iconScaleAnim: new Animated.Value(0.6),
circleScaleAnim: new Animated.Value(0.01),
circleBorderWidthAnim: new Animated.Value(3),
circleOpacityAnim: new Animated.Value(0.4),
lightHeightAnim: new Animated.Value(0.01),
lightMoveAnim: new Animated.Value(4),
lightOpacityAnim: new Animated.Value(1),
lightScaleAnim: new Animated.Value(1)
};
}复制代码
注意:这里并无把动画的初始化参数放在state里面,而是赋值给一个变量this.animate,由于state是react的状态,触发setState才会改变,animated.Value 是动画的状态,只要调用动画的start()方法就会改变,能够减小没必要要的性能浪费;接下来还有一点就是初始化的时候,+1和光圈的缩放状态都是0,这里为何初始化写成0.01呢,是由于在ios初始化0是没有问题的,可是在android上面部分手机在动画开始的时候会闪现出光圈实际的大小(样式设置的大小),为何设置成0.01也是通过了屡次的尝试,设置过大或者太小仍是会有问题,具体缘由这里也不是很明白,应该算是RN动画的小坑吧,若是你们有不一样观点望不吝留言赐教。
java
3. icon动画:点赞过程当中,大拇指的抖动、大小变化效果,用transform的缩放scale、旋转rotate实现。
react
const icon = [
Animated.sequence([
Animated.timing(this.animate.iconRotateAnim, {
toValue: -6,
duration: 400,
delay: 0,
easing: Easing.easeOut
}),
Animated.timing(this.animate.iconRotateAnim, {
toValue: 0,
duration: 300,
delay: 0,
easing: Easing.spring
})
]),
Animated.sequence([
Animated.timing(this.animate.iconScaleAnim, {
toValue: 1.4,
duration: 400,
delay: 0,
easing: Easing.easeOut
}),
Animated.spring(
//缩小动画
this.animate.iconScaleAnim,
{
tension: 100,
friction: 5,
toValue: 1
}
)
])
];复制代码
4. +1动画:此动画包括文字上移、透明度变化、大小变化,使用transform的tranlateY、缩放scale和透明度opacity样式实现。
android
const txt = [
Animated.sequence([
Animated.timing(this.animate.txtMoveAnim, {
toValue: -13,
duration: 400,
delay: 0,
easing: Easing.bezier(0.25, 0.1, 0.25, 0.1)
}),
Animated.timing(this.animate.txtOpacityAnim, {
toValue: 0,
duration: 500,
delay: 100,
easing: Easing.bezier(0.25, 0.1, 0.25, 0.1)
}),
]),
Animated.timing(this.animate.txtScaleAnim, {
toValue: 1.5,
duration: 400,
delay: 0,
easing: Easing.bezier(0.25, 0.1, 0.25, 0.1)
}),
];复制代码
5. 光环动画:光环变过程当中宽度、透明度、大小变化,使用边框的宽度改变、缩放scale和透明度opacity样式实现。
ios
const circle = [
Animated.timing(this.animate.circleOpacityAnim, {
toValue: 0,
duration: 300,
delay: 400,
easing: Easing.bezier(0.25, 0.1, 0.25, 0.1)
}),
Animated.sequence([
Animated.timing(this.animate.circleScaleAnim, {
toValue: 2.5,
duration: 600,
delay: 100,
easing: Easing.bezier(0.25, 0.1, 0.25, 1)
})
]),
Animated.sequence([
Animated.timing(this.animate.circleBorderWidthAnim, {
toValue: 4,
duration: 150,
delay: 400,
easing: Easing.easeOut
}),
Animated.timing(this.animate.circleBorderWidthAnim, {
toValue: 1,
duration: 150,
delay: 0,
easing: Easing.bezier(0.25, 0.1, 0.25, 1)
})
])
];复制代码
以上三部分的动画变化稍微简单一点,布局主要是使用绝对定位,只要初始位置正确,其余的动画变化就按照设计给的数据,设置对应的属性就能够了,主要的难点在于接下来要说的光线的动画变化,这个稍微复杂一点。再说这个动画以前咱们先了解一下css3动画的一个属性transform-origin(该属性容许您改变被转换元素的位置),定义是这样的,经过此属性能够设置动画的起始位置,默认是在变化的Dom节点的中心位置,为何要说这个属性呢,由于光线的变化效果是这样的,首先在所在的位置长度慢慢变大,而后再进行移动,最后长度慢慢变小直至消失。若是按照css3的这个属性,咱们能够设置变化的位置,这样看来这个动画也不是很复杂;可是RN里面的样式跟css仍是有必定的差异的,RN里面的样式没有transform-origin这个属性,那么咱们怎么来实现光线的动画呢,不卖关子了,直接上代码:
css3
6. 光线动画:变化过程当中主要涉及光线长度、位置、透明度的变化。
spring
const light = [
// light opcity
Animated.timing(this.animate.lightOpacityAnim, {
toValue: 0,
duration: 450,
delay: 550,
easing: Easing.bezier(0.25, 0.1, 0.25, 1)
}),
// light height
Animated.sequence([
Animated.timing(this.animate.lightHeightAnim, {
toValue: 6,
duration: 150,
delay: 150,
easing: Easing.easeOut
}),
Animated.timing(this.animate.lightHeightAnim, {
toValue: 6,
duration: 250,
delay: 0,
easing: Easing.bezier(0.25, 0.1, 0.25, 1)
}),
Animated.timing(
// replace tanslate-origin
this.animate.lightScaleAnim,
{
toValue: 0.2,
duration: 200,
delay: 0,
easing: Easing.bezier(0.25, 0.1, 0.25, 1)
}
)
]),
// light tanslateY
Animated.sequence([
Animated.timing(this.animate.lightMoveAnim, {
toValue: -2,
duration: 200,
delay: 100,
easing: Easing.bezier(0.25, 0.1, 0.25, 1)
}),
Animated.timing(this.animate.lightMoveAnim, {
toValue: -4 - 4,
duration: 600,
delay: 0,
easing: Easing.bezier(0.25, 0.1, 0.25, 1)
})
])
];复制代码
四条光线是以0度为分界点,以30度的间隔平均分配,因为没有transform-origin这个属性,咱们在长度开始变化的时候,同时进行位置的移动,这样一来就能够模拟以初始位置为动画的起始点,进行长度的变化;最后消失的过程采用位置变化的同时进行缩放处理,能够达到ui图上的消失的动画效果。
react-native
最后附上样式代码:
css3动画
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
width: 32,
height: 39,
position: 'absolute',
zIndex: 9999,
bottom: 6,
left: 1.5,
borderTopRightRadius: 16,
borderTopLeftRadius: 16,
borderColor: '#fff',
},
txt: {
position: 'absolute',
top: 9,
left: 9,
zIndex: 90,
height: 12,
color: '#FD8C20',
fontSize: 12,
fontFamily: 'System'
},
icon: {
width: 33 / 2,
height: 33 / 2,
position: 'absolute',
top: 17,
left: 8,
zIndex: 100
},
circle: {
height: 11,
width: 11,
position: 'absolute',
left: 10,
top: 15,
zIndex: 79,
borderWidth: 11 / 2,
borderRadius: 11 / 2,
borderColor: '#FBCB45',
transform: [{ scale: 0 }]
},
ray: {
width: 1.5,
height: 3,
borderRadius: 1.5,
position: 'absolute',
backgroundColor: '#FD8C20',
zIndex: 81
},
ray1: {
left: 10,
top: 17,
transform: [{ rotate: '-60deg' }]
},
ray2: {
left: 12,
top: 15,
transform: [{ rotate: '-30deg' }]
},
ray3: {
left: 16,
top: 15,
transform: [{ rotate: '30deg' }]
},
ray4: {
left: 18,
top: 17,
transform: [{ rotate: '60deg' }]
}
});复制代码
至此动画的部分已经介绍完毕,接下来要说的是RN动画性能方面的影响,因为点赞是在列表里面实现的,原本已经认为大功告成了,但是天不遂人愿,尤为是在android上表现的特别明显,动画会随着翻页的增多,开始时间会变长,并且动画也出现卡顿现象,百思不得其解,最后和同事通过半天的调试发现主要的缘由是页面的从新渲染会影响动画的性能,最后的解决方法是组件状态的更新放到动画结束以后再进行,这样页面从新渲染时动画已经结束了,给用户呈现出来的效果是动画比较流畅了。效果图以下图:(ui设计的还原度还算能够,能够作一下对比)
7. 总结
通过此次的动画实践,得出如下几点结论:对复杂的动画进行合理的拆解,而后对单个动画进行处理;遇到走不通的地方咱们能够换一种思惟,用其余的可替代方式来实现咱们须要的效果;要深刻的去研究和了解RN的性能问题以及动画的性能问题,这样才能从根本上解决问题;最后一点就是作技术不能闭门造车,这样不只影响本身成长,也会使本身的思惟更加局限,经过跟同事之间相互讨论,学习别人的思惟和解决问题的思路,会使本身受益不浅。
到此结束,在实际app应用中,仍是须要结合实际状况不断优化,但愿能够给RN的初学者带来一些借鉴。