Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Native: Different styles applied on orientation change

I'm developing a React Native application to be deployed as a native application on iOS and Android (and Windows, if possible).

The problem is that we want the layout to be different depending on screen dimensions and its orientation.

I've made some functions that return the styles object and are called on every component render's function, so I am able to apply different styles at application startup, but if the orientation (or screen's size) changes once the app has been initialized, they aren't recalculated nor reapplied.

I've added listeners to the top rendered so it updates its state on orientation change (and it forces a render for the rest of the application), but the subcomponents are not rerendering (because, in fact, they have not been changed).

So, my question is: how can I make to have styles that may be completely different based on screen size and orientation, just as with CSS Media Queries (which are rendered on the fly)?

I've already tried react-native-responsive module without luck.

Thank you!

like image 293
Unapedra Avatar asked Dec 06 '17 21:12

Unapedra


People also ask

How do you handle screen orientation changes in react native?

For Android, open the AndroidManifest. xml file and within the activity element add 'android:screenOrientation="portrait"' to lock to portrait or 'android:screenOrientation="landscape"' to lock to landscape.


8 Answers

If using Hooks. You can refer to this solution: https://stackoverflow.com/a/61838183/5648340

The orientation of apps from portrait to landscape and vice versa is a task that sounds easy but may be tricky in react native when the view has to be changed when orientation changes. In other words, having different views defined for the two orientations can be achieved by considering these two steps.

Import Dimensions from React Native

import { Dimensions } from 'react-native';

To identify the current orientation and render the view accordingly

/**
 * 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;
};

To know when orientation changes to change view accordingly

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

Assembling all pieces

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

  }
}

As the event defined for looking out the orientation change uses this command ‘this.setState()’, this method automatically again calls for ‘render()’ so we don’t have to worry about rendering it again, it’s all taken care of.

like image 118
Mridul Tripathi Avatar answered Sep 30 '22 13:09

Mridul Tripathi


Here's @Mridul Tripathi's answer as a reusable hook:

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

You can then consume it using:

import {useOrientation} from './useOrientation';

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

    return (
        <View style={{color: orientation === 'PORTRAIT' ? 'red' : 'blue'}} />
    );
}
like image 38
Eric Wiener Avatar answered Sep 30 '22 15:09

Eric Wiener


You can use the onLayout prop:

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({
  ...
});
like image 26
Adrian Avatar answered Sep 30 '22 13:09

Adrian


Finally, I've been able to do so. Don't know the performance issues it can carry, but they should not be a problem since it's only called on resizing or orientation change.

I've made a global controller where I have a function which receives the component (the container, the view) and adds an event listener to it:

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

With this, I force to rerender the component when there's a change on orientation, or on window resizing.

Then, on every component constructor:

import ScreenMetrics from './globalFunctionContainer';

export default class UserList extends Component {
  constructor(props){
    super(props);

    this.state = {};

    ScreenMetrics.bindScreenDimensionsUpdate(this);
  }
}

This way, it gets rerendered everytime there's a window resize or an orientation change.

You should note, however, that this must be applied to every component which we want to listen to orientation changes, since if the parent container is updated but the state (or props) of the children do not update, they won't be rerendered, so it can be a performance kill if we have a big children tree listening to it.

Hope it helps someone!

like image 24
Unapedra Avatar answered Sep 30 '22 14:09

Unapedra


I made a super light component that addresses this issue. https://www.npmjs.com/package/rn-orientation-view

The component re-renders it's content upon orientation change. You can, for example, pass landscapeStyles and portraitStyles to display these orientations differently. Works on iOS and Android. It's easy to use. Check it out.

like image 38
KentAgent Avatar answered Sep 30 '22 13:09

KentAgent


I had the same problem. After the orientation change the layout didn't change. Then I understood one simple idea - layout should depend on screen width that should be calculated inside render function, i.e.

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

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

In that case, the width will be calculated at the moment of render.

like image 45
Aliaksei Avatar answered Sep 30 '22 15:09

Aliaksei


React Native also have useWindowDimensions hooks that returns the width and height of your device.
With this, you can check easily if the device is in 'Portrait' or 'Landscape' by comparing the width and height.
See more here

like image 38
Abraham Anak Agung Avatar answered Sep 30 '22 13:09

Abraham Anak Agung


** I am using this logic for my landscape and portrait Logic.** ** by this if I launch my app in landscape first I am getting the real height of my device. and manage the hight of the header accordingly.**

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>
    );
  }
like image 25
Kushal Avatar answered Sep 30 '22 14:09

Kushal