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的初学者带来一些借鉴。

相关文章
相关标签/搜索