React Native'de Akıcı Animasyonlar ve UX Optimizasyonu
React Native uygulamalarında akıcı animasyonlar ve iyi bir kullanıcı deneyimi, uygulamanızın başarısı için kritik öneme sahiptir. Kullanıcılar, native uygulamalardaki gibi akıcı geçişler, doğal hissettiren etkileşimler ve anlık geri bildirimler beklerler. Bu yazıda, React Native'in animasyon sistemini derinlemesine inceleyecek ve kullanıcı deneyimini optimize etmek için kullanabileceğimiz teknikleri öğreneceğiz.
Animated API ve Reanimated 2
React Native'de animasyonlar için iki temel yaklaşım vardır: yerleşik Animated API ve Reanimated 2. Animated API basit animasyonlar için yeterliyken, Reanimated 2 daha karmaşık ve performanslı animasyonlar için tercih edilir. Her ikisinin de avantajları ve kullanım alanları vardır.
Temel Animated API Kullanımı
Animated API, React Native'in yerleşik animasyon sistemidir. Basit fade, scale ve translate animasyonları için idealdir. useNativeDriver özelliği ile JS thread'inden bağımsız çalışarak performanslı animasyonlar oluşturabilirsiniz.
import { Animated, Easing } from 'react-native'; const FadeInView: React.FC = ({ children }) => { const opacity = useRef(new Animated.Value(0)).current; useEffect(() => { Animated.timing(opacity, { toValue: 1, duration: 500, easing: Easing.bezier(0.4, 0, 0.2, 1), useNativeDriver: true, }).start(); }, []); return ( <Animated.View style={{ opacity }}> {children} </Animated.View> ); };
Reanimated 2 ile İleri Seviye Animasyonlar
Reanimated 2, JavaScript thread'inden tamamen bağımsız çalışan, yüksek performanslı animasyonlar oluşturmanıza olanak tanır. SharedValue'lar ve worklet'ler sayesinde karmaşık animasyonları bile akıcı bir şekilde çalıştırabilirsiniz.
import Animated, { useAnimatedStyle, withSpring, useSharedValue, } from 'react-native-reanimated'; const AnimatedCard: React.FC = () => { const offset = useSharedValue(0); const scale = useSharedValue(1); const animatedStyles = useAnimatedStyle(() => ({ transform: [ { translateY: offset.value }, { scale: scale.value } ], })); const handlePress = () => { offset.value = withSpring(offset.value + 50, { damping: 10, stiffness: 100, }); scale.value = withSpring(1.2); }; return ( <Animated.View style={[styles.card, animatedStyles]}> <Pressable onPress={handlePress}> <Text>Bana Tıkla!</Text> </Pressable> </Animated.View> ); };
Gesture Handling ve Interaktif Animasyonlar
Gesture Handler, native platform gesture sistemini kullanarak dokunma etkileşimlerini yönetmenizi sağlar. Bu sayede kaydırma, sürükleme, yakınlaştırma gibi hareketleri native performansında işleyebilirsiniz.
PanGestureHandler ile Sürüklenebilir Kartlar
PanGestureHandler, kullanıcının parmağını ekranda sürüklemesiyle tetiklenen hareketleri yakalar. Bu örnek, sürükle-bırak işlevselliği olan bir kart komponenti oluşturur.
import { PanGestureHandler } from 'react-native-gesture-handler'; import Animated, { useAnimatedGestureHandler, useAnimatedStyle, withSpring, runOnJS, } from 'react-native-reanimated'; const DraggableCard: React.FC = () => { const translateX = useSharedValue(0); const translateY = useSharedValue(0); const gestureHandler = useAnimatedGestureHandler({ onStart: (_, ctx: any) => { ctx.startX = translateX.value; ctx.startY = translateY.value; }, onActive: (event, ctx) => { translateX.value = ctx.startX + event.translationX; translateY.value = ctx.startY + event.translationY; }, onEnd: (event) => { const velocity = Math.sqrt( event.velocityX * event.velocityX + event.velocityY * event.velocityY ); if (velocity < 500) { translateX.value = withSpring(0); translateY.value = withSpring(0); } else { translateX.value = withSpring(translateX.value + event.velocityX / 10); translateY.value = withSpring(translateY.value + event.velocityY / 10); } }, }); const animatedStyle = useAnimatedStyle(() => ({ transform: [ { translateX: translateX.value }, { translateY: translateY.value }, ], })); return ( <PanGestureHandler onGestureEvent={gestureHandler}> <Animated.View style={[styles.card, animatedStyle]}> <Text>Sürükle Beni!</Text> </Animated.View> </PanGestureHandler> ); };
Performans Optimizasyonu
Layout Animation Optimizasyonu
import { LayoutAnimation, Platform, UIManager } from 'react-native'; if (Platform.OS === 'android') { if (UIManager.setLayoutAnimationEnabledExperimental) { UIManager.setLayoutAnimationEnabledExperimental(true); } } const AnimatedList: React.FC = () => { const [items, setItems] = useState<string[]>([]); const addItem = () => { LayoutAnimation.configureNext(LayoutAnimation.Presets.spring); setItems([...items, `Item ${items.length + 1}`]); }; const removeItem = (index: number) => { LayoutAnimation.configureNext(LayoutAnimation.Presets.spring); setItems(items.filter((_, i) => i !== index)); }; return ( <View style={styles.container}> <Button title="Yeni Ekle" onPress={addItem} /> {items.map((item, index) => ( <Pressable key={index} style={styles.item} onPress={() => removeItem(index)} > <Text>{item}</Text> </Pressable> ))} </View> ); };
Memory ve FPS Optimizasyonu
import { memo, useCallback } from 'react'; import { FlatList } from 'react-native'; interface ListItemProps { item: string; onPress: (item: string) => void; } const ListItem = memo<ListItemProps>(({ item, onPress }) => ( <Pressable style={styles.item} onPress={() => onPress(item)} > <Text>{item}</Text> </Pressable> )); const OptimizedList: React.FC = () => { const [data, setData] = useState<string[]>([]); const renderItem = useCallback(({ item }: { item: string }) => ( <ListItem item={item} onPress={handleItemPress} /> ), []); const keyExtractor = useCallback((item: string) => item, []); return ( <FlatList data={data} renderItem={renderItem} keyExtractor={keyExtractor} removeClippedSubviews={true} maxToRenderPerBatch={10} updateCellsBatchingPeriod={50} windowSize={5} /> ); };
İleri Seviye Animasyon Teknikleri
Interpolasyon ve Timing Fonksiyonları
import Animated, { useAnimatedStyle, withTiming, withSequence, interpolate, Extrapolate, } from 'react-native-reanimated'; const AdvancedAnimation: React.FC = () => { const progress = useSharedValue(0); const animatedStyle = useAnimatedStyle(() => { const scale = interpolate( progress.value, [0, 0.5, 1], [1, 1.2, 1], Extrapolate.CLAMP ); const rotate = interpolate( progress.value, [0, 1], [0, 360], Extrapolate.CLAMP ); return { transform: [ { scale }, { rotate: `${rotate}deg` } ], opacity: interpolate( progress.value, [0, 0.5, 1], [1, 0.5, 1] ), }; }); const handlePress = () => { progress.value = withSequence( withTiming(0.5, { duration: 500 }), withTiming(1, { duration: 500 }) ); }; return ( <Pressable onPress={handlePress}> <Animated.View style={[styles.box, animatedStyle]} /> </Pressable> ); };
Gesture ve Animasyon Kombinasyonu
import { GestureDetector, Gesture } from 'react-native-gesture-handler'; import Animated, { useAnimatedStyle, withSpring, useSharedValue, } from 'react-native-reanimated'; const PinchableView: React.FC = () => { const scale = useSharedValue(1); const savedScale = useSharedValue(1); const rotation = useSharedValue(0); const savedRotation = useSharedValue(0); const pinchGesture = Gesture.Pinch() .onUpdate((event) => { scale.value = savedScale.value * event.scale; rotation.value = savedRotation.value + event.rotation; }) .onEnd(() => { savedScale.value = scale.value; savedRotation.value = rotation.value; }); const animatedStyle = useAnimatedStyle(() => ({ transform: [ { scale: withSpring(scale.value) }, { rotate: `${rotation.value}rad` } ], })); return ( <GestureDetector gesture={pinchGesture}> <Animated.View style={[styles.container, animatedStyle]}> <Image source={require('./image.jpg')} style={styles.image} /> </Animated.View> </GestureDetector> ); };
Kullanıcı Deneyimi İyileştirmeleri
Haptic Feedback
import * as Haptics from 'expo-haptics'; const HapticButton: React.FC = () => { const handlePress = async () => { try { await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); } catch (error) { console.log('Haptic feedback desteklenmiyor'); } }; return ( <Pressable onPress={handlePress} style={({ pressed }) => [ styles.button, pressed && styles.buttonPressed ]} > <Text>Bana Dokun!</Text> </Pressable> ); };
Sonuç
React Native'de animasyonlar ve kullanıcı deneyimi optimizasyonu, uygulamanızın kalitesini belirleyen en önemli faktörlerden biridir. Reanimated 2 ve Gesture Handler gibi modern kütüphanelerin sunduğu güçlü özellikleri kullanarak, native uygulamalar kadar akıcı ve etkileyici deneyimler oluşturabilirsiniz.
Animasyonları kullanırken dikkat edilmesi gereken en önemli nokta, performans ve kullanıcı deneyimi arasındaki dengeyi korumaktır. Her zaman useNativeDriver
kullanmaya çalışın ve karmaşık animasyonları Reanimated 2 ile JS thread'inden native tarafa taşıyın.
İlgili Etiketler: #ReactNative #Animation #UX #Performance #Mobile #JavaScript #iOS #Android