Обновление содержимого на экране в React Native приводит к зависанию анимированных компонентов

На главном экране моего собственного приложения есть компоненты, которые анимируются при прокрутке. Однако любое небольшое изменение на экране сбрасывает компоненты в их состояние по умолчанию, и они больше не анимируются.

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

главная /index.tsx

import React, { useEffect } from 'react'
import { View, StyleSheet, StatusBar, Image } from 'react-native'
import Animated, { Extrapolate } from 'react-native-reanimated'
import { useDispatch, useSelector } from 'react-redux'
import { bannerImage, logoImage } from '../../assets/images'
import CategoryContainer from './CategoryContainer'
import colors from '../../styles/colors'
import CstmBigDisplayButton from '../../components/CstmBigDisplayButton'
import globalStyles from '../../styles/globals'
import MenuButton from '../../components/MenuButton'
import TranslucentStatusBar from '../../components/TranslucentStatusBar'
import { getCategories } from '../../redux/actions/categoryAction'
import { RootState } from '../../redux/types'

const HEADER_MAX_HEIGHT = 430
const HEADER_MIN_HEIGHT = 100

const Home = ({ navigation }: any) => {
    const { categories } = useSelector((state: RootState) => state.categories)
    const dispatch = useDispatch()
    useEffect(() => {
        dispatch(getCategories())
    }, [])
    let scrollY = new Animated.Value(0)
    const headerHeight = Animated.interpolate(
        scrollY,
        {
            inputRange: [0, HEADER_MAX_HEIGHT],
            outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT],
            extrapolate: Extrapolate.CLAMP,
        }
    )
    const animateOverlay = Animated.interpolate(
        scrollY,
        {
            inputRange: [0, HEADER_MAX_HEIGHT / 2, HEADER_MAX_HEIGHT - 30],
            outputRange: [1, 0.5, 0.1],
            extrapolate: Extrapolate.CLAMP,
        }
    )
    const menuOpacity = Animated.interpolate(
        scrollY,
        {
            inputRange: [0, HEADER_MAX_HEIGHT - 30],
            outputRange: [0.5, 1],
            extrapolate: Extrapolate.CLAMP
        }
    )

    return (
    <>
        <TranslucentStatusBar />
        <Animated.View
            style={[
                styles.header,
                {
                    height: headerHeight,
                    elevation: 10
                }
            ]}
        >
            <View style={styles.headerBackground} >
                <Animated.View
                    style={{
                        position: 'absolute',
                        top: 0,
                        right: 0,
                        zIndex: 11,
                        marginTop: StatusBar.currentHeight,
                        opacity: menuOpacity,
                    }}
                >
                    <View style={{ margin: 16 }}>
                        <MenuButton onPress={() => {navigation.openDrawer()}}/>
                    </View>
                </Animated.View>
                <Animated.Image
                    source={bannerImage}
                    resizeMode='cover'
                    style={{
                        position: 'absolute',
                        left: 0,
                        right: 0,
                        top: 0,
                        height: headerHeight,
                        opacity: animateOverlay,
                }}/>
                <Animated.View
                    style={[ 
                        styles.overlay,
                        {
                            backgroundColor: animateOverlay
                        }
                    ]}
                >
                    <Image
                        source={logoImage}
                        style={styles.logo}
                        resizeMode='contain'
                    />
                </Animated.View>
            </View>
        </Animated.View>
        <Animated.ScrollView
            scrollEventThrottle={16}
            style={globalStyles.screenDefaults}
            onScroll={Animated.event(
                [{ nativeEvent: { contentOffset: { y: scrollY } } }],
                {
                    useNativeDriver: true,
                    listener: (event: any) => console.log(event)
                }
            )}
        >
            <View style={styles.contentContainer}>
                <CategoryContainer
                    titleStyle={[styles.general]}
                    titleText='General Categories'
                    titleTextStyle={{ color: colors.primary["500TextColor"] }}
                    categories={categories.filter((category: any) => !category.special)}
                    navigation={navigation}
                />
                <View style={styles.divider}></View>
                <CategoryContainer
                    titleStyle={[styles.special]}
                    titleText='Special Categories'
                    titleTextStyle={{ color: colors.secondary["700TextColor"] }}
                    categories={categories.filter((category: any) => category.special)}
                    navigation={navigation}
                />
            </View>
            <CstmBigDisplayButton />
        </Animated.ScrollView>
    </>
    )
}

