如何使用状态动态更改 React Native 转换?

Posted

技术标签:

【中文标题】如何使用状态动态更改 React Native 转换?【英文标题】:How to dynamically change React Native transform with state? 【发布时间】:2019-11-03 21:05:14 【问题描述】:

我正在构建一个自定义视图,它将根据设备方向旋转其内容。这个应用程序的方向锁定为纵向,我只想旋转一个视图。它获取当前设备方向,更新状态,然后使用更新后的style=transform: [rotate: 'xxxdeg'] 呈现新组件。

我正在使用react-native-orientation-locker 来检测方向变化。

视图在第一次渲染时正确旋转。例如,如果屏幕在设备旋转时加载,它将渲染视图旋转。但是在更改设备或模拟器的方向时,视图不会旋转。它一直锁定在初始化时的 rotate 值。

似乎对transform rotate 值的更新不会改变旋转。我已经验证在渲染过程中存在新的rotate 值。我已经验证方向更改正确地更新了状态。但是当方向改变时,视图永远不会在 UI 中旋转。就好像 React Native 在渲染期间没有注意到 rotate 值的变化。

我希望rotate 值的更新会相应地轮换View,但似乎并非如此。是否有其他方法可以完成此操作,或者我在此代码中是否有错误?

编辑:rotate 是否必须是 Animated 值?

import React, useState, useEffect from 'react';
import View from 'react-native';
import Orientation from 'react-native-orientation-locker';

const RotateView = props => 
  const getRotation = newOrientation => 
    switch (newOrientation) 
      case 'LANDSCAPE-LEFT':
        return '90deg';
      case 'LANDSCAPE-RIGHT':
        return '-90deg';
      default:
        return '0deg';
    
  ;

  const [orientation, setOrientation] = useState(
    // set orientation to the initial device orientation
    Orientation.getInitialOrientation(),
  );
  const [rotate, setRotate] = useState(
    // set rotation to the initial rotation value (xxdeg)
    getRotation(Orientation.getInitialOrientation()),
  );

  useEffect(() => 
    // Set up listeners for device orientation changes
    Orientation.addDeviceOrientationListener(setOrientation);
    return () => Orientation.removeDeviceOrientationListener(setOrientation);
  , []);

  useEffect(() => 
    // when orientation changes, update the rotation
    setRotate(getRotation(orientation));
  , [orientation]);

  // render the view with the current rotation value
  return (
    <View style=transform: [rotate]>
      props.children
    </View>
  );
;

export default RotateView;

【问题讨论】:

【参考方案1】:

我遇到了同样的问题,并通过使用 react-native-reanimated 中的 Animated.View 解决了它。 (标准 react-native 包中的 Animated.View 也可能有效,但我没有检查)。我不需要使用 Animated 值,我仍然只是使用状态中的实际值,并且它起作用了。

【讨论】:

效果很好!使用 react-native-reanimated 库中的 Animated.View 解决了这个问题,并允许我的视图在每次更新角度时旋转一个角度。我从标准的 react-native 测试了默认的 Animated.View 但这没有用,所以我认为主要的 react-native 库有问题。【参考方案2】:

如果你直接从 react native 中使用Animated.Value + Animated.View,你会没事的。 有同样的问题并使用 Animated.Value 类字段解决了它(在你的情况下,我猜你会使用 useState 来解决这个问题,因为功能性 + useEffect 在更改时设置 Animated.Value 的值props.rotation),然后将其作为transform = [ rotate: animatedRotationValue ] 传递给Animated.View

这是作为 sn-p 的类组件形式:

interface Props 
    rotation: number;


class SomethingThatNeedsRotation extends React.PureComponent<Props> 
    rotation = new Animated.Value(0);
    rotationValue = this.rotation.interpolate(
        inputRange: [0, 2 * Math.PI],
        outputRange: ['0deg', '360deg'],
    );

    render() 
        this.rotation.setValue(this.props.rotation);
        const transform = [ rotate: this.rotationValue ];
        
        return (
            <Animated.View style= transform  />
        );
    

