【问题标题】:React Native: Different styles applied on orientation changeReact Native:不同的样式应用于方向变化
【发布时间】:2018-05-20 21:12:02
【问题描述】:

我正在开发一个 React Native 应用程序,将其作为原生应用程序部署在 iOS 和 Android(以及 Windows,如果可能的话)上。

问题是我们希望布局是不同的取决于屏幕尺寸及其方向。

我创建了一些返回 styles 对象 的函数,并在每个组件渲染的函数上调用,因此我可以在应用程序启动时应用不同的样式,但如果方向(或屏幕大小) 应用程序初始化后发生更改,它们不会重新计算或重新应用。

我已将侦听器添加到顶部渲染,因此它会在方向更改时更新其状态(并强制为应用程序的其余部分进行渲染),但子组件不会重新渲染(因为事实上,它们没有改变)。

所以,我的问题是:我怎样才能让样式根据屏幕尺寸和方向完全不同,就像 CSS 媒体一样查询(即时呈现)?

我已经尝试过react-native-responsive 模块,但运气不佳。

谢谢!

【问题讨论】:

标签: css reactjs screen-orientation react-native


【解决方案1】:

可以在[这里][1]找到解决方案。

应用程序的方向从纵向到横向,反之亦然,这听起来很容易,但当方向更改时必须更改视图时,在 react native 中可能会很棘手。换句话说,可以通过考虑这两个步骤来为两个方向定义不同的视图。

从 React Native 导入维度

import { Dimensions } from 'react-native';

识别当前方向并相应地渲染视图

/**
 * Returns true if the screen is in portrait mode
 */
const isPortrait = () => {
    const dim = Dimensions.get('screen');
    return dim.height >= dim.width;
};

/**
 * Returns true of the screen is in landscape mode
 */
const isLandscape = () => {
    const dim = Dimensions.get('screen');
    return dim.width >= dim.height;
};

了解方向何时改变以相应地改变视图

// Event Listener for orientation changes
    Dimensions.addEventListener('change', () => {
        this.setState({
            orientation: Platform.isPortrait() ? 'portrait' : 'landscape'
        });
    });

组装所有部件

import React from 'react';
import {
  StyleSheet,
  Text,
  Dimensions,
  View
} from 'react-native';

export default class App extends React.Component {
  constructor() {
    super();

    /**
    * Returns true if the screen is in portrait mode
    */
    const isPortrait = () => {
      const dim = Dimensions.get('screen');
      return dim.height >= dim.width;
    };

    this.state = {
      orientation: isPortrait() ? 'portrait' : 'landscape'
    };

    // Event Listener for orientation changes
    Dimensions.addEventListener('change', () => {
      this.setState({
        orientation: isPortrait() ? 'portrait' : 'landscape'
      });
    });

  }

  render() {
    if (this.state.orientation === 'portrait') {
      return (
          //Render View to be displayed in portrait mode
       );
    }
    else {
      return (
        //Render View to be displayed in landscape mode
      );
    }

  }
}

由于为查看方向变化而定义的事件使用此命令“this.setState()”,因此该方法会自动再次调用“render()”,因此我们不必担心再次渲染它,一切都已经处理好了。

【讨论】:

  • 请将代码作为文本发布在答案中,而不是图像。您可以使用 Ctrl + K 或编辑器顶部的{} 按钮将代码格式化为代码块。
  • 谢谢@FeiXiang。我已经做出了改变。
  • 如果设备的宽度大于高度,则此解决方案将无法使用。
  • 这也适用于网络吗?它适用于世博会吗?他们似乎使用不同的 API 来获取尺寸:docs.expo.io/versions/latest/sdk/screen-orientation/…
【解决方案2】:

这是@Mridul Tripathi 作为可重复使用的钩子的答案:

// useOrientation.tsx
import {useEffect, useState} from 'react';
import {Dimensions} from 'react-native';

/**
 * Returns true if the screen is in portrait mode
 */
const isPortrait = () => {
  const dim = Dimensions.get('screen');
  return dim.height >= dim.width;
};

/**
 * A React Hook which updates when the orientation changes
 * @returns whether the user is in 'PORTRAIT' or 'LANDSCAPE'
 */
