Animated API là built-in animation system của RN. Hoạt động bằng cách tạo Animated.Value → drive property changes qua Animated.timing/spring/decay.
const fadeAnim = useRef(new Animated.Value(0)).current;
const fadeIn = () => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
};
<Animated.View style={{ opacity: fadeAnim }}>
<Text>Hello</Text>
</Animated.View>
| Type | Mô tả | Use case |
|---|---|---|
timing | Duration-based, easing curves | Fade, slide, controlled transitions |
spring | Physics-based, bouncy | Button press, card flip, natural motion |
decay | Deceleration từ initial velocity | Fling gesture, scroll momentum |
Animated.parallel([
Animated.timing(fadeAnim, { toValue: 1, duration: 300, useNativeDriver: true }),
Animated.timing(slideAnim, { toValue: 0, duration: 300, useNativeDriver: true }),
]).start();
Animated.sequence([
Animated.timing(a, { toValue: 1, duration: 200, useNativeDriver: true }),
Animated.timing(b, { toValue: 1, duration: 200, useNativeDriver: true }),
]).start();
Animated.stagger(100, [
Animated.timing(item1, { toValue: 1, duration: 300, useNativeDriver: true }),
Animated.timing(item2, { toValue: 1, duration: 300, useNativeDriver: true }),
Animated.timing(item3, { toValue: 1, duration: 300, useNativeDriver: true }),
]).start();
Tại sao mượt: Khi useNativeDriver: true, animation chạy hoàn toàn trên UI thread — không cần giao tiếp qua Bridge mỗi frame. JS thread có thể bận mà animation vẫn 60fps.
Limitations: Chỉ support non-layout properties:
opacity, transform (translateX/Y, scale, rotate)width, height, marginTop, backgroundColor, borderRadiusWorkaround: Dùng transform: [{ scaleX }] thay vì animate width. Hoặc dùng Reanimated.
const rotation = scrollY.interpolate({
inputRange: [0, 100],
outputRange: ['0deg', '360deg'],
extrapolate: 'clamp',
});
<Animated.View style={{ transform: [{ rotate: rotation }] }} />
Extrapolation:
| Value | Behavior khi vượt inputRange |
|---|---|
extend | Tiếp tục theo tỷ lệ (default) |
clamp | Dừng tại outputRange boundary |
identity | Trả về input value trực tiếp |
Tại sao cần Reanimated?
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
withSpring,
} from 'react-native-reanimated';
const offset = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: offset.value }],
backgroundColor: interpolateColor(
offset.value,
[0, 200],
['#6c63ff', '#00d4aa']
),
}));
const moveRight = () => {
offset.value = withSpring(200, { damping: 15 });
};
<Animated.View style={[styles.box, animatedStyle]} />
SharedValue vs Animated.Value:
| Animated.Value | SharedValue | |
|---|---|---|
| Thread | JS thread (hoặc UI với useNativeDriver) | UI thread (worklet) |
| Properties | Limited (useNativeDriver) | Tất cả properties |
| Sync | Async | Synchronous trên UI thread |
| Gesture | Hạn chế | Tích hợp tốt với Gesture Handler |
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
const pan = Gesture.Pan()
.onStart(() => {
context.value = { x: offsetX.value, y: offsetY.value };
})
.onUpdate((event) => {
offsetX.value = context.value.x + event.translationX;
offsetY.value = context.value.y + event.translationY;
})
.onEnd(() => {
offsetX.value = withSpring(0);
offsetY.value = withSpring(0);
});
<GestureDetector gesture={pan}>
<Animated.View style={animatedStyle} />
</GestureDetector>
Simple API cho layout transitions — animate changes khi state update gây layout change.
import { LayoutAnimation, UIManager, Platform } from 'react-native';
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental?.(true);
}
const toggleExpand = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setExpanded(!expanded);
};
Khi nào dùng: Simple expand/collapse, add/remove items. Không dùng cho complex gesture animations.
import LottieView from 'lottie-react-native';
<LottieView
source={require('./animations/loading.json')}
autoPlay
loop
style={{ width: 200, height: 200 }}
/>
Use cases: Loading indicators, success checkmarks, onboarding illustrations, empty states. Tìm animations tại lottiefiles.com.
Dùng @gorhom/bottom-sheet — dựa trên Reanimated + Gesture Handler. Snap points, backdrop, keyboard handling built-in.
Animated gradient placeholder khi data loading. Libraries: react-native-skeleton-placeholder, rn-placeholder. Hoặc tự build với Reanimated + LinearGradient.
Smooth transition giữa screens sharing cùng element (ảnh, card). Dùng react-native-shared-element hoặc Reanimated 3 shared transitions.
const translateX = useSharedValue(0);
const pan = Gesture.Pan()
.activeOffsetX([-10, 10])
.failOffsetY([-8, 8])
.onUpdate((event) => {
translateX.value = Math.max(-96, Math.min(0, event.translationX));
})
.onEnd(() => {
translateX.value = withSpring(translateX.value < -48 ? -96 : 0);
});
const rowStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
Điểm cần nhớ: activeOffsetX và failOffsetY giúp swipe ngang không tranh gesture với scroll dọc của FlatList.
useNativeDriver: true gửi animation config sang native (UI thread) 1 lần → native code drive animation mỗi frame mà không cần Bridge communication. Kết quả: 60fps ngay cả khi JS thread bận.
Limitations: Chỉ animate non-layout properties (opacity, transform). Không animate width/height/padding/margin/backgroundColor vì cần layout recalculation — phải dùng Reanimated thay thế.
Animated API: Simple animations (fade, slide), built-in (không cần extra dependency), đủ cho hầu hết UI transitions.
Reanimated: Complex gesture-driven animations, animate mọi property, cần synchronous response (swipe cards, drag-drop, parallax scroll), cần integrate với Gesture Handler.
Rule: Bắt đầu với Animated API. Chuyển sang Reanimated khi cần animate layout properties hoặc gesture-driven logic phức tạp.
Worklet = JS function được compile sang code chạy được trên UI thread. Đánh dấu bằng 'worklet'; directive. Cho phép animation logic chạy synchronous trên UI thread mà không cần Bridge → 60fps. Tương tự Web Workers nhưng cho animations.
timing: Duration-based, predictable. Bạn control chính xác bao lâu animation chạy + easing curve. Dùng cho: fade, slide, controlled transitions.
spring: Physics-based, "bouncy". Config bằng damping/stiffness/mass. Không có fixed duration — chạy cho tới khi đạt equilibrium. Dùng cho: interactive animations, button press, tự nhiên hơn.
Map 1 Animated.Value range sang output range khác. Ví dụ: scroll position (0-300) → opacity (1-0), hoặc scroll → rotation (0deg-360deg). Rất hữu ích cho scroll-based animations, parallax effects, progress indicators.
LayoutAnimation: Declarative, simple API. Animate layout changes (add/remove/resize) tự động. Nhưng: ít control, không gesture-driven, Android cần enable experimental.
Animated API: Imperative, fine-grained control. Manual setup nhưng linh hoạt hơn nhiều.
useNativeDriver: true rồi không chạy.Case: Swipe-to-delete trong notification list hoạt động tốt khi test riêng, nhưng trong FlatList thật thì kéo dọc bị nhầm thành swipe ngang.
Cách xử lý: Dùng Gesture Handler với ngưỡng activeOffsetX/failOffsetY, chỉ mở action khi vượt threshold, và đóng row đang mở khi row khác được swipe.
Chọn API theo độ phức tạp: Animated cho transition đơn giản, Reanimated + Gesture Handler cho tương tác theo gesture, thư viện có sẵn cho bottom sheet/shared element nếu requirement phổ biến.
useNativeDriver không animate layout property?