Wat is Animated?

De Animated API is een krachtige library voor het maken van vloeiende animaties in React Native. Het biedt betere performance dan gewone state updates.

Waarom Animated gebruiken?

  • Performance: Animaties lopen op de native thread (60 FPS)
  • Smooth: Geen haperingen tijdens animaties
  • Composable: Combineer meerdere animaties
  • Interruptible: Animaties kunnen worden onderbroken

Animated Components:

  • Animated.View - Geanimeerde View
  • Animated.Text - Geanimeerde Text
  • Animated.Image - Geanimeerde Image
  • Animated.ScrollView - Geanimeerde ScrollView

Basis Animatie

Elke animatie begint met een Animated.Value.

Fade In Animatie

import { useRef, useEffect } from 'react';
import { Animated, View, StyleSheet } from 'react-native';

export default function FadeInExample() {
  const fadeAnim = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }).start();
  }, []);

  return (
    <View style={styles.container}>
      <Animated.View style={[styles.box, { opacity: fadeAnim }]}>
        <Animated.Text>Fade In!</Animated.Text>
      </Animated.View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  box: {
    width: 100,
    height: 100,
    backgroundColor: '#007AFF',
    justifyContent: 'center',
    alignItems: 'center',
  },
});

Let op: Gebruik altijd useNativeDriver: true voor betere performance! Dit werkt voor opacity en transform, maar niet voor width/height/color.

Timing Animaties

Animated.timing() animaties veranderen een waarde over tijd.

Slide In Animatie

import { useRef, useEffect } from 'react';
import { Animated, View, StyleSheet } from 'react-native';

export default function SlideInExample() {
  const slideAnim = useRef(new Animated.Value(-100)).current;

  useEffect(() => {
    Animated.timing(slideAnim, {
      toValue: 0,
      duration: 500,
      useNativeDriver: true,
    }).start();
  }, []);

  return (
    <Animated.View 
      style={[
        styles.box,
        { transform: [{ translateX: slideAnim }] }
      ]}
    >
      <Text>Slide In!</Text>
    </Animated.View>
  );
}

const styles = StyleSheet.create({
  box: {
    width: 200,
    height: 100,
    backgroundColor: '#007AFF',
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: 100,
  },
});

Met Button Trigger

import { useRef } from 'react';
import { Animated, Pressable, Text, View, StyleSheet } from 'react-native';

export default function AnimatedButton() {
  const scaleAnim = useRef(new Animated.Value(1)).current;

  const animatePress = () => {
    Animated.sequence([
      Animated.timing(scaleAnim, {
        toValue: 0.8,
        duration: 100,
        useNativeDriver: true,
      }),
      Animated.timing(scaleAnim, {
        toValue: 1,
        duration: 100,
        useNativeDriver: true,
      }),
    ]).start();
  };

  return (
    <Pressable onPress={animatePress}>
      <Animated.View 
        style={[
          styles.button,
          { transform: [{ scale: scaleAnim }] }
        ]}
      >
        <Text style={styles.buttonText}>Druk mij!</Text>
      </Animated.View>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 8,
    alignItems: 'center',
  },
  buttonText: {
    color: 'white',
    fontWeight: 'bold',
  },
});

Spring Animaties

Animated.spring() maakt een verende animatie (bouncy effect).

Bounce Animatie

import { useRef } from 'react';
import { Animated, Pressable, StyleSheet } from 'react-native';

export default function BounceExample() {
  const bounceAnim = useRef(new Animated.Value(0)).current;

  const bounce = () => {
    bounceAnim.setValue(0);
    
    Animated.spring(bounceAnim, {
      toValue: 1,
      friction: 2,        // Hoe "stug" de veer is
      tension: 40,        // Hoe snel de veer terug springt
      useNativeDriver: true,
    }).start();
  };

  return (
    <Pressable onPress={bounce}>
      <Animated.View
        style={[
          styles.box,
          {
            transform: [
              { scale: bounceAnim },
              { 
                translateY: bounceAnim.interpolate({
                  inputRange: [0, 1],
                  outputRange: [0, -50],
                })
              }
            ],
          },
        ]}
      >
        <Text>Bounce!</Text>
      </Animated.View>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  box: {
    width: 100,
    height: 100,
    backgroundColor: '#007AFF',
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 8,
  },
});

Spring Properties:

  • friction - Weerstand (standaard: 7, hoger = minder bounce)
  • tension - Spanning (standaard: 40, hoger = sneller)
  • speed - Algemene snelheid
  • bounciness - Hoe bouncy (alternatief voor friction)

Interpolate

interpolate() zet één animated value om naar meerdere waarden (zoals kleur, rotatie, etc.).

Rotatie Animatie

import { useRef, useEffect } from 'react';
import { Animated, View, StyleSheet } from 'react-native';

export default function RotateExample() {
  const rotateAnim = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.loop(
      Animated.timing(rotateAnim, {
        toValue: 1,
        duration: 2000,
        useNativeDriver: true,
      })
    ).start();
  }, []);

  const spin = rotateAnim.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '360deg'],
  });

  return (
    <Animated.View
      style={[
        styles.box,
        { transform: [{ rotate: spin }] }
      ]}
    >
      <Text>🔄</Text>
    </Animated.View>
  );
}

const styles = StyleSheet.create({
  box: {
    width: 100,
    height: 100,
    backgroundColor: '#007AFF',
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 8,
  },
});

Kleur Veranderen (zonder Native Driver)

const colorAnim = useRef(new Animated.Value(0)).current;

const backgroundColor = colorAnim.interpolate({
  inputRange: [0, 1],
  outputRange: ['rgb(0, 122, 255)', 'rgb(255, 59, 48)'],
});

