基础动画的实现流程

  1. 使用支持动画的组件
<Animated.Viewstyle={[{opacity: fadeAnim, // 绑定透明度动画值},]}><Text>动画元素</Text></Animated.View>
  • Animated.View:用于创建动画容器,支持所有 View 的属性。
  • Animated.Text:用于创建文本动画,支持所有 Text 的属性。
  • Animated.Image:用于创建图片动画,支持所有 Image 的属性。
  • Animated.ScrollView:用于创建滚动视图动画,支持所有 ScrollView 的属性。
  • Animated.FlatList:用于创建列表动画,支持所有 FlatList 的属性。
  • Animated.SectionList:用于创建列表动画,支持所有 SectionList 的属性。
  1. 导入 Animated
import {Animated
} from "react-native";
  1. 声明动画变量
// 透明度动画值const fadeAnim = useState(new Animated.Value(0))[0]; // 初始值为0(完全透明)

// 透明度动画值const fadeAnim = useRef(new Animated.Value(0)).current; // 初始值为0(完全透明)
  1. 执行动画
// 淡入动画函数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.ValueXYconst moveHorizontal = () => {Animated.timing(positionAnim, {toValue: { x: positionAnim.x._value === 0 ? 150 : 0, y: 0 },duration: 1000,useNativeDriver: true,}).start();};// 垂直移动函数 - 使用 Animated.ValueXYconst moveVertical = () => {Animated.timing(positionAnim, {toValue: { x: 0, y: positionAnim.y._value === 0 ? 150 : 0 },duration: 1000,useNativeDriver: true,}).start();};// 对角线移动函数 - 使用 Animated.ValueXYconst 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.Valueconst 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.Viewstyle={[styles.box,{transform: [{ translateX: positionAnim.x },{ translateY: positionAnim.y },],},]}><Text style={styles.text}>ValueXY 平移</Text></Animated.View>{/* 使用单独 Animated.Value 的平移动画 */}<Animated.Viewstyle={[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.Viewstyle={[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.Viewstyle={[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.Viewstyle={[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.Viewstyle={[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.ValueXYconst position = useRef(new Animated.ValueXY()).current;const moveSquare = () => {// 同时对 x 和 y 进行动画Animated.timing(position, {toValue: { x: 200, y: 100 },duration: 1000,useNativeDriver: true,}).start();};
<Animated.Viewstyle={[{ transform: position.getTranslateTransform() } // 应用平移变换,返回 [{translateX: x}, {translateY: y}]]}/>

<Animated.Viewstyle={[{ 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/ 可视化查看动画效果

React Native【详解】动画_react native

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/

React Native【详解】动画_Text_02

弹性动画 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, [  // 每个动画间隔100msAnimated.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),             // 先等待500msAnimated.timing(value1, { ... }), // 然后执行第一个动画Animated.delay(300),             // 再等待300msAnimated.timing(value2, { ... }), // 最后执行第二个动画
]).start();

布局动画 LayoutAnimation

适用于自动给布局变化添加动画,特别丝滑好用

Android 中需手动开启布局动画

import { LayoutAnimation, UIManager } from 'react-native';// 启用布局动画(仅 Android 需要)
if (Platform.OS === 'android') {UIManager.setLayoutAnimationEnabledExperimental(true);
}

预设动画效果

  1. LayoutAnimation.Presets.linear
  • 匀速变化
  • 持续时间:500ms

  1. LayoutAnimation.Presets.easeInEaseOut
  • 先慢后快再慢
  • 持续时间:300ms

  1. LayoutAnimation.Presets.spring
  • 带有弹性效果
  • 持续时间:700ms
  • 弹性参数:阻尼系数 0.5,初始速度 0.8

自定义动画配置

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;

实战范例

  • 同步跟随滚动
  • 弹跳动画菜单导航