平时没有写技术文章的习惯,但是这次写红包的动画还是挺有意思的,记录一下,如果有需要,可以参考一下。
UI给的红包效果如下:
看到红包效果后,本来想直接让设计师导出json格式的动画数据,然后用lottie加载的,发现不行;想想算了,直接用JS写吧,于是就开始了漫长的写动画调UI的过程。
动画大致分为两个部分,点击拆红包后向服务端发请求,开始抖动红包动画,拿到请求响应后停止抖动,并开始拆红包动画。
UI部分的代码如下,数据部分和style部分都没给出。
/* global Promise */
// @flow
import React from 'react';
import { connect } from 'dva';
import {
Text,
View,
StyleSheet,
Animated,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native';
import { Modal } from 'antd-mobile';
import icon_close from '../assets/other/icon_close.png';
import icon_redPackage from '../assets/other/icon_redPackage.png';
import icon_redPackage_open from '../assets/other/icon_redPackage_open.png';
import icon_redPackage_close from '../assets/other/icon_redPackage_close.png';
type RedPackage = {
id: String;
name: String;
integral: number;
};
type Props = {
visible: boolean;
isOpen: boolean;
totalIntegral: number,
redPackages: RedPackage[];
currentRedPackage: ?RedPackage;
isOrderResult: boolean; // 是否是提交订单给的红包
dispatch: () => Promise<any>;
};
@connect(state => state.redPackage)
export default class RedPackageModal extends {
render() {
const { visible } = this.props;
return (
<Modal
transparent
style={styles.modal}
visible={visible}
maskClosable={false}
animationType={'fade'}
>
{this._renderContent()}
</Modal>
);
}
_renderContent = () => {
const {
visible,
redPackages,
totalIntegral,
isOpen,
isOrderResult,
currentRedPackage,
shake,
progress,
dispatch
} = this.props;
if (!visible || !redPackages || !redPackages.length) return null;
const { integral } = currentRedPackage || {};
const total = totalIntegral + (isOrderResult ? 0 : integral);
return (
<View style={styles.container}>
<TouchableWithoutFeedback onPress={() => {
if (isOpen) return;
// 开启红包抖动动效
const shakeAnimation = Animated.loop(Animated.timing(shake, {
toValue: 2,
duration: 100,
}));
shakeAnimation.start();
// 请求拆红包接口
dispatch({
type: 'redPackage/openRedPackage'
}).then(() => {
// 结束抖动动效
shakeAnimation.stop();
// 动画结束后,防止图片倾斜,将倾斜度置为0
Animated.timing(shake, {
toValue: 0,
duration: 10,
}).start();
// 开始拆红包动效
Animated.timing(progress, {
toValue: 2,
duration: 600,
}).start();
});
}}>
<Animated.View style={{
// 抖动红包
transform: [{
rotateZ: shake.interpolate({
inputRange: [0, 0.5, 1, 1.5, 2],
outputRange: ['0deg', '5deg', '0deg', '-5deg', '0deg'],
}),
}],
}}>
<Animated.Image source={icon_redPackage} style={styles.redPackage} />
<Animated.Image source={icon_redPackage_close} style={[styles.packageClose, {
// 开启红包时红包关闭图片渐渐消失
opacity: progress.interpolate({
inputRange: [0, 0.25, 0.5, 0.75, 1, 2],
outputRange: [1, 0.98, 0.9, 0.75, 0, 0],
})
}]} />
<Animated.View style={[styles.top, {
// 开启红包时开启图片向上移动,造成翻转错觉
top: progress.interpolate({
inputRange: [0, 1, 3],
outputRange: [0, -38, -38],
})
}]}>
<Animated.Image source={icon_redPackage_open} style={[styles.packageOpen, {
// 开启红包时开启图片渐渐出现
opacity: progress.interpolate({
inputRange: [0, 0.25, 0.5, 0.75, 1, 2],
outputRange: [0, 0.02, 0.1, 0.25, 1, 1],
})
}]} />
</Animated.View>
<Animated.View style={[styles.content, {
// 开启红包后白纸探出,造成白纸跳动动效
top: progress.interpolate({
inputRange: [0, 0.75, 1, 1.5, 2],
outputRange: [46, 46, 36, 0, 25],
})
}]}>
<Animated.View style={{
// 开启红包后跳转白纸高度
height: progress.interpolate({
inputRange: [0, 0.75, 1.5, 2],
outputRange: [0, 0, 255, 255],
})
}}>
<Animated.View style={[styles.wrap, {
// 白纸出现后UI渐出
opacity: progress.interpolate({
inputRange: [0, 1, 1.5, 2],
outputRange: [0, 0, 0.2, 1],
})
}]}>
<Text style={styles.title}>恭喜发财 大吉大利</Text>
<Text style={styles.integralText}>
本次获得
<Text style={styles.integral}>{integral}</Text>
积分
</Text>
<Text style={styles.desc}>{isOrderResult ? '(发货后到账)' : ''}</Text>
<Text style={styles.total}>您当前共有{total}积分</Text>
{
// 如果有多个红包,在最后一个红包显示积分商城按钮,防止流程被打断
redPackages.length <= 1 ? (
<TouchableOpacity style={styles.touchable} onPress={() => {
// 跳转到积分商城
dispatch({
type: 'redPackage/onGoToStore'
});
}}>
<Text style={styles.touchableText}>积分商城</Text>
</TouchableOpacity>
) : null
}
</Animated.View>
</Animated.View>
</Animated.View>
</Animated.View>
</TouchableWithoutFeedback>
<Text style={styles.packageNumber}>{`您有${redPackages.length}个红包待领取`}</Text>
{
// 在红包开启后显示关闭按钮
isOpen && currentRedPackage ? (
<TouchableOpacity style={styles.closeable} onPress={() => {
dispatch({
type: 'redPackage/onClose',
});
}}>
<Animated.Image source={icon_close} style={styles.close} />
</TouchableOpacity>
) : null
}
</View >
);
};
}