在 Android 开发中,我们习惯使用 ObjectAnimator、ValueAnimator 或 Compose 中的 animate*AsState / Transition 来构建丝滑的动画。处理手势时,我们会重写 onTouchEvent 或者在 Compose 中使用 pointerInput (如 detectDragGestures)。
在 React Native (RN) 早期的 Animated API 中,由于 JS 线程和 UI 线程(原生端)是分离的,复杂的动画和手势容易受 JS 帧率(丢帧、卡顿)的影响。
为了彻底打破这个性能瓶颈,React Native Reanimated 3 和 React Native Gesture Handler 诞生了。本篇将为 Android 开发者揭秘它们为什么能实现 60fps/120fps 的原生级丝滑体验。
1. 痛点:JS 与 UI 线程的通信延迟
回想一下 Android 开发。如果我们在后台线程(非主线程)去极其频繁地更新 UI 属性,或者依赖 IPC(进程间通信)来决定下一帧怎么画,一定会卡顿。
早期的 RN 就是面临这个问题:JS 线程负责逻辑,UI 线程负责渲染。手指滑动 -> Native UI 线程 -> 经过 Bridge -> JS 线程计算新的位置 -> 经过 Bridge -> Native UI 线程更新 View。这中间的延迟在快速滑动时会造成明显的滞后。
2. Reanimated 的杀手锏:Worklets
Reanimated 3 的核心概念是 Worklets(工作片段)。
可以把 Worklet 理解为一小段 被提前编译并下发到 Native UI 线程执行的 JS/C++ 代码。
- 在 Android 视角下 :这就像是你写了一段协程逻辑,但这段逻辑被强制绑定在了
MainThread(甚至在 RenderThread 的一部分),完全绕过了跨线程通信的开销。 - 在 Compose 视角下 :使用 Worklet 定义的动画样式,非常类似于 Compose 里的
Modifier.graphicsLayer阶段。在 Compose 中,只要只修改graphicsLayer的属性(如translationX,alpha),就不会触发 Recomposition(重组),而是直接在绘制阶段生效,因此极为高效。Reanimated 就是通过 Worklet 直接在 UI 线程修改原生 View 的属性,跳过了 React 的 JS Render 阶段。
代码对比
Compose 动画:
ini
var offsetX by remember { mutableStateOf(0f) }
val animatedOffsetX by animateFloatAsState(targetValue = offsetX)
Box(modifier = Modifier.graphicsLayer { translationX = animatedOffsetX })
Reanimated 3 动画:
javascript
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
export default function MyComponent() {
// SharedValue 是跨越 JS 和 UI 线程的共享状态
// 类似于 Compose 中的 remember { mutableStateOf() }
const translateX = useSharedValue(0);
// useAnimatedStyle 里的代码是一个 Worklet,运行在 UI 线程!
const animatedStyles = useAnimatedStyle(() => {
return {
// 类似于 Modifier.graphicsLayer
transform: [{ translateX: translateX.value }],
};
});
return (
<Animated.View style={[styles.box, animatedStyles]} />
);
}
// JS 线程触发动画:translateX.value = withSpring(100);
在这里,useSharedValue 就如同 Android 的 LiveData 或 Compose 的 State。当它改变时,绑定它的 useAnimatedStyle 会在 UI 线程 同步执行,不会阻塞 JS 线程。
3. Gesture Handler:精准的手势拦截
在 Android 中,手势拦截是一个树形结构(onInterceptTouchEvent 和 onTouchEvent)。Compose 使用了 Modifier.pointerInput 进行声明式手势处理。
react-native-gesture-handler 抛弃了 RN 内置的、基于 JS 线程的触摸系统,直接在原生层使用底层的 MotionEvent 进行手势识别,然后将识别结果发送给需要的监听者。
结合 Reanimated,我们可以实现 零 JS 线程开销的手势拖拽:
ini
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';
function DraggableBox() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
// 定义平移手势
// 类似于 Compose 的 detectDragGestures
const panGesture = Gesture.Pan()
.onUpdate((e) => {
// 这些回调也是 Worklets,在 UI 线程实时执行!
translateX.value = e.translationX;
translateY.value = e.translationY;
})
.onEnd(() => {
// 松手时弹回原位 (类似 SpringAnimation)
translateX.value = withSpring(0);
translateY.value = withSpring(0);
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
],
}));
return (
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}
概念对比:
Gesture.Pan():非常类似 Android 的GestureDetector(识别 Fling/Scroll) 或者 Compose 中的pointerInput { detectDragGestures { ... } }。.onUpdate:在这个闭包中直接修改SharedValue。因为是由 Gesture Handler 原生识别,加上 Reanimated 的 UI 线程执行,这段手势更新代码是 绝对同步且无延迟的 ,表现与纯 Android 编写的View.setTranslationX()一模一样。
4. 总结
对于 Android 开发者,理解 Reanimated 和 Gesture Handler 非常简单:
- Shared Value = 跨线程的
State<T>或ValueAnimator变量。 - Worklet (
useAnimatedStyle,onUpdate) = 强制运行在主线程 (Main/UI Thread) 更新 View 属性的代码块,避免了跨进程/跨线程通信。 - Gesture Handler = 原生层的
OnTouchListener和GestureDetector,代替了 JS 层低效的手势处理。
掌握了这两个库,你就能在 React Native 中构建出媲美,甚至超越部分原生应用性能的复杂手势和丝滑动画体验!