
react-native-reanimated 布局动画实战用 entering / exiting / layout 打造流畅的参与者列表【免费下载链接】react-native-reanimatedReact Natives Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated导读本文以 react-native-reanimated 官方 2.x 教程《How to easily animate your screens and components》教程主文档为骨架完整讲解如何在已有 React Native 界面中仅通过给Animated.View挂上entering、exiting、layout三个属性就让组件入场、离场、位置重排全部拥有流畅动画。读完本文你将掌握预置布局动画如LightSpeedInLeft/LightSpeedOutRight的选型与接入方法、Layout.springify()等链式修饰符的用法以及这些动画在库源码中的底层实现原理。:::tip 布局动画Layout Animations自 reanimated v2.3.0 起可用本教程对应的源码与文档均位于当前仓库。 :::背景布局动画能解决什么在 React Native 中组件出现、消失、改变位置这三类变化默认是瞬时的。react-native-reanimated 的布局动画Layout Animations允许开发者使用预置动画为这三类变化添加过渡效果组件出现appearance——通过entering属性组件消失disappearance——通过exiting属性布局变化layout changes——通过layout属性。库内置了大量可直接使用的预置动画如果预置动画无法满足需求还可以基于动画构建器自行实现定制动画详见仓库 layoutReanimation/defaultAnimations 目录下的各类实现。为了让读者直观理解用法官方教程选择了一个参会者列表Participant list界面用户可以输入姓名添加参与者也可以点击 Remove 删除参与者。本文将沿用该示例逐步为其加上动画。原始界面只有普通 View 的 Participant 组件教程的第一步先给出一个尚未动画化的完整示例代码来源_fullCode.mdfunction Participant({ name, onRemove, }: { name: string; onRemove: () void; }) { return ( View style{[styles.participantView]} Text{name}/Text Button titleRemove colorred onPress{onRemove} / /View ); } export default function AnimatedListExample() { const [inputValue, setInputValue] useState(); const [participantList, setParticipantList] useStateEventParticipant[]( [] ); const addParticipant () { setParticipantList( [{ name: inputValue, id: Date.now().toString() }].concat(participantList) ); setInputValue(); }; const removeParticipant (id: string) { setParticipantList( participantList.filter((participant) participant.id ! id) ); }; return ( View style{[styles.listView]} ScrollView style{[{ width: 100% }]} {participantList.map((participant) ( Participant key{participant.id} name{participant.name} onRemove{() removeParticipant(participant.id)} / ))} /ScrollView View style{[ styles.bottomRow]} View style{[styles.textInput]} TextAdd participant: /Text TextInput placeholderName value{inputValue} onChangeText{setInputValue} / /View Button titleAdd disabled{inputValue } onPress{addParticipant} / /View /View ); }从代码可见列表由participantList状态驱动addParticipant向数组头部插入新成员removeParticipant按id过滤掉被删除的成员。每个列表项Participant被包裹在单个View中这正是我们挂载动画的位置。需要注意一个关键前提只能给 Reanimated 提供的组件例如Animated.View或通过createAnimatedComponent包装过的组件添加布局动画。普通 RN 组件如View不会响应这些属性。关键约束key 必须稳定列表渲染处对应_participantComponent.md为每个Participant传入了key{participant.id}ScrollView style{[{ width: 100% }]} {participantList.map((participant) ( Participant key{participant.id} name{participant.name} onRemove{() removeParticipant(participant.id)} / ))} /ScrollView当删除一个参与者时React 依据key决定哪个组件被卸载、哪些组件保留并移动位置——前者触发exiting后者触发layout。因此key必须是稳定且唯一的标识这里用Date.now().toString()生成。Step 1把 View 换成 Animated.View首先从 reanimated 导入Animated并把Participant内部包裹内容的View替换为Animated.View使其具备接收布局动画属性的能力对应_step1.mdimport Animated from react-native-reanimated; ... function Participant({ name, onRemove }: { name: string; onRemove: () void }) { return ( Animated.View style{[styles.participantView]} Text{name}/Text Button titleRemove colorred onPress{onRemove} / /Animated.View ); }这一步本身不产生动画它是后续所有entering/exiting/layout属性生效的基础。Step 2添加 entering 入场动画LightSpeedInLeftentering动画在组件首次挂载时播放。库提供了大量预置入场动画官方教程选用LightSpeedInLeft光速从左侧飞入并把它作为entering属性传入对应_step2.mdimport Animated, { LightSpeedInLeft } from react-native-reanimated; ... Animated.View entering{LightSpeedInLeft} style{[styles.participantView]} Text{name}/Text Button titleRemove colorred onPress{onRemove} / /Animated.View添加后每当addParticipant向列表头部插入新成员新条目就会从屏幕左侧以光速效果飞入并最终停在目标位置。源码视角LightSpeedInLeft 的底层曲线从源码结构看LightSpeedInLeft并不是一个纯位移动画而是位移 倾斜skew 透明度三者组合。它在 defaultAnimations/Lightspeed.ts 中实现为ComplexAnimationBuilder的子类build()返回一个 worklet 动画描述初始状态opacity: 0translateX: -values.windowWidth从屏幕左边缘外开始skewX: 45deg动画过程opacity用withTiming在完整duration内从 0 变到 1translateX用animation(targetTranslateX, { ...config, duration: duration * 0.7 })在70% 时长内平移到 0skewX使用withSequence分三段-10deg70% 时长→5deg15% 时长→0deg15% 时长形成飞入时先歪斜、再回正的视觉减速感。也就是说LightSpeedInLeft的光速质感来自 translateX 快速推进 skewX 的摆动回正 opacity 淡入的三重叠加。Web 端则用等价的关键帧描述见 layoutReanimation/web/animation/Lightspeed.ts从translateX: -100vw, skewX: 45deg, opacity: 0起步经过 70% 处的skewX: -10deg、85% 处的skewX: 5deg最终回到skewX: 0deg默认时长 0.3s。Step 3添加 layout 布局过渡Layout.springify当列表项由于其他成员的增删而发生位置变化时layout属性负责让位置跳变变成平滑移动。官方教程使用Layout构建器并链式调用.springify()让位移采用弹簧动画对应_step3.mdimport Animated, { LightSpeedInLeft, Layout } from react-native-reanimated; ... Animated.View entering{LightSpeedInLeft} layout{Layout.springify()} style{[styles.participantView]} Text{name}/Text Button titleRemove colorred onPress{onRemove} / /Animated.View.springify()的链式修饰符机制springify()来自 layoutReanimation/animationBuilder/ComplexAnimationBuilder.tsstatic springifyT extends AnimationConfigBuilderClass(...): T { ... return instance.springify(duration); } springify(duration?: number): this { this.durationV duration; this.type withSpring as AnimationFunction; return this; }可见springify()做的事是把内部动画函数切换为withSpring并可传入可选参数duration默认使用withSpring自身的默认配置。同理Layout还支持.duration()、.delay()、.easing()等修饰符它们都采用返回this的链式设计因此可以连续调用Layout.springify().duration(500)。加入 layout 动画后删除一个中间成员时其余成员会弹簧式地滑到新位置而不是瞬间跳位。Step 4添加 exiting 离场动画LightSpeedOutRight——本教程的收尾最后一步即本文关联文档_step4.md为列表项添加exiting 离场动画。exiting在组件被卸载时播放。库中同样有大量预置离场动画教程选用LightSpeedOutRight光速向右飞出import Animated, { LightSpeedInLeft, LightSpeedOutRight Layout } from react-native-reanimated; ... function Participant({ name, onRemove, }: { name: string; onRemove: () void; }) { return ( Animated.View entering{LightSpeedInLeft} exiting{LightSpeedOutRight} layout{Layout.springify()} style{[styles.participantView]} Text{name}/Text Button titleRemove colorred onPress{onRemove} / /Animated.View ); } ...至此单个Animated.View同时具备了三类动画属性值触发时机效果enteringLightSpeedInLeft组件挂载添加参与者从左侧光速飞入exitingLightSpeedOutRight组件卸载点击 Remove向右光速飞出layoutLayout.springify()列表布局变化其他成员增删弹簧式平滑挪位源码视角LightSpeedOutRight 如何飞出去与入场版本对称LightSpeedOutRight同样定义在 defaultAnimations/Lightspeed.ts初始状态opacity: 1translateX: 0skewX: 0deg组件停留在原位动画目标opacity用animation(targetValues?.opacity ?? 0, config)淡出到 0translateX通过animateTransformToValues移动到values.windowWidth屏幕右边缘外skewX倾斜到-45deg。Web 端对应关键帧见 layoutReanimation/web/animation/Lightspeed.ts从translateX: 0vw, skewX: 0deg, opacity: 1过渡到translateX: 100vw, skewX: -45deg, opacity: 0默认时长 0.3s。离场方向与入场方向左侧相反形成从哪边进、往另一边出的对称视觉。组合效果与完整代码将四个步骤叠加后整个列表的交互体验就完成了添加成员时新条目光速飞入删除成员时该条目光速飞出同时其余成员弹簧式地滑入空位。官方教程的总结是Adding layout animations to our list was super easy and the user experience is way better。若要对照完整成品可参考教程各分步文件完整初始代码见_fullCode.mdParticipant 组件结构见_participantInternals.md逐步演进见_step1.md、_step2.md、_step3.md、_step4.md。实用要点与注意事项动画目标必须是 Animated 组件普通View不响应entering/exiting/layout要么直接用Animated.View要么用createAnimatedComponent包装自定义组件。key的稳定性决定动画正确性列表项的key必须唯一且稳定React 才能正确区分卸载触发 exiting与挪位触发 layout。预置动画可链式定制LightSpeedInLeft、LightSpeedOutRight、Layout都是动画构建器支持.duration()、.delay()、.springify()等链式修饰符从 ComplexAnimationBuilder.ts 源码看springify本质是把底层动画切换为withSpring。进入 / 布局 / 退出互不冲突三者分别监听挂载、布局变化、卸载可以在同一个组件上同时启用彼此独立工作。更多预置动画除 Lightspeed 系列外仓库 layoutReanimation/defaultAnimations 目录还包含Fade、Slide、Zoom、Rotate、Bounce、Stretch、Flip等系列入口导出可按需挑选入场 / 出场 / 布局过渡Web 端对应实现集中在 layoutReanimation/web/animation 目录。结语通过本教程的四个步骤可以清楚地看到 react-native-reanimated 布局动画的设计哲学把出现、消失、布局变化三种高频交互降维成三个组件属性开发者只需要选择预置动画并可选地链式微调即可获得丝滑的列表动效无需手工管理定时器或共享值。若需要更精细的控制还可基于动画构建器自行实现entering/exiting/layout的定制逻辑。【免费下载链接】react-native-reanimatedReact Natives Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考