基础动画的实现流程
使用支持动画的组件
<Animated.View style={[ { opacity: fadeAnim, // 绑定透明度动画值 }, ]} > <Text>动画元素</Text> </Animated.View>
导入 Animated
import { Animated } from "react-native";
声明动画变量
// 透明度动画值 const fadeAnim = useState(new Animated.Value(0))[0]; // 初始值为0(完全透明)
或
// 透明度动画值 const fadeAnim = useRef(new Animated.Value(0)).current; // 初始值为0(完全透明)
执行动画
// 淡入动画函数 const fadeIn = () => { Animated.timing(fadeAnim, { toValue: 1, // 目标值为1(完全不透明) duration: 1000, // 动画持续时间1秒 useNativeDriver: false, // 是否启用原生动画驱动,建议不使用 }).start(); };
动画的启动 start
通过动画函数定义好动画后,需执行 start 方法,才会启动动画
start 方法可以传入一个动画结束/中断的回调函数
Animated.timing(value, {
toValue: 1,
duration: 1000,
}).start(({ finished }) => {
if (finished) {
console.log('动画成功完成');
} else {
console.log('动画被中断');
}
});
动画类型
平移
可以通过设置不同的样式实现
- marginLeft、marginRight、marginTop、marginBottom
- translateX、translateY
- 绝对定位时 left、right、top、bottom
import React, { useState } from "react";
import {
Animated,
Button,
SafeAreaView,
StyleSheet,
Text,
View,
} from "react-native";
const App = () => {
// 使用 Animated.ValueXY 控制二维平移
const positionAnim = useState(new Animated.ValueXY({ x: 0, y: 0 }))[0];
// 使用单独的 Animated.Value 控制一维平移
const translateX = useState(new Animated.Value(0))[0];
const translateY = useState(new Animated.Value(0))[0];
// 水平移动函数 - 使用 Animated.ValueXY
const moveHorizontal = () => {
Animated.timing(positionAnim, {
toValue: { x: positionAnim.x._value === 0 ? 150 : 0, y: 0 },
duration: 1000,
useNativeDriver: true,
}).start();
};
// 垂直移动函数 - 使用 Animated.ValueXY
const moveVertical = () => {
Animated.timing(positionAnim, {
toValue: { x: 0, y: positionAnim.y._value === 0 ? 150 : 0 },
duration: 1000,
useNativeDriver: true,
}).start();
};
// 对角线移动函数 - 使用 Animated.ValueXY
const moveDiagonal = () => {
Animated.timing(positionAnim, {
toValue: {
x: positionAnim.x._value === 0 ? 150 : 0,
y: positionAnim.y._value === 0 ? 150 : 0,
},
duration: 1000,
useNativeDriver: true,
}).start();
};
// 复合移动函数 - 使用单独的 Animated.Value
const complexMove = () => {
Animated.sequence([
// 向右移动
Animated.timing(translateX, {
toValue: 150,
duration: 1000,
useNativeDriver: true,
}),
// 向下移动
Animated.timing(translateY, {
toValue: 150,
duration: 1000,
useNativeDriver: true,
}),
// 向左移动
Animated.timing(translateX, {
toValue: 0,
duration: 1000,
useNativeDriver: true,
}),
// 向上移动
Animated.timing(translateY, {
toValue: 0,
duration: 1000,
useNativeDriver: true,
}),
]).start();
};
// 重置所有动画
const resetAnimation = () => {
positionAnim.setValue({ x: 0, y: 0 });
translateX.setValue(0);
translateY.setValue(0);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.controls}>
<Button title="水平移动" onPress={moveHorizontal} />
<Button title="垂直移动" onPress={moveVertical} />
<Button title="对角线移动" onPress={moveDiagonal} />
<Button title="复合移动" onPress={complexMove} />
<Button title="重置" onPress={resetAnimation} color="red" />
</View>
{/* 使用 Animated.ValueXY 的平移动画 */}
<Animated.View
style={[
styles.box,
{
transform: [
{ translateX: positionAnim.x },
{ translateY: positionAnim.y },
],
},
]}
>
<Text style={styles.text}>ValueXY 平移</Text>
</Animated.View>
{/* 使用单独 Animated.Value 的平移动画 */}
<Animated.View
style={[
styles.box,
{
transform: [{ translateX }, { translateY }],
backgroundColor: "green",
marginTop: 20,
},
]}
>
<Text style={styles.text}>独立值 平移</Text>
</Animated.View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
controls: {
marginBottom: 20,
width: "80%",
flexDirection: "row",
justifyContent: "space-around",
flexWrap: "wrap",
},
box: {
width: 120,
height: 120,
backgroundColor: "blue",
justifyContent: "center",
alignItems: "center",
borderRadius: 10,
},
text: {
color: "white",
fontSize: 16,
},
});
export default App;
旋转
需用 interpolate 实现角度值格式转换
import React, { useState } from "react";
import {
Animated,
Button,
SafeAreaView,
StyleSheet,
Text,
View,
} from "react-native";
const App = () => {
// 2D旋转动画值 (单位是弧度)
const rotateValue = useState(new Animated.Value(0))[0];
// 开始旋转
const startRotation = () => {
Animated.loop(
Animated.timing(rotateValue, {
toValue: rotateValue._value + Math.PI * 2, // 每次增加360度
duration: 3000, // 旋转一周的时间(毫秒)
useNativeDriver: true, // 使用原生驱动以获得更好的性能
})
).start();
};
// 停止旋转
const stopRotation = () => {
rotateValue.stopAnimation();
};
// 重置旋转
const resetRotation = () => {
rotateValue.setValue(0);
};
// 将弧度值转换为角度值
const rotateInterpolate = rotateValue.interpolate({
inputRange: [0, Math.PI * 2],
outputRange: ["0deg", "360deg"],
});
return (
<SafeAreaView style={styles.container}>
<View style={styles.controls}>
<Button title="开始旋转" onPress={startRotation} />
<Button title="停止旋转" onPress={stopRotation} />
<Button title="重置" onPress={resetRotation} color="red" />
</View>
{/* 旋转视图 */}
<Animated.View
style={[
styles.box,
{
transform: [{ rotate: rotateInterpolate }],
},
]}
>
<Text style={styles.text}>2D旋转动画</Text>
</Animated.View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
controls: {
marginBottom: 20,
width: "80%",
flexDirection: "row",
justifyContent: "space-around",
flexWrap: "wrap",
},
box: {
width: 150,
height: 150,
backgroundColor: "blue",
justifyContent: "center",
alignItems: "center",
borderRadius: 10,
},
text: {
color: "white",
fontSize: 18,
},
});
export default App;
缩放
import React, { useState } from "react";
import {
Animated,
Button,
SafeAreaView,
StyleSheet,
Text,
View,
} from "react-native";
const App = () => {
// 缩放动画值
const scaleValue = useState(new Animated.Value(1))[0]; // 初始值为1(原始大小)
// 放大动画
const zoomIn = () => {
Animated.timing(scaleValue, {
toValue: 2, // 放大到2倍大小
duration: 1000,
useNativeDriver: true,
}).start();
};
// 缩小动画
const zoomOut = () => {
Animated.timing(scaleValue, {
toValue: 0.5, // 缩小到0.5倍大小
duration: 1000,
useNativeDriver: true,
}).start();
};
// 恢复原始大小
const resetScale = () => {
Animated.timing(scaleValue, {
toValue: 1, // 恢复到原始大小
duration: 500,
useNativeDriver: true,
}).start();
};
// 脉冲动画(循环放大缩小)
const pulse = () => {
Animated.loop(
Animated.sequence([
Animated.timing(scaleValue, {
toValue: 1.2,
duration: 500,
useNativeDriver: true,
}),
Animated.timing(scaleValue, {
toValue: 0.8,
duration: 500,
useNativeDriver: true,
}),
Animated.timing(scaleValue, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
])
).start();
};
// 停止所有动画
const stopAnimation = () => {
scaleValue.stopAnimation();
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.controls}>
<Button title="放大" onPress={zoomIn} />
<Button title="缩小" onPress={zoomOut} />
<Button title="恢复" onPress={resetScale} />
<Button title="脉冲" onPress={pulse} />
<Button title="停止" onPress={stopAnimation} color="red" />
</View>
{/* 缩放视图 */}
<Animated.View
style={[
styles.box,
{
transform: [{ scale: scaleValue }],
},
]}
>
<Text style={styles.text}>缩放动画</Text>
</Animated.View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
controls: {
marginBottom: 20,
width: "80%",
flexDirection: "row",
justifyContent: "space-around",
flexWrap: "wrap",
},
box: {
width: 150,
height: 150,
backgroundColor: "blue",
justifyContent: "center",
alignItems: "center",
borderRadius: 10,
},
text: {
color: "white",
fontSize: 18,
},
});
export default App;
渐变
渐变透明度 – 淡入/淡出
import React, { useState } from "react";
import {
Animated,
Button,
SafeAreaView,
StyleSheet,
Text,
View,
} from "react-native";
const App: React.FC = () => {
// 透明度动画值
const fadeAnim = useState(new Animated.Value(0))[0]; // 初始值为0(完全透明)
// 淡入函数
const fadeIn = () => {
Animated.timing(fadeAnim, {
toValue: 1, // 目标值为1(完全不透明)
duration: 1500, // 动画持续时间(毫秒)
useNativeDriver: true,
}).start();
};
// 淡出函数
const fadeOut = () => {
Animated.timing(fadeAnim, {
toValue: 0, // 目标值为0(完全透明)
duration: 1500,
useNativeDriver: true,
}).start();
};
// 淡入淡出循环函数
const fadeInOutLoop = () => {
Animated.loop(
Animated.sequence([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1500,
useNativeDriver: true,
}),
Animated.timing(fadeAnim, {
toValue: 0,
duration: 1500,
useNativeDriver: true,
}),
])
).start();
};
// 停止动画
const stopAnimation = () => {
fadeAnim.stopAnimation();
};
// 重置动画
const resetAnimation = () => {
stopAnimation();
fadeAnim.setValue(0);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.controls}>
<Button title="淡入" onPress={fadeIn} />
<Button title="淡出" onPress={fadeOut} />
<Button title="循环淡入淡出" onPress={fadeInOutLoop} />
<Button title="停止" onPress={stopAnimation} />
<Button title="重置" onPress={resetAnimation} color="red" />
</View>
{/* 淡入淡出视图 */}
<Animated.View
style={[
styles.box,
{
opacity: fadeAnim, // 绑定透明度动画值
},
]}
>
<Text style={styles.text}>淡入淡出示例</Text>
</Animated.View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
controls: {
marginBottom: 20,
width: "80%",
flexDirection: "row",
justifyContent: "space-around",
flexWrap: "wrap",
},
box: {
width: 200,
height: 200,
backgroundColor: "blue",
justifyContent: "center",
alignItems: "center",
borderRadius: 10,
marginTop: 20,
},
text: {
color: "white",
fontSize: 20,
fontWeight: "bold",
},
});
export default App;
渐变背景色
import React, { useState } from "react";
import {
Animated,
Button,
SafeAreaView,
StyleSheet,
Text,
View,
} from "react-native";
const App: React.FC = () => {
// 渐变动画值
const gradientAnim = useState(new Animated.Value(0))[0];
// 开始渐变动画
const startGradient = () => {
Animated.loop(
Animated.sequence([
Animated.timing(gradientAnim, {
toValue: 1,
duration: 3000,
useNativeDriver: true,
}),
Animated.timing(gradientAnim, {
toValue: 0,
duration: 3000,
useNativeDriver: true,
}),
])
).start();
};
// 停止渐变动画
const stopGradient = () => {
gradientAnim.stopAnimation();
};
// 重置渐变动画
const resetGradient = () => {
stopGradient();
gradientAnim.setValue(0);
};
// 背景色插值 - 从蓝色渐变到绿色再到红色
const backgroundColor = gradientAnim.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [
"rgba(0, 122, 255, 1)", // 蓝色
"rgba(76, 217, 100, 1)", // 绿色
"rgba(255, 59, 48, 1)", // 红色
],
});
return (
<SafeAreaView style={styles.container}>
<View style={styles.controls}>
<Button title="开始渐变" onPress={startGradient} />
<Button title="停止渐变" onPress={stopGradient} />
<Button title="重置" onPress={resetGradient} color="red" />
</View>
{/* 渐变背景色视图 */}
<Animated.View
style={[
styles.box,
{
backgroundColor,
},
]}
>
<Text style={styles.text}>渐变背景色示例</Text>
</Animated.View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
controls: {
marginBottom: 20,
width: "80%",
flexDirection: "row",
justifyContent: "space-around",
flexWrap: "wrap",
},
box: {
width: 250,
height: 250,
justifyContent: "center",
alignItems: "center",
borderRadius: 15,
},
text: {
color: "white",
fontSize: 22,
fontWeight: "bold",
textAlign: "center",
},
});
export default App;
二维(矢量)动画 Animated.ValueXY
适合需要同时控制 x 和 y 坐标的场景
// 创建一个初始位置为 {x: 0, y: 0} 的 Animated.ValueXY
const position = useRef(new Animated.ValueXY()).current;
const moveSquare = () => {
// 同时对 x 和 y 进行动画
Animated.timing(position, {
toValue: { x: 200, y: 100 },
duration: 1000,
useNativeDriver: true,
}).start();
};
<Animated.View
style={[
{ transform: position.getTranslateTransform() } // 应用平移变换,返回 [{translateX: x}, {translateY: y}]
]}
/>
或
<Animated.View
style={[{ marginLeft: position.x, marginTop: position.y }]}
/>
动画函数
动画类型 | 适用场景 | 示例 |
---|---|---|
timing |
平滑过渡(如淡入淡出、平移) | 淡入淡出、缩放 |
spring |
弹性效果(如按钮点击反馈) | 弹跳、弹性拖拽 |
decay |
惯性滚动(如列表松手后继续滚动) | 滚动列表、滑动控件 |
parallel |
多属性同时动画(如边淡入边缩放) | 组合动画 |
sequence |
按顺序执行动画(如步骤引导) | 依次显示多个元素 |
stagger |
交错动画(如网格元素依次出现) | 瀑布流加载、菜单展开 |
loop |
循环动画(如加载指示器) | 旋转图标、呼吸效果 |
平滑动画 Animated.timing()
参数名 | 类型 | 描述 | 默认值 |
---|---|---|---|
toValue |
number | 动画的目标值 | 必需 |
duration |
number | 动画持续时间(毫秒) | 500 |
easing |
function | 缓动函数,控制动画的速度曲线(如加速、减速、弹跳等) | Easing.inOut(Easing.ease) |
delay |
number | 动画延迟开始的时间(毫秒) | 0 |
isInteraction |
boolean | 标记动画是否为用户交互的结果(影响动画优先级) | true |
useNativeDriver |
boolean | 是否使用原生驱动(性能优化)。 | false |
缓动函数 easing
可通过 https://easings.net/ 可视化查看动画效果
easing: Animated.Easing.ease, // 先加速后减速(默认)
取值有:
- Animated.Easing.ease:先加速后减速(默认),比较平缓
- Animated.Easing.linear:匀速运动 – 速度曲线为一次方函数(即直线)
- Animated.Easing.quad:速度曲线为二次方函数(即抛物线)
- Animated.Easing.cubic:速度曲线为三次方函数(比抛物线更陡)
- Animated.Easing.sin:速度曲线为正弦曲线
- Animated.Easing.exp:速度曲线为指数曲线
- Animated.Easing.circle:速度曲线为环形(即圆)
- Animated.Easing.bounce:弹跳效果(不会超过最终位置,类似弹力球落地撞回,回弹高度不断衰减,直到停止)
- Animated.Easing.elastic(amplitude):弹性效果
- amplitude 为可选参数,表示振幅,默认值为 1
- Animated.Easing.in(EasingFunc):加速,将参数(缓动函数)的速度曲线应用于动画的开始阶段
- Animated.Easing.out(EasingFunc):减速,将参数(缓动函数)的速度曲线应用于动画的结束阶段
- Animated.Easing.inOut(type):先加速后减速,将参数(缓动函数)的速度曲线应用于动画的开始和结束阶段
- Animated.Easing.back(overshoot):后拉特效
- overshoot 为可选参数,表示 “超出” 的程度,默认值为 1.70158,值越大,超出的距离越远,回弹效果越明显,推荐值为 3
- Animated.Easing.bezier(x1, y1, x2, y2) 创建自定义的贝塞尔曲线缓动函数
x1, y1:第一个控制点的坐标
x2, y2:第二个控制点的坐标
可通过下方链接,可视化调节曲线,获取到目标参数值
https://cubic-bezier.com/
弹性动画 Animated.spring()
模拟真实世界中弹簧的物理行为,包括弹性、阻尼和质量
使用场景
- 交互反馈:按钮点击、拖拽释放后的回弹效果
- 导航转换:页面切换时的弹性过渡
- 加载指示器:使用弹簧动画创建弹性加载效果
- 微交互:如点赞、收藏等状态变化的反馈动画
Animated.spring(scaleAnim, {
toValue: 1,
friction: 2, // 摩擦力(阻尼),值越小弹性越大
tension: 30, // 张力(弹性),值越大弹簧越硬
useNativeDriver: false,
}).start(),
共三种配置方式,只能选一种!
配置方式一:bounciness + speed
- bounciness(弹性): 越大弹的幅度越大,默认值8
- speed(速度): 越大弹得越快,默认值12
配置方式二:tension + friction
- tension(张力):控制速度,越大速度越快,默认值40
- friction(摩擦):控制能量耗散的速度,决定弹簧停止振动的快慢,越小弹得越久,默认值7
配置方式三:tension + friction
- stiffness(刚度): 弹簧刚度系数,越大越弹,默认为100
- damping(阻尼): 弹簧运动因摩擦力而受到阻尼,越小越弹,默认值10
- mass(质量): 附着在弹末端的物体的质量,越大惯性越大,动画越难停下,越小惯性越小,动画很快停下,默认值1
其他参数
- velocity(速度): 附着在弹上物体的初始速度,默认值0
- overshootClamping(过冲): 弹是否应夹紧而不应弹跳,默认为false
- restDisplacementThreshold(恢复位移阈值): 从静止状态开始的位移阈值,低于该阈值,弹簧应被视为静止状态,默认为0.001
- restspeedthreshold(弹簧静止速度),单位为像素/秒,默认为0.001
- delay(延迟):延迟后启动动画,默认为0
衰减动画 Animated.decay()
用于滚动或拖拽结束后的惯性效果。
const scrollAnim = new Animated.Value(0);
Animated.decay(scrollAnim, {
velocity: 1, // 初始速度(必选)
deceleration: 0.997, // 减速系数
useNativeDriver: false,
}).start();
deceleration 的值越小,则衰减越快,动画越快结束。
组合动画
方法 | 执行方式 | 中断行为 | 适用场景 |
---|---|---|---|
parallel |
并行执行 | 默认全部中断 | 多属性同步变化、多元素协同动画 |
sequence |
按顺序执行 | 中断当前,后续不执行 | 分步动画、依赖关系动画 |
stagger |
错开时间并行执行 | 默认全部中断 | 级联效果、列表项依次出现 |
loop |
无限循环执行 | 手动停止 | 加载动画、背景循环效果 |
同时执行 Animated.parallel
Animated.parallel([
Animated.timing(opacity, { toValue: 1 }), // 淡入
Animated.spring(scale, { toValue: 1.5 }), // 缩放
Animated.timing(rotation, { toValue: 1 }), // 旋转
]).start();
顺序执行 Animated.sequence
Animated.sequence([
Animated.timing(opacity, { toValue: 1 }), // 第一步:淡入
Animated.timing(position, { toValue: { x: 100, y: 0 } }), // 第二步:平移
Animated.spring(scale, { toValue: 0.8 }), // 第三步:缩放回弹
]).start();
错开执行 Animated.stagger
Animated.stagger(100, [ // 每个动画间隔100ms
Animated.timing(items[0].opacity, { toValue: 1 }),
Animated.timing(items[1].opacity, { toValue: 1 }),
Animated.timing(items[2].opacity, { toValue: 1 }),
]).start();
循环执行 Animated.loop
Animated.loop(
Animated.sequence([
Animated.timing(rotation, { toValue: 1, duration: 2000 }),
Animated.timing(rotation, { toValue: 0, duration: 2000 }),
])
).start();
动画控制
延迟执行 Animated.delay
Animated.delay(1000); // 创建一个1000ms(1秒)的延迟
如
Animated.sequence([
Animated.delay(500), // 先等待500ms
Animated.timing(value1, { ... }), // 然后执行第一个动画
Animated.delay(300), // 再等待300ms
Animated.timing(value2, { ... }), // 最后执行第二个动画
]).start();
布局动画 LayoutAnimation
适用于自动给布局变化添加动画,特别丝滑好用
Android 中需手动开启布局动画
import { LayoutAnimation, UIManager } from 'react-native';
// 启用布局动画(仅 Android 需要)
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
预设动画效果
自定义动画配置
LayoutAnimation.configureNext(
LayoutAnimation.create(
500, // 动画持续时间(毫秒)
LayoutAnimation.Types.easeInEaseOut, // 动画类型
LayoutAnimation.Properties.opacity // 要动画的属性
)
);
支持的动画类型:spring、linear、easeInEaseOut、easeIn、easeOut
支持的动画属性:opacity、scaleXY、translate、width、height
实战范例:显隐组件
import React, { useState } from 'react';
import {
Button,
LayoutAnimation,
StyleSheet,
UIManager,
View,
Platform,
} from 'react-native';
// 启用布局动画(仅 Android 需要)
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
const LayoutAnimationExample = () => {
const [showBox, setShowBox] = useState(true);
const toggleBox = () => {
// 配置下一次布局更新的动画
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
// 修改状态,触发布局更新
setShowBox(!showBox);
};
return (
<View style={styles.container}>
<Button title="切换显示/隐藏" onPress={toggleBox} />
{showBox && (
<View style={styles.box} />
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
box: {
width: 200,
height: 200,
backgroundColor: 'blue',
borderRadius: 10,
marginTop: 20,
},
});
export default LayoutAnimationExample;
实战范例