Простая анимация движения вверх в React Native

Я новичок в React Native (Expo Go) и пытаюсь создать следующую анимацию:

При запуске приложения изображение начинается в произвольной позиции примерно на середине экрана и перемещается вверх, пока не выйдет за пределы. После выхода за пределы изображения изображение теперь появляется в нижней части экрана.

У меня возникла проблема с тем, что изображение начиналось ТОЛЬКО в середине первой анимации.

Я безуспешно экспериментировал с различными реанимированными библиотеками, такими как withRepeat и withTiming.

Умерло ли Create-React-App?
Умерло ли Create-React-App?
В этом документальном фильме React.dev мы исследуем, мертв ли Create React App (CRA) и какое будущее ждет этот популярный фреймворк React.
Освоение React Native: Пошаговое руководство для начинающих
Освоение React Native: Пошаговое руководство для начинающих
React Native - это популярный фреймворк с открытым исходным кодом, используемый для разработки мобильных приложений. Он был разработан компанией...
В чем разница между react native и react ?
В чем разница между react native и react ?
React и React Native - два популярных фреймворка для создания пользовательских интерфейсов, но они предназначены для разных платформ. React - это...
От React к React Native: Руководство для начинающих по разработке мобильных приложений с использованием React
От React к React Native: Руководство для начинающих по разработке мобильных приложений с использованием React
Если вы уже умеете работать с React, создание мобильных приложений для iOS и Android - это новое приключение, в котором вы сможете применить свои...
Хуки React: что это такое и как их использовать
Хуки React: что это такое и как их использовать
Хуки React - это мощная функция библиотеки React, которая позволяет разработчикам использовать состояние и другие возможности React без написания...
1
0
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Попробуйте вот так Ссылка на закуску: https://snack.expo.dev/@cyberking40/7d4562

import React, { Component } from 'react';
import { View, Image, Animated, Dimensions } from 'react-native';

const { height } = Dimensions.get('window');

class AnimatedImage extends Component {
  constructor(props) {
    super(props);
    this.state = {
      position: new Animated.Value(height / 2), // Initial position halfway up the screen
    };
  }

  componentDidMount() {
    this.moveImage();
  }

  moveImage = () => {
    Animated.timing(this.state.position, {
      toValue: -height, // Move the image to the top of the screen
      duration: 3000, // Adjust the duration as needed
      useNativeDriver: false,
    }).start(() => {
      // When animation completes, reset position and start again
      this.state.position.setValue(height); // Move the image to the bottom of the screen
      this.moveImage();
    });
  };

  render() {
    const { position } = this.state;
    return (
      <Animated.View style = {{ position: 'absolute', top: position }}>
        <Image source = {require('./assets/snack-icon.png')} />
      </Animated.View>
    );
  }
}

export default AnimatedImage;

Сделать это в реанимации довольно сложно. Вы можете использовать withRepeat и withSequence, чтобы получить плавную зацикленную анимацию; но только после того, как вы завершили первую прокрутку экрана вверх. А чтобы изображение начиналось в случайном месте в середине экрана, вы можете использовать useSharedValue вместе с useWindowDimensions, чтобы получить положение изображения рядом с средней отметкой:

const { width, height } = useWindowDimensions();
  const remainingWidth = width - imageSize;
  const remainingHeight = height - imageSize;
  const imageX = useSharedValue(
    getRandomIntWithinRange(remainingWidth * 0.5, 50)
  );
  const imageY = useSharedValue(
    getRandomIntWithinRange(remainingHeight * 0.5, 100)
  );

Теперь мы настраиваем анимированный стиль, используя width и height, чтобы позиция не выходила за пределы экрана:

const imageStyle = useAnimatedStyle(() => {
    return {
      // since we will allow imageY to exceed
      // the the height of the parent view
      // we need to bound it
      bottom: imageY.value % height,
      left: imageX.value%width,
    };
  });

А затем, поскольку вы, возможно, захотите использовать аналогичную зацикленную анимацию для значения x, я решил создать функцию для зацикливания (distRatio используется для точной настройки продолжительности анимации):

export const loopAnimationAtValue = (animValue, maxValue,distRatio=1.8) => {
  const distToReset = maxValue - animValue.value;
  // withRepeat combined withSequence will allow you to
  // get indefinite animation; but to get smooth transitions
  // first move image off screen the first time
  animValue.value = withTiming(
    animValue.value + distToReset,
    { duration: distToReset * distRatio },
    (finished) => {
      // once image is offscreen just continously scroll by maxValue
      if (!finished) return;
      animValue.value = withRepeat(
        withSequence(
          withTiming(animValue.value + maxValue, {
            duration: maxValue * distRatio,
          })
        ),
        -1
      );
    }
  );
};

И собираем все это вместе (демо):

import {
  Text,
  SafeAreaView,
  StyleSheet,
  Image,
  Button,
  View,
  useWindowDimensions,
} from 'react-native';
import { useEffect, useMemo } from 'react';
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  cancelAnimation,
  withTiming,
} from 'react-native-reanimated';
import { getRandomIntWithinRange, loopAnimationAtValue } from './helpers';

const imageSize = 100;
// multiple distance traveled by this value to get
// get animation duration
const distRatio = 1.8;
const AnimatedImage = Animated.createAnimatedComponent(Image);
export default function App() {
  const { width, height } = useWindowDimensions();
  const remainingWidth = width - imageSize;
  const remainingHeight = height - imageSize;
  const imageX = useSharedValue(
    getRandomIntWithinRange(remainingWidth * 0.5, 50)
  );
  const imageY = useSharedValue(
    getRandomIntWithinRange(remainingHeight * 0.5, 100)
  );
  const imageStyle = useAnimatedStyle(() => {
    return {
      // since we will allow imageY to exceed
      // the the height of the parent view
      // we need to bound it
      bottom: imageY.value % height,
      left: imageX.value % width,
    };
  });
  const getRandomPosition = () => {
    stopAnimation();
    imageY.value = getRandomIntWithinRange(
      remainingHeight * 0.5,
      remainingHeight * 0.35
    );
    imageX.value = getRandomIntWithinRange(remainingWidth * 0.5, 50);

    loopAnimationAtValue(imageY, height,distRatio);
    // can loop x if wanted
    // loopAnimationAtValue(imageX,width,distRatio)
  };
  const stopAnimation = () => {
    cancelAnimation(imageX);
    cancelAnimation(imageY);
  };
  useEffect(() => {
    loopAnimationAtValue(imageY, height);
    // cleanup animation
    return () => {
      cancelAnimation(imageY);
    };
  }, [height, imageY]);
  return (
    <SafeAreaView style = {styles.container}>
      <AnimatedImage
        source = {require('./assets/snack-icon.png')}
        style = {[styles.image, imageStyle]}
      />
      <View style = {styles.buttonRow}>
        <Button title = "Set Random Position" onPress = {getRandomPosition} />
        <Button title = "Stop" onPress = {stopAnimation} />
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    // justifyContent: 'center',
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  image: {
    position: 'absolute',
    width: imageSize,
    height: imageSize,
  },
  buttonRow: {
    flexDirection: 'row',
    width: '100%',
    justifyContent: 'space-between',
    paddingHorizontal: 10,
  },
});

Другие вопросы по теме