请注意,在我的示例中,我也有插值,因为我的输入是弧度,我希望它是度数。

【讨论】:

【参考方案3】:

这是我完成的处理旋转的组件。当应用程序锁定为纵向时,它将根据设备方向旋转其子项。我确信这可以清理一些,但它适用于我的目的。

import React, useState, useEffect, useRef from 'react';
import Animated, Easing, View, StyleSheet from 'react-native';
import Orientation from '../utility/constants';
import OrientationManager from '../utility/orientation';

const OrientedView = (props) => 
  const getRotation = useRef((newOrientation) => 
    switch (newOrientation) 
      case Orientation.LANDSCAPE_LEFT:
        return 90;
      case Orientation.LANDSCAPE_RIGHT:
        return -90;
      default:
        return 0;
    
  );
  const duration = 100, style = props;
  const initialized = useRef(false);
  const [orientation, setOrientation] = useState();
  const [rotate, setRotate] = useState();
  const [containerStyle, setContainerStyle] = useState(styles.containerStyle);
  // Animation kept as a ref
  const rotationAnim = useRef();
  // listen for orientation changes and update state
  useEffect(() => 
    OrientationManager.getDeviceOrientation((initialOrientation) => 
      const initialRotation = getRotation.current(initialOrientation);
      // default the rotation based on initial orientation
      setRotate(initialRotation);
      rotationAnim.current = new Animated.Value(initialRotation);
      setContainerStyle([
        styles.containerStyle,
        
          transform: [rotate: `$initialRotationdeg`],
        ,
      ]);
      initialized.current = true;
      // set orientation and trigger the first render
      setOrientation(initialOrientation);
    );
    OrientationManager.addDeviceOrientationListener(setOrientation);
    return () =>
      OrientationManager.removeDeviceOrientationListener(setOrientation);
  , []);

  useEffect(() => 
    if (initialized.current === true) 
      const rotation = getRotation.current(orientation);
      setRotate(
        rotationAnim.current.interpolate(
          inputRange: [-90, 0, 90],
          outputRange: ['-90deg', '0deg', '90deg'],
        ),
      );
      Animated.timing(rotationAnim.current, 
        toValue: rotation,
        duration: duration,
        easing: Easing.ease,
        useNativeDriver: true,
      ).start();
    
  , [duration, orientation]);

  // FIXME: This is causing unnessary animation outside of the oriented view. Disabling removes the scale animation.
  // useEffect(() => 
  //   applyLayoutAnimation.current();
  // , [orientation]);

  useEffect(() => 
    if (initialized.current === true) 
      setContainerStyle([
        styles.containerStyle,
        
          transform: [rotate],
        ,
      ]);
    
  , [rotate]);

  if (initialized.current === false) 
    return <View style=[containerStyle, style] />;
  

  return (
    <Animated.View style=[containerStyle, style]>
      props.children
    </Animated.View>
  );
;

const styles = StyleSheet.create(
  containerStyle: flex: 0, justifyContent: 'center', alignItems: 'center',
);

export default OrientedView;

【讨论】:

【参考方案4】:

这是一个错误,因为当rotate 的值更新时,旋转应该会改变。一种解决方法是将 View 的 key 属性也设置为旋转值。

例如:

return (
  <View
    key=rotate // <~~~ fix!
    style=transform: [rotate]
  >
    props.children
  </View>
)

我找到了这个解决方案here。

【讨论】:

以上是关于如何使用状态动态更改 React Native 转换?的主要内容,如果未能解决你的问题,请参考以下文章

动态更改文本输出 - React Native

React-Native iOS 状态栏

React Native刷新屏幕/组件/更改状态

尝试更改状态栏样式在 ios 中给了我错误 - React native

在 react-native 中显示/隐藏状态更改的组件时出错

React Native:状态更改后无法重新渲染图像