<Animated.View style={{ backgroundColor }}>
  <Text>Kleur verandert!</Text>
</Animated.View>

Meerdere Transforms

const anim = useRef(new Animated.Value(0)).current;

const translateX = anim.interpolate({
  inputRange: [0, 1],
  outputRange: [0, 100],
});

const scale = anim.interpolate({
  inputRange: [0, 1],
  outputRange: [1, 1.5],
});

<Animated.View
  style={{
    transform: [
      { translateX },
      { scale },
    ],
  }}
/>

Sequence & Parallel

Combineer meerdere animaties die na elkaar of tegelijk lopen.

Sequence - Na Elkaar

import { useRef } from 'react';
import { Animated, Pressable, Text, StyleSheet } from 'react-native';

export default function SequenceExample() {
  const anim = useRef(new Animated.Value(0)).current;

  const runSequence = () => {
    anim.setValue(0);
    
    Animated.sequence([
      // Eerst fade in
      Animated.timing(anim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true,
      }),
      // Dan wachten
      Animated.delay(500),
      // Dan fade out
      Animated.timing(anim, {
        toValue: 0,
        duration: 500,
        useNativeDriver: true,
      }),
    ]).start();
  };

  return (
    <Pressable onPress={runSequence}>
      <Animated.View style={[styles.box, { opacity: anim }]}>
        <Text>Sequence!</Text>
      </Animated.View>
    </Pressable>
  );
}

Parallel - Tegelijk

import { useRef } from 'react';
import { Animated, Pressable, StyleSheet } from 'react-native';

export default function ParallelExample() {
  const fadeAnim = useRef(new Animated.Value(0)).current;
  const slideAnim = useRef(new Animated.Value(-100)).current;

  const runParallel = () => {
    fadeAnim.setValue(0);
    slideAnim.setValue(-100);
    
    Animated.parallel([
      Animated.timing(fadeAnim, {
        toValue: 1,
        duration: 1000,
        useNativeDriver: true,
      }),
      Animated.timing(slideAnim, {
        toValue: 0,
        duration: 1000,
        useNativeDriver: true,
      }),
    ]).start();
  };

  return (
    <Pressable onPress={runParallel}>
      <Animated.View
        style={[
          styles.box,
          {
            opacity: fadeAnim,
            transform: [{ translateX: slideAnim }],
          },
        ]}
      >
        <Text>Parallel!</Text>
      </Animated.View>
    </Pressable>
  );
}

Loop - Oneindig Herhalen

useEffect(() => {
  Animated.loop(
    Animated.sequence([
      Animated.timing(anim, {
        toValue: 1,
        duration: 1000,
        useNativeDriver: true,
      }),
      Animated.timing(anim, {
        toValue: 0,
        duration: 1000,
        useNativeDriver: true,
      }),
    ])
  ).start();
}, []);

Praktijkvoorbeelden

Loading Spinner

import { useRef, useEffect } from 'react';
import { Animated, View, StyleSheet } from 'react-native';

export default function LoadingSpinner() {
  const spinAnim = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.loop(
      Animated.timing(spinAnim, {
        toValue: 1,
        duration: 1000,
        useNativeDriver: true,
      })
    ).start();
  }, []);

  const spin = spinAnim.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '360deg'],
  });

  return (
    <View style={styles.container}>
      <Animated.View
        style={[
          styles.spinner,
          { transform: [{ rotate: spin }] }
        ]}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  spinner: {
    width: 50,
    height: 50,
    borderRadius: 25,
    borderWidth: 5,
    borderColor: '#007AFF',
    borderTopColor: 'transparent',
  },
});

Pulse Animatie

import { useRef, useEffect } from 'react';
import { Animated, View, StyleSheet } from 'react-native';

export default function PulseAnimation() {
  const pulseAnim = useRef(new Animated.Value(1)).current;

  useEffect(() => {
    Animated.loop(
      Animated.sequence([
        Animated.timing(pulseAnim, {
          toValue: 1.2,
          duration: 800,
          useNativeDriver: true,
        }),
        Animated.timing(pulseAnim, {
          toValue: 1,
          duration: 800,
          useNativeDriver: true,
        }),
      ])
    ).start();
  }, []);

  return (
    <Animated.View
      style={[
        styles.circle,
        { transform: [{ scale: pulseAnim }] }
      ]}
    />
  );
}

const styles = StyleSheet.create({
  circle: {
    width: 100,
    height: 100,
    borderRadius: 50,
    backgroundColor: '#007AFF',
  },
});

Shake Animatie (Fout)

const shakeAnim = useRef(new Animated.Value(0)).current;

const shake = () => {
  Animated.sequence([
    Animated.timing(shakeAnim, { toValue: 10, duration: 50, useNativeDriver: true }),
    Animated.timing(shakeAnim, { toValue: -10, duration: 50, useNativeDriver: true }),
    Animated.timing(shakeAnim, { toValue: 10, duration: 50, useNativeDriver: true }),
    Animated.timing(shakeAnim, { toValue: 0, duration: 50, useNativeDriver: true }),
  ]).start();
};

<Animated.View
  style={{
    transform: [{ translateX: shakeAnim }]
  }}
>
  <TextInput style={styles.input} />
</Animated.View>

Samenvatting

Animated.Value

Start van elke animatie

Animated.timing()

Lineaire animatie

Animated.spring()

Verende animatie

interpolate()

Waarden omzetten

Je hebt nu geleerd:

  • Animated.Value gebruiken voor animaties
  • Timing en Spring animaties maken
  • Interpolate voor rotatie en kleur
  • Sequence en Parallel voor combinaties
  • Loop voor oneindige animaties
  • Praktische voorbeelden: spinner, pulse, shake