React Native twitter收藏动画

参考文章:react native how to create twitter exploding hearts

看了这个文章后试着写了下,一些参数什么的写的有点随意,效果木有原作的那么好_(:зゝ∠)_,就当记录下学习笔记好了(ง •_•)ง

预览

start~

引入Animated和ART组件,图形有大小位移等的变化,需要让Shape组价可动画化:

...

import {
    ART,
    Animated,
    ...
} from 'react-native';

const {
    Surface,
    Shape,
    Group,
    Path
} = ART;

const AnimatedShape = Animated.createAnimatedComponent(Shape);

创建Animated.Value,之后样式变化都靠它了_(:зゝ∠)_

    constructor (props) {
        super(props);

        this.state = {
            animation: new Animated.Value(0)
        };

    }
    
    ...
    
    animate () {
        Animated.timing(this.state.animation, {
            toValue: 28,  // 可以假设是做了一个28帧的gif
            duration: 1000
        }).start();
    }

心形

  • 形状用的SVG,也可以用css来画;
  • 有一个大小变化的动画;
  • 颜色从灰色变成红色
<TouchableWithoutFeedback
    onPress={this.animate}
>
    <View style={{height: 100, width: 100, backgroundColor: '#fff'}}>
        <Surface
            height="100"
             width="100">
            <AnimatedShape
                d="M16,29 C15.9970361,29 11.914011,25.472682 8.03173648,20.4590299 C4.14946196,15.4453778 -2.46221558,7.68242391 3.08862305,2.10644531 C8.63946168,-3.46953329 16.1049427,5.07202148 16.0279365,5.07202148 C16.0279365,4.99789663 23.4116905,-3.48231344 28.9511719,2.10644531 C34.4897069,7.69648208 28.0043918,15.4325977 24.1315817,20.4590299 C20.2587715,25.4841842 15.9970361,29 16,29 Z"
                fill={this.state.animation.interpolate({
                    inputRange: [0, 1],
                    outputRange: ['rgba(243,243,243)', 'rgba(235,65,61)'],
                    extrapolate: 'clamp'
                })}
                scale={this.state.animation.interpolate({
                    inputRange:  [0, .01, 6, 10, 12, 18],
                    outputRange: [1, 0, .1, 1, 1.2, 1],
                    extrapolate: 'clamp'
                })}
            />
        </Surface>
    </View>
</TouchableWithoutFeedback>

inputRange并没有写0-28,这里用了extrapolate: 'clamp',不让值超出outputRange的范围,不然大小和颜色还会继续变化。

形状没有类似[缩放中心点]的属性,所以大小变化的时候位置也会变(╯‵□′)╯︵┻━┻,不过可以改变的值还有x和y,即图形左上角的坐标。

谜之蹩脚的数学插入:

QQ20161021-2@2x.png

假设左上角坐标为(x0,y0),中心点坐标为(x1,y1),正常大小为1倍,缩放为原来的n≠0倍后

新x0 = x0 - (n - 1)  * ( x1 - x0);

新y0 = y0 - (n - 1) * (y1 - y1);

这里画布是100 100,中心点为(50, 50),图形是[约]30 30 大小,左上角坐标就是(35, 35),算出来x y 的变化:

<AnimatedShape
    ...
    x={this.state.animation.interpolate({
        inputRange:  [0, .01, 6, 10, 12, 18],
        outputRange: [35, 50, 48.5, 35, 32, 35 ],
        extrapolate: 'clamp'
    })}
    y={this.state.animation.interpolate({
        inputRange:  [0, .01, 6, 10, 12, 18],
        outputRange: [35, 50, 48.5, 35, 32, 35 ],
        extrapolate: 'clamp'
    })}
/>

那一坨小圆点

位置

如果看成每个位置只有一个圆的话,就是7个圆在一个大圆形的边上面平均分布,假设第一个圆位置和y轴的夹角为0,那剩下6个圆形夹角[弧度]就是 360 / 7 1, 360 / 7 2 ...

