React Native:不同的样式应用于方向变化

Posted

技术标签:

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

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

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

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

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

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

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

谢谢!

【问题讨论】:

***.com/a/64559463/9444013 试试这个 【参考方案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>

【讨论】:

以上是关于React Native:不同的样式应用于方向变化的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 styled-components 将样式应用于 React Native 中的自定义组件?

react-native-web 更新:TypeError:无法读取未定义的属性“样式”

react-native 中的组件样式在应用程序中呈现不一致

如何为 React Native 锁定设备方向

在React native中设置按钮样式

将布局样式应用于站点上不同位置的样式组件和 React 组件的最佳方法是啥?