export function useOrientation(): 'PORTRAIT' | 'LANDSCAPE' {
  // State to hold the connection status
  const [orientation, setOrientation] = useState<'PORTRAIT' | 'LANDSCAPE'>(
    isPortrait() ? 'PORTRAIT' : 'LANDSCAPE',
  );

  useEffect(() => {
    const callback = () => setOrientation(isPortrait() ? 'PORTRAIT' : 'LANDSCAPE');

    Dimensions.addEventListener('change', callback);

    return () => {
      Dimensions.removeEventListener('change', callback);
    };
  }, []);

  return orientation;
}

然后您可以使用以下方式使用它:

import {useOrientation} from './useOrientation';

export const MyScreen = () => {
    const orientation = useOrientation();

    return (
        <View style={{color: orientation === 'PORTRAIT' ? 'red' : 'blue'}} />
    );
}

【讨论】:

  • 您可能希望将[] 添加为useEffect 的第二个参数,否则效果挂钩将无限期运行。
  • 如果我想在没有组件的情况下使用方向怎么办?有什么解决办法吗
  • NB: const dim = Dimensions.get('screen');应该是 const dim = Dimensions.get('window');
  • @AzeezatRaheem 不一定。 window 是可见应用程序窗口的大小。 screen 是设备屏幕的大小。 src
【解决方案3】:

您可以使用onLayout 属性:

export default class Test extends Component {

  constructor(props) {
    super(props);
    this.state = {
      screen: Dimensions.get('window'),
    };
  }

  getOrientation(){
    if (this.state.screen.width > this.state.screen.height) {
      return 'LANDSCAPE';
    }else {
      return 'PORTRAIT';
    }
  }

  getStyle(){
    if (this.getOrientation() === 'LANDSCAPE') {
      return landscapeStyles;
    } else {
      return portraitStyles;
    }
  }
  onLayout(){
    this.setState({screen: Dimensions.get('window')});
  }

  render() {
    return (
      <View style={this.getStyle().container} onLayout = {this.onLayout.bind(this)}>

      </View>
      );
    }
  }
}

const portraitStyles = StyleSheet.create({
 ...
});

const landscapeStyles = StyleSheet.create({
  ...
});

