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的初學者帶來一些借鑑。