如果状态改变,动画将不会启动(用于演示的小点心)

Animation won't start if state changed (short snack for demonstration)

Snack is here

你好,我被一个愚蠢的问题困住了,我快疯了。

我只是想在屏幕聚焦(在标签栏导航中)时制作一个简单而优雅的动画。在我在屏幕上执行状态更改 之前,我的点心效果很好 。然后动画就不会开始,即使调用并执行了焦点侦听器的回调(检查日志)... WHY?

我制作了一个手动触发动画的按钮...它有效!???? 我想我把小吃说清楚了,但如果你需要更多信息,请问我。求求你,帮帮一个处于绝境的兄弟

Snack is here

如果你懒得点击零食:

import React, { useState, useEffect } from "react";
import { Text, View, Animated, Dimensions, StyleSheet, SafeAreaView, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

function HomeScreen({navigation}) {
  const initialXPos = Dimensions.get("window").height * 0.5 ;
  const xPos = new Animated.Value(initialXPos);
  const opacity = new Animated.Value(0);
  const [change, setChange] = useState(true)

   useEffect(() => {
    const unsubscribe = navigation.addListener("focus", comingFromBot);
    return unsubscribe;
  }, []);

  const comingFromBot = () => {
    xPos.setValue(initialXPos);
    opacity.setValue(0);
    Animated.parallel([
      Animated.spring(xPos, {
        toValue: 100,
        tension:3,
        useNativeDriver: true,
      }),
      Animated.timing(opacity, {
        toValue: 1,
        duration: 1000,
        useNativeDriver: true,
      }),
    ]).start();
    console.log("Animation's Fired!");
  };

  return (
    <SafeAreaView style={{flex:1}}>
      <Animated.View style={[
            styles.container,
            { transform: [{ translateY: xPos }] },
            { opacity: opacity },
          ]}>
        <Text style={{fontSize:30}}>{change ? "Home!" : "TIMMY!"}</Text>
      </Animated.View>

      {/* debug */}
      <View style={styles.fire}>
        <Button title="fire" onPress={() => comingFromBot()}/>
      </View>

      <View style={styles.change}>
        <Button title="change" onPress={() => setChange(!change)}/>
      </View>
    </SafeAreaView>
  );
}
const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center' },
  fire:{position:"absolute", width:"100%", bottom:0},
  change:{position:"absolute", width:"100%", bottom:48}
  });

function SettingsScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding:8 }}>
      <Text>{"Go to Home tab again, and notice the animation.\n\nEXCEPT if we changed the text... WHY?\n\nBUT still works if we fire the animation with the button, but after still won't work on focus detection... HOW?\n\nWorks if you hot reload / hard reload the app... HELP?"}</Text>
    </View>
  );
}

const Tab = createBottomTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <MyTabs />
    </NavigationContainer>
  );
}

它不起作用,因为你没有关注rules of hooks。您的代码中存在以下错误:

  1. 您在 useEffect 挂钩中使用了来自外部的变量,但传递了空的依赖项数组
  2. 动画值需要在 useStateuseRef 挂钩中,这样它们就不会在每次渲染时都重新创建

Then the animation just won't start, even though the callback from focus listener is called and executed (check logs)... WHY?

问题是回调是在 re-render 上的状态更新后重新创建的,因此传递给焦点侦听器的回调与渲染中的回调不再相同。并且由于您在 state/ref 中也没有动画值,因此在旧的焦点侦听器引用旧值时也会创建新的动画值。基本上你看到的日志是来自旧的监听器而不是新的。

您应该使用 official eslint plugin 并确保从中修复所有 warnings/errors 以避免此类问题。

要修复您的代码,请进行以下更改:

const [xPos] = React.useState(() => new Animated.Value(initialXPos));
const [opacity] = React.useState(() => new Animated.Value(0));
const [change, setChange] = useState(true)

useEffect(() => {
  const unsubscribe = navigation.addListener("focus", comingFromBot);
  return unsubscribe;
}, [navigation, comingFromBot]);

const comingFromBot = useCallback(() => {
  xPos.setValue(initialXPos);
  opacity.setValue(0);
  Animated.parallel([
    Animated.spring(xPos, {
      toValue: 100,
      tension:3,
      useNativeDriver: true,
    }),
    Animated.timing(opacity, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }),
  ]).start();
  console.log("Animation's Fired!");
}, [xPos, opacity]);

我基本上添加了 useCallback,修复了依赖数组,并将动画值移动到 useState 挂钩。

感谢@satya164,我终于结束了:Snack

我也希望我之前在 documentation 读过这篇文章。

HomeScreen 的代码:

// HomeScreen.js
function HomeScreen({navigation}) {
  const initialXPos = Dimensions.get("window").height * 0.5 ;
  const xPos = useRef(new Animated.Value(initialXPos)).current 
  const opacity = useRef(new Animated.Value(0)).current 
  const [change, setChange] = useState(true)

  useEffect(() => {
    const unsubscribe = navigation.addListener("focus", comingFromBot);
    return unsubscribe;
  }, [navigation, comingFromBot]);

  const comingFromBot = useCallback(() => {
    xPos.setValue(initialXPos);
    opacity.setValue(0);
    Animated.parallel([
      Animated.spring(xPos, {
        toValue: 100,
        tension:3,
        useNativeDriver: true,
      }),
      Animated.timing(opacity, {
        toValue: 1,
        duration: 1000,
        useNativeDriver: true,
      }),
    ]).start();
    console.log("Animation's Fired!");
  }, [xPos, opacity, initialXPos ]);

  return (
    <SafeAreaView style={{flex:1}}>
      <Animated.View style={[
            styles.container,
            { transform: [{ translateY: xPos }] },
            { opacity: opacity },
          ]}>
        <Text style={{fontSize:30}}>{change ? "Home!" : "TIMMY!"}</Text>
      </Animated.View>

      {/* debug */}
      <View style={styles.fire}>
        <Button title="fire" onPress={() => comingFromBot()}/>
      </View>

      <View style={styles.change}>
        <Button title="change" onPress={() => setChange(!change)}/>
      </View>
    </SafeAreaView>
  );
}