Getting Started with Animations in Vega

Vega supports three main approaches for creating animations in your Fire TV apps:

  • Animated API (built into React Native) - No additional dependencies required. Best for simple programmatic animations like fades, slides, and basic transitions. Great for learning animation fundamentals and interactive UI responses.

  • React Native Reanimated (@amazon-devices/react-native-reanimated) - Runs animations on the UI thread for better performance. Best for complex animations, gesture-based interactions, and smooth 60fps experiences. Recommended for production apps.

  • Lottie (@amazon-devices/lottie-react-native) - Renders designer-created animations exported from After Effects. Best for complex vector animations, brand animations, and illustrations.

In this tutorial, we’ll build an interactive button with the Animated API that moves to a random position when clicked. We’ll start with the hello-world template and add animation functionality to it.


Step 1: Import Animated API and useRef

Open your App.tsx file and update the imports at the top. Add useRef to the React import and add Animated to the react-native imports:

import React, {useState, useRef} from 'react';  // Add useRef here
import {
  StyleSheet, 
  Text, 
  ImageBackground, 
  View, 
  Image,
  Animated  // Add Animated here
} from 'react-native';
import {Link} from './components/Link';

What we’re adding:

  • useRef - React hook to create a persistent reference for our animation value
  • Animated - React Native’s animation library for creating smooth animations

Step 2: Define the position reference

Inside the App component, add a position reference after the useState line:

export const App = () => {
  const [image, setImage] = useState(images.vega);
  
  // Add this line:
  const position = useRef(new Animated.ValueXY({x: 0, y: 0})).current;

  const styles = getStyles();
  // ... rest of component

This creates a position reference using useRef to store the x and y coordinates. This value persists across component re-renders.


Step 3: Create the animation function

Add this function inside the App component, before the return statement:

export const App = () => {
  const [image, setImage] = useState(images.vega);
  const position = useRef(new Animated.ValueXY({x: 0, y: 0})).current;

  const styles = getStyles();

  // Add these functions:
  const getRandomNumber = (min: number, max: number) => {
    return Math.random() * (max - min) + min;
  };

  const handleImagePress = () => {
    const randomX = getRandomNumber(-100, 100);
    const randomY = getRandomNumber(-100, 100);

    Animated.timing(position, {
      toValue: {x: randomX, y: randomY},
      duration: 1000,
      useNativeDriver: false,
    }).start();
  };

  return (
    // ... rest of component

This creates an animation that moves the image to a random position over 1 second (1000 milliseconds).


Step 4: Wrap the image with Animated.View

Find the image View in your return statement and wrap it with Animated.View:

Before:

<View style={styles.image}>
  <Image source={image} />
</View>

After:

<Animated.View style={[styles.image, position.getLayout()]}>
  <Image source={image} />
</Animated.View>

The position.getLayout() applies the animated x and y values to the View’s style.


Step 5: Make the image clickable

Wrap the Image with a TouchableOpacity to make it clickable. You’ll need to import TouchableOpacity first:

Update imports:

import {
  StyleSheet, 
  Text, 
  ImageBackground, 
  View, 
  Image,
  Animated,
  TouchableOpacity  // Add this
} from 'react-native';

Update the image section:

<Animated.View style={[styles.image, position.getLayout()]}>
  <TouchableOpacity onPress={handleImagePress}>
    <Image source={image} />
  </TouchableOpacity>
</Animated.View>

Complete Updated App.tsx

Here’s what your complete App.tsx should look like:

import React, {useState, useRef} from 'react';
import {
  StyleSheet,
  Text,
  ImageBackground,
  View,
  Image,
  Animated,
  TouchableOpacity,
} from 'react-native';
import {Link} from './components/Link';

const images = {
  vega: require('./assets/vega.png'),
  learn: require('./assets/learn.png'),
  support: require('./assets/support.png'),
  build: require('./assets/build.png'),
};

export const App = () => {
  const [image, setImage] = useState(images.vega);
  const position = useRef(new Animated.ValueXY({x: 0, y: 0})).current;

  const styles = getStyles();

  const getRandomNumber = (min: number, max: number) => {
    return Math.random() * (max - min) + min;
  };

  const handleImagePress = () => {
    const randomX = getRandomNumber(-100, 100);
    const randomY = getRandomNumber(-100, 100);

    Animated.timing(position, {
      toValue: {x: randomX, y: randomY},
      duration: 1000,
      useNativeDriver: false,
    }).start();
  };

  return (
    <ImageBackground
      source={require('./assets/background.png')}
      style={styles.background}>
      <View style={styles.container}>
        <View style={styles.links}>
          <View style={styles.headerContainer}>
            <Text style={styles.headerText}>Hello World!</Text>
            <Text style={styles.subHeaderText}>
              Select one of the options below to start your Vega journey 🚀
            </Text>
          </View>
          <Link
            linkText={'Learn'}
            onPress={() => {
              setImage(images.learn);
            }}
            testID="sampleLink"
          />
          <Link
            linkText={'Build'}
            onPress={() => {
              setImage(images.build);
            }}
          />
          <Link
            linkText={'Support'}
            onPress={() => {
              setImage(images.support);
            }}
          />
        </View>
        <Animated.View style={[styles.image, position.getLayout()]}>
          <TouchableOpacity onPress={handleImagePress}>
            <Image source={image} />
          </TouchableOpacity>
        </Animated.View>
      </View>
      <View style={styles.textContainer}>
        <Text style={styles.text}>
          💡 Edit App.tsx to change this screen and then come back to see your
          edits. Click the image to see it move!
        </Text>
      </View>
    </ImageBackground>
  );
};

const getStyles = () =>
  StyleSheet.create({
    background: {
      color: 'white',
      flex: 1,
      flexDirection: 'column',
    },
    container: {
      flex: 6,
      flexDirection: 'row',
      alignItems: 'center',
    },
    headerContainer: {
      marginLeft: 200,
    },
    headerText: {
      color: 'white',
      fontSize: 80,
      marginBottom: 10,
    },
    subHeaderText: {
      color: 'white',
      fontSize: 40,
    },
    links: {
      flex: 1,
      flexDirection: 'column',
      justifyContent: 'space-around',
      height: 600,
    },
    image: {
      flex: 1,
      paddingLeft: 150,
    },
    textContainer: {
      justifyContent: 'center',
      flex: 1,
      marginLeft: 190,
    },
    text: {
      color: 'white',
      fontSize: 40,
    },
  });

Try It Out!

Build and run the project on the Vega Virtual Device:

  1. Build: npm install && npx react-native build-vega
  2. Run: vega run-app <your-package-file.vpkg>

Now when you click on the image on the right side of the screen, it will animate to a random position!

Last updated: Mar 3, 2026