React Native'de Akıcı Animasyonlar ve UX Optimizasyonu

Yunus Emre Güzel
3 Ocak 202515 dkReact Native
React Native'de Akıcı Animasyonlar ve UX Optimizasyonu

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

Kaynaklar