React Native:在方向更改时应用了不同的样式

时间:2017-12-06 21:17:32

标签: css reactjs screen-orientation react-native

我正在开发 React Native 应用程序,以便在iOS和Android(以及Windows,如果可能)上部署为本机应用程序。

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

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

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

所以,我的问题是:如何根据屏幕尺寸和方向制作可能完全不同的样式,就像 CSS Media一样查询(即时呈现)?

我已经尝试了react-native-responsive模块而没有运气。

谢谢!

12 个答案:

答案 0 :(得分:12)

可以在[此处] [1]找到解决方案。

从纵向到横向(反之亦然)的应用程序定向是一项任务,听起来很容易,但是当方向更改时必须更改视图时,在响应本机时可能会比较棘手。换句话说,通过考虑这两个步骤,可以为两个方向定义不同的视图。

从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()',因此我们不必担心再次渲染它,这一切都得到了照顾。

答案 1 :(得分:12)

这是@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'}} />
    );
}

答案 2 :(得分:9)

您可以使用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({
  ...
});

答案 3 :(得分:3)

最后,我已经能够这样做了。不知道它可以带来的性能问题,但它们不应该是一个问题,因为它只需要调整大小或方向更改。

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

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(或{ {1}})孩子们没有更新,他们不会被重新渲染,所以如果我们有一个大孩子树听它,那么它可以是表演杀人

希望它有所帮助!

答案 4 :(得分:2)

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

该组件会在方向改变时重新呈现其内容。 例如,您可以传递landscapeStyles和PortraitStyles以不同方式显示这些方向。 适用于iOS和Android。 易于使用。看看。

答案 5 :(得分:2)

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

getScreen = () => {
  return Dimensions.get('screen');
}

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

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

答案 6 :(得分:0)

到目前为止,我在此库中获得了最大的成功: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>

为此:

enter image description here

答案 7 :(得分:0)

我为我的 expo SDK36项目编写了一个HoC解决方案,它支持方向更改并基于props.orientation值传递ScreenOrientation.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} />;
}

答案 8 :(得分:0)

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

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
  }
}

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

答案 9 :(得分:0)

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

答案 10 :(得分:0)

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

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>
    );
  }

答案 11 :(得分:0)

我正在使用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。