const styles = StyleSheet.create({
    container: {
        flex: 1
    },
    header: {
        position: 'absolute',
        left: 0,
        right: 0,
        top: 0,
        height: HEADER_MAX_HEIGHT,
        backgroundColor: 'grey',
        zIndex: 10,
        alignContent: 'center',
        justifyContent: 'center'
    },
    headerBackground: {
        width: '100%',
        flex: 1,
        backgroundColor: '#FFF'
    },
    logo: {
        flex: 1,
        height: undefined,
        width: undefined,
    },
    overlay: {
        flex: 1,
        paddingTop: StatusBar.currentHeight
    },
    contentContainer: {
        flexDirection: 'row',
        flex: 1,
        paddingTop: HEADER_MAX_HEIGHT,
        paddingBottom: 24
    },
    general: {
        backgroundColor: colors.primary[500],
        color: colors.primary["500TextColor"]
    },
    special: {
        backgroundColor: colors.secondary[700],
        color: colors.secondary["700TextColor"]
    },
    divider: {
        backgroundColor: '#909090',
        height: '99%',
        width: '0.1%'
    },
    
})

export default Home

globals.ts

import { StyleSheet, StatusBar } from 'react-native' 
import theme, { standardInterval } from './theme'

const globalStyles = StyleSheet.create({
  gutterBottom: {
    marginBottom: 8
  },
  captionText: {
    fontSize: 12
  },
  screenDefaults: {
    flex: 1,
        backgroundColor: theme.pageBackgroundColor,
  },
  headlessScreenDefaults: {
        paddingTop: StatusBar.currentHeight,
        padding: 16
    },
  iconText: {
    flexDirection: 'row',
    alignItems: 'center'
  },
  iconTextMargin: {
    marginLeft: 4
  },
  label: {
    fontSize: 16,
    marginBottom: 4,
    paddingLeft: 12,
    color: '#555'
  },
  textInput: {
    padding: 0,
    fontSize: 16,
    color: '#444',
    marginLeft: 6,
    marginRight: 8,
    flex: 1
  },
  textInputContainer: {
    borderWidth: 1,
    borderColor: '#ddd',
    backgroundColor: 'white',
    borderRadius: standardInterval(0.5),
    padding: 8,
    flexDirection: 'row',
    alignItems: 'center'
  },
  helperText: {
    marginTop: 4,
    paddingLeft: 12,
    fontSize: 12,
    color: '#555'
  },
  button: {
    padding: standardInterval(1.5),
    borderRadius: standardInterval(.5),
    // marginLeft: 12,
  },
})

export default globalStyles

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

DrawerNavigation.tsx

import React, { useEffect } from 'react'
import { createDrawerNavigator, DrawerContentScrollView, DrawerItemList, DrawerItem, DrawerContentComponentProps,  } from '@react-navigation/drawer'
import { NavigationContainer } from '@react-navigation/native'
import { useSelector, useDispatch } from 'react-redux'
import AuthNavigator from './AuthNavigator'
import MainNavigator from './MainNavigator'
import theme from '../styles/theme'
import colors from '../styles/colors'
import Profile from '../screens/profile'
import { RootState } from '../redux/types'
import { verifyToken } from '../redux/actions/authAction'
import Splash from '../screens/splash'
import { logOut } from '../redux/actions/authAction'
import { getMetadata } from '../redux/actions/metadataActions'
import { getCategories } from '../redux/actions/categoryAction'

const Drawer = createDrawerNavigator()

const DrawerNavigator = () => {
  const dispatch = useDispatch()
  const { hasInitiallyLoaded, authenticationToken, authenticatedUser } = useSelector((state: RootState) => state.auth)
  useEffect(() => {
    if (!hasInitiallyLoaded) dispatch(verifyToken(authenticationToken))
  })
  useEffect(() => {
    dispatch(getMetadata())
    dispatch(getCategories())
  }, [getMetadata])
  
  return (
    <>
      {
        hasInitiallyLoaded
          ? <NavigationContainer>
              <Drawer.Navigator
                drawerStyle={{ backgroundColor: theme.pageBackgroundColor }}
                drawerContentOptions={{ activeTintColor: colors.secondary[500] }}
                drawerContent={(props: DrawerContentComponentProps) => (
                  <DrawerContentScrollView {...props}>
                    <DrawerItemList {...props} />
                    {
                      authenticatedUser
                        && <DrawerItem
                              label='log out'
                              onPress={() => {dispatch(logOut([() => props.navigation.navigate('home')]))}}
                            />
                    }
                  </DrawerContentScrollView>
                )}
              >
                <Drawer.Screen name='home' component={MainNavigator} />
                {
                  authenticatedUser
                    ? <Drawer.Screen name='profile' component={Profile} />
                    : <Drawer.Screen name='login' component={AuthNavigator} />
                }
              </Drawer.Navigator>
            </NavigationContainer>
          : <Splash />
      }
    </>
  )
}

export default DrawerNavigator

Также слушатель в Animated.event() в Animated.ScrollView не работает

Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
    {
        useNativeDriver: true,
        listener: (event: any) => console.log(event)
    }
)}

1 ответ

После помещения представления scrollY в состояние все работало так, как ожидалось.

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