react-native動畫實踐----點贊動畫

        學習和應用react-native已經有快一年的時間了,在平時的工做實踐中遇到的坑和痛點也是不勝枚舉的,本身使用RN的動畫組件Animated也有一段時間了,從剛開始的簡單動畫慢慢到複雜動畫,中間也經歷了很多「微妙」的事情讓本身苦不堪言。此次就談一談本身在實現一個較爲複雜動畫的經歷和遇到的一些坑,記錄一下此次「微妙」的經歷,也但願能給廣大react-native的初學者帶來參考,若是寫的有欠缺的地方但願你們能夠不吝賜教,能夠相互學習,相互促進。再也不廢話,進入本次主題:
今天要實現的是一個點贊動畫:ui的效果圖以下:


這是大小兩個動畫,比例是3:1,小的點贊動畫多了一個+1的動畫。最開始的時候是用圖片的輪幀播放實現的,因爲圖片較多,雖然使用提早預加載圖片的方式,可是在android第一次加載動畫上仍是會有丟幀的現象,接下來考慮用gif代替,發現用gif的時候,動畫裏面的一下漸變的透明的效果都出不來,最後把以上兩個方案都pass掉了,那麼接下來就是用RN的Animatied組件來實現這個動畫,因爲大拇指有一個變形的過程程序實現起來會有困難,因此最後去掉了這個效果,手的動畫只有旋轉,縮放,漸變消失三部分了。

1.  首先對動畫的內容進項拆分:icon、光環、發光效果、+1四部分,先看下佈局:(從上到下依次是+一、光線、icon、光環)javascript

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

相關文章
相關標籤/搜索