【讨论】:

    【解决方案4】:

    终于,我做到了。不知道它可能带来的性能问题,但它们应该不是问题,因为它只需要调整大小或改变方向。

    我创建了一个全局控制器,其中有一个函数接收组件(容器、视图)并向其添加事件侦听器:

    const getScreenInfo = () => {
        const dim = Dimensions.get('window');
        return dim;
    }    
    
    const bindScreenDimensionsUpdate = (component) => {
        Dimensions.addEventListener('change', () => {
            try{
                component.setState({
                    orientation: isPortrait() ? 'portrait' : 'landscape',
                    screenWidth: getScreenInfo().width,
                    screenHeight: getScreenInfo().height
                });
            }catch(e){
                // Fail silently
            }
        });
    }
    

    这样,当方向或窗口大小发生变化时,我会强制重新渲染组件。

    然后,在每个组件构造函数上:

    import ScreenMetrics from './globalFunctionContainer';
    
    export default class UserList extends Component {
      constructor(props){
        super(props);
    
        this.state = {};
    
        ScreenMetrics.bindScreenDimensionsUpdate(this);
      }
    }
    

    这样,每次调整窗口大小或改变方向时都会重新渲染。

    您应该注意,但是,这必须应用于我们想要监听方向变化的每个组件,因为如果父容器更新但state(或props ) 的孩子不会更新,它们不会被重新渲染,所以如果我们有一棵大的孩子树在听它,这可能会导致性能下降

    希望它对某人有所帮助!

    【讨论】:

    • 你可能有内存泄漏,事件监听器没有被移除?也可能会影响性能,因为当它们随着时间的推移未被删除时,可能会调用许多回调。
    • 是的,正如我在回答中已经解释的那样,这可能会导致性能下降,具体取决于附加到侦听器的项目数量。但是,如果您删除了侦听器,它不会在您下次更改方向时更新 UI,因此删除它们也没有用。
    • 在组件卸载时移除监听器很有用。如果你不这样做,你最终会耗尽内存。
    【解决方案5】:

    我制作了一个超轻的组件来解决这个问题。 https://www.npmjs.com/package/rn-orientation-view

    组件在方向改变时重新渲染它的内容。 例如,您可以传递 LandscapeStyles 和 PortraitStyles 以不同方式显示这些方向。 适用于 iOS 和 Android。 它很容易使用。看看吧。

    【讨论】:

      【解决方案6】:

      我遇到了同样的问题。方向改变后布局没有改变。 然后我明白了一个简单的想法 - 布局应该取决于应该在渲染函数内部计算的屏幕宽度,即

      getScreen = () => {
        return Dimensions.get('screen');
      }
      
      render () {
        return (
          <View style={{ width: this.getScreen().width }>
            // your code
          </View>
        );
      }
      

      在这种情况下,宽度将在渲染时计算。

      【讨论】:

        【解决方案7】:

        React Native 还有useWindowDimensions 钩子,可以返回设备的宽度和高度。
        有了这个,您可以通过比较宽度和高度轻松检查设备是“纵向”还是“横向”。
        查看更多here

        【讨论】:

          【解决方案8】:

          ** 我将此逻辑用于横向和纵向逻辑。** ** 如果我首先在横向启动我的应用程序,我将获得设备的真实高度。并相应地管理标题的高度。**

          const [deviceOrientation, setDeviceOrientation] = useState(
              Dimensions.get('window').width < Dimensions.get('window').height
                ? 'portrait'
                : 'landscape'
            );
            const [deviceHeight, setDeviceHeight] = useState(
              Dimensions.get('window').width < Dimensions.get('window').height
                ? Dimensions.get('window').height
                : Dimensions.get('window').width
            );
          
            useEffect(() => {
              const setDeviceHeightAsOrientation = () => {
                if (Dimensions.get('window').width < Dimensions.get('window').height) {
                  setDeviceHeight(Dimensions.get('window').height);
                } else {
                  setDeviceHeight(Dimensions.get('window').width);
                }
              };
              Dimensions.addEventListener('change', setDeviceHeightAsOrientation);
              return () => {
                //cleanup work
                Dimensions.removeEventListener('change', setDeviceHeightAsOrientation);
              };
            });
          
            useEffect(() => {
              const deviceOrientation = () => {
                if (Dimensions.get('window').width < Dimensions.get('window').height) {
                  setDeviceOrientation('portrait');
                } else {
                  setDeviceOrientation('landscape');
                }
              };
              Dimensions.addEventListener('change', deviceOrientation);
              return () => {
                //cleanup work
                Dimensions.removeEventListener('change', deviceOrientation);
              };
            });
            console.log(deviceHeight);
            if (deviceOrientation === 'landscape') {
              return (
                <View style={[styles.header, { height: 60, paddingTop: 10 }]}>
                  <TitleText>{props.title}</TitleText>
                </View>
              );
            } else {
              return (
                <View
                  style={[
                    styles.header,
                    {
                      height: deviceHeight >= 812 ? 90 : 60,
                      paddingTop: deviceHeight >= 812 ? 36 : 10
                    }
                  ]}>
                  <TitleText>{props.title}</TitleText>
                </View>
              );
            }
          

          【讨论】:

            【解决方案9】:

            到目前为止,我在这个库上取得了最大的成功:https://github.com/axilis/react-native-responsive-layout 它可以满足您的要求以及更多功能。简单的组件实现几乎没有像上面一些更复杂的答案那样的逻辑。我的项目是通过 RNW 使用电话、平板电脑、和网络 - 并且实施完美无缺。此外,在调整浏览器大小时,它是真正的响应式,而不仅仅是在初始渲染时 - 处理手机方向的变化完美无缺。

            示例代码(将任何组件作为块的子级):

            <Grid>
              <Section> {/* Light blue */}
                <Block xsSize="1/1" smSize="1/2" />
                <Block xsSize="1/1" smSize="1/2" />
                <Block xsSize="1/1" smSize="1/2" /> 
              </Section>
              <Section> {/* Dark blue */}
                <Block size="1/1" smSize="1/2" />
                <Block size="1/1" smSize="1/2" />
                <Block size="1/1" smSize="1/2" />
                <Block size="1/1" smSize="1/2" />
                <Block size="1/1" smSize="1/2" />
              </Section>
            </Grid>
            

            给出这个:

            【讨论】:

              【解决方案10】:

              我为我的 expo SDK36 项目编写了一个 HoC 解决方案,它支持方向更改并基于 ScreenOrientation.Orientation 值传递 props.orientation

              import React, { Component } from 'react';
              import { ScreenOrientation } from 'expo';
              
              export default function withOrientation(Component) {
                class DetectOrientation extends React.Component {
                  constructor(props) {
                    super(props);
                    this.state = {
                      orientation: '',
                    };
                    this.listener = this.listener.bind(this);
                  }
              
                  UNSAFE_componentWillMount() {
                    this.subscription = ScreenOrientation.addOrientationChangeListener(this.listener);
                  }
              
                  componentWillUnmount() {
                    ScreenOrientation.removeOrientationChangeListener(this.subscription);
                  }
              
                  listener(changeEvent) {
                    const { orientationInfo } = changeEvent;
                    this.setState({
                      orientation: orientationInfo.orientation.split('_')[0],
                    });
                  }
              
                  async componentDidMount() {
                    await this.detectOrientation();
                  }
              
                  async detectOrientation() {
                    const { orientation } = await ScreenOrientation.getOrientationAsync();
                    this.setState({
                      orientation: orientation.split('_')[0],
                    });
                  }
              
                  render() {
                    return (
                      <Component
                        {...this.props}
                        {...this.state}
                        onLayout={this.detectOrientation}
                      />
                    );
                  }
                }
                return (props) => <DetectOrientation {...props} />;
              }
              

              【讨论】:

                【解决方案11】:

                为了实现更高性能的集成,我将以下内容用作我的每个反应导航屏幕的超类:

                export default class BaseScreen extends Component {
                  constructor(props) {
                    super(props)
                
                    const { height, width } = Dimensions.get('screen')
                
                    // use this to avoid setState errors on unmount
                    this._isMounted = false
                
                    this.state = {
                      screen: {
                        orientation: width < height,
                        height: height,
                        width: width
                      }
                    }
                  }
                
                  componentDidMount() {
                    this._isMounted = true
                    Dimensions.addEventListener('change', () => this.updateScreen())
                  }
                
                  componentWillUnmount() {
                    this._isMounted = false
                    Dimensions.removeEventListener('change', () => this.updateScreen())
                  }
                
                  updateScreen = () => {
                    const { height, width } = Dimensions.get('screen')
                
                    if (this._isMounted) {
                      this.setState({
                        screen: {
                          orientation: width < height,
                          width: width, height: height
                        }
                      })
                    }
                  }
                

                将任何根组件设置为从该组件扩展,然后将屏幕状态从继承的根组件传递给您的叶/哑组件。

                此外,为了避免增加性能开销,请更改样式对象而不是添加更多组件:

                const TextObject = ({ title }) => (
                  <View style={[styles.main, screen.orientation ? styles.column : styles.row]}>
                    <Text style={[styles.text, screen.width > 600 ? {fontSize: 14} : null ]}>{title}</Text>
                  </View>
                )
                
                const styles = StyleSheet.create({
                  column: {
                    flexDirection: 'column'
                  },
                  row: {
                    flexDirection: 'row'
                  },
                  main: {
                    justifyContent: 'flex-start'
                  },
                  text: {
                    fontSize: 10
                  }
                }
                

                我希望这对将来的任何人都有帮助,并且您会发现它在开销方面非常理想。

                【讨论】:

                  【解决方案12】:

                  我正在使用styled-components,这就是我在方向更改时重新渲染 UI 的方式。

                  import React, { useState } from 'react';
                  import { View } from 'react-native';
                  import { ThemeProvider } from 'styled-components';
                  import appTheme from 'constants/appTheme';
                  
                  const App = () => {
                    // Re-Layout on orientation change
                    const [theme, setTheme] = useState(appTheme.getTheme());
                    const onLayout = () => {
                      setTheme(appTheme.getTheme());
                    }
                  
                    return (
                      <ThemeProvider theme={theme}>
                        <View onLayout={onLayout}/>
                        {/* Components */}
                      </ThemeProvider>
                    );
                  }
                  
                  export default App;
                  

                  即使您不使用styled-components,您也可以创建一个状态并在onLayout 上更新它以重新渲染 UI。

                  【讨论】:

                    【解决方案13】:

                    这是我的解决方案:

                    const CheckOrient = () => {
                      console.log('screenHeight:' + Dimensions.get('screen').height + ', screenWidth: ' + Dimensions.get('screen').width);
                    }
                    
                    return ( <
                        View onLayout = {
                          () => CheckOrient()
                        } >
                        ............
                        <
                        /View>

                    【讨论】:

                      猜你喜欢
                      • 2015-07-07
                      • 1970-01-01
                      • 1970-01-01
                      • 2018-09-13
                      • 2020-11-01
                      • 2021-01-23
                      • 1970-01-01
                      • 2021-09-06
                      • 1970-01-01
                      相关资源
                      最近更新 更多