谜之蹩脚的数学再次乱入:

假设大圆形半径为R,中心点坐标是(0,0),小圆从0开始第n个坐标为(Xn,Yn)

1弧度=180 / π * 1角度

弧度 360 / total i = 360 / 7 i

Xn = cosθ * R = cos((360 / 7 * i) * (π / 180)) * R = cos(2 * Math.PI * i / 7) * R
Yn = sinθ * R = sin((360 / 7 * i) * (π / 180)) * R = sin(2 * Math.PI * i / 7) * R

so:

const CENTER_X = 50,
    CENTER_Y = 50; // 画布中心刚才说的是(50, 50)
...

    componentWillMount () {

        //  init circle position

        this.setState({
            circlePosition: CIRCLE_COUNT.map((item, index) => {
                return getPosition(index, 30);
            })
        });

        function getPosition (index, radius) {

            const a = 2 * Math.PI * index / 7;

            return {
                x: Math.cos(a) * radius + CENTER_X,
                y: Math.sin(a) * radius + CENTER_Y
            }
        }

    }

位移变化,颜色,旋转

    ...
    renderCircle () {

        let moveUp = this.state.animation.interpolate({
            inputRange: [0, 5.99, 14],
            outputRange: [0, 0, -1]
        });

        let moveDown = this.state.animation.interpolate({
            inputRange: [0, 5.99, 14],
            outputRange: [0, 0, 1]
        });

        var PARTICLE_COLORS = [
            'rgb(158, 202, 250)',
            'rgb(161, 235, 206)',
            'rgb(208, 148, 246)',
            'rgb(244, 141, 166)',
            'rgb(234, 171, 104)',
            'rgb(170, 163, 186)',
            'rgb(208, 148, 246)'
        ];

        return this.state.circlePosition.map((item, index) =>
            <Group
                x={item.x}
                y={item.y}
                key={index}
                rotation={20 * index} // 角度额写的有点随意了。。
            >
                <Circle
                    x={moveUp}
                    y={moveUp}
                    scale={this.state.animation.interpolate({
                        inputRange: [0, 20, 28],
                        outputRange: [0, 1, 0]
                    })}
                    fill={PARTICLE_COLORS[index]}
                    stroke={PARTICLE_COLORS[index]}
                    radius={2}
                />
                <Circle
                    x={moveDown}
                    y={moveDown}
                    scale={this.state.animation.interpolate({
                        inputRange: [0, 20, 28],
                        outputRange: [0, 1, 0]
                    })}
                    fill={PARTICLE_COLORS[6 - index]}
                    stroke={PARTICLE_COLORS[6 - index]}
                    radius={1}
                />
            </Group>
        )
    }

这里单独把小圆拉出来写

class Circle extends Component {
    render () {

        let radius = this.props.radius;
        let circle = Path().moveTo(0,-radius)
            .arc(0,2 * radius, radius)
            .arc(0,-2 * radius, radius)
            .close();

        return (
            <AnimatedShape d={circle} {...this.props} />
        )
    }
}

到这基本完了,还有一个从小到大的原型在星星的后面,变化基本是从小到大,然后中间空白[要写下描边],然后消失

<Circle
    radius={30}
    stroke={this.state.animation.interpolate({
        inputRange: [0, 5],
        outputRange: ["#ce3586", "#c885ef"],
        extrapolate: 'clamp'
    })}
fill={this.state.animation.interpolate({
        inputRange: [0, 5, 6],
        outputRange: ["#ce3586", "#c885ef", "#fff"],
        extrapolate: 'clamp'
    })}
x={50}
y={50}
scale={this.state.animation.interpolate({
        inputRange: [0, 1, 4],
        outputRange: [0, .3, 1],
        extrapolate: 'clamp'
    })}
strokeWidth={this.state.animation.interpolate({
        inputRange: [0, 5.99, 6, 7, 10],
        outputRange: [0, 0, 3, 2, 0],
        extrapolate: 'clamp'
    })}
/>
Comments
Write a Comment