React Native 플렉스박스 레이아웃

리액트네이티브에서 UI 래이아웃을 작성할 때 사용하는 플랙스박스(Flexbox)에대해서 간단히 코드를 작성해 보면서 살펴 보도록 하겠습니다. 안드로이드나 아이오에스 앱을 만들 때 UI를 구성하고 레이아웃을 일관되게 유지하는 것은 어려운 일입니다. 리엑트네이티브에서도 쉬운 일은 아니지만 플랙스박스라고하는 방식을 잘 이해하면 화면 크기가 다른 여러 기기에서도 일관되게 보이는 UI를 작성하는데 많은 도움이 됩니다. 콤포넌트는 자식들의 레이아웃을 플렉스박스 알고리즘에따라서 배치를 합니다. 제일 자주 사용하게 되는 옵션은 flexDirection, alignItems, justifyContent 인데요. 우선은 요 몇가지만 잘 활용해도 많은 부분이 해결되죠.

설명을 시작하기에 앞서서 아래 명령을 실행해서 리액트네이티브 프로젝트를 생성합니다.

 expo init -t blank flex-layout

각각 필요한 설명에따라서 콤포넌트 파일을 하나씩 새로 생성하고요. 생성한 콤포넌트를 App.js에 임포트를 해서 실행하면서 실제로 어떻게 동작하는 지 직접 보도록 하겠습니다. 작성이 완료된 코드는 깃허브를 통해서 공유되어 있습니다. 링크는 참고자료에 기록해 두었습니다.

Flex

flex 프라퍼티는 각 아이템들이 주어진 공간을 주축(main axis)을 따라서 얼만큼 채울지를 정합니다. 여기서 주축이라고 하는 것은 행(column, 위/아래 방향, 세로 방향), 열(row, 좌/우 방향, 가로 방향) 중의 하나인데요. 설정을 해 주지 않으면 기본으로는 column 으로 지정이 됩니다.

flex.js 파일을 생성하고 아래와 같이 코드를 작성합니다.


import React from 'react';
import { View } from 'react-native';

const FlexDemo = () => {
  return (
    <View style={{ marginTop: 30, height: '100%' }}>
      <View style={{ flex: 1, backgroundColor: '#e93e43' }} />
      <View style={{ flex: 2, backgroundColor: '#f5a941' }} />
      <View style={{ flex: 3, backgroundColor: '#4ebd7a' }} />
    </View>
  );
};

export default FlexDemo;

App.js 파일은 아래와 같이 수정합니다.


import React from 'react';
import { View } from 'react-native';

import FlexDemo from './flex';

export default function App() {
  return FlexDemo();
}

위 코드를 실행하면 아래와 같은 화면을 볼 수 있는데요. 빨강, 노랑, 초록 뷰가 flex 프라퍼티에따라 부모 컨테이너에서 공간을 차지하고 있습니다. 빨강은 flex: 1, 노랑은 flex: 2, 초록은 flex: 3으로 되어 있는데요. 이를 다 더한 값 1+2+3=6 에서 가각의 flex 값만큼 공간을 차지합니다. 메인축인 세로방향(column)의 전체 공간에서 빨강은 1/6, 노랑은 2/6, 초록은 3/6 을 차지하고 있습니다. 컨테이너인 부모 뷰의 전체 공간에서 각각의 뷰가 차지할 공간을 자식 뷰의 프라퍼티로 지정을 한다는데 주의를 해야합니다.

Flex Direction

flexDirection은 부모가 자식 뷰를 어떤 방향으로 배치를 할 것인지를 결정합니다. 여기서 지정한 방향이 주축(메인축, main axis)가 됩니다. 지정을 하지 않으면 기본으로 세로방향(column, 행)으로 지정이 됩니다. 주축과 구별되는 말로 교차축(cross axis)은 주축의 방향과 직각을 이루는 방향을 말합니다. 교차축은 앞으로 다루게 될 alignItems의 기준이 됩니다.

  • row: 자식뷰를 왼쪽에서 오른쪽 방향으로 배치를 합니다.
  • column: 기본값입니다. 자식뷰를 위에서 아래 방향으로 배치합니다.
  • row-reverse: 자식뷰를 오른쪽에서 왼쪽 방향으로 배치합니다.
  • column-reverse: 자식뷰를 아래에서 위 방향으로 배치합니다.

flexDirection으로 뷰를 만들어 보겠습니다. flexDirection.js 파일을 만들고 아래와 같이 코드를 작성합니다.


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

const FlexDemo = () => {
  return (
    <View style={{ flex: 1, flexDirection: 'row', margin: 30 }}>
      <View style={{ width: 50, height: 50, backgroundColor: '#e93e43' }}>
        <Text>1</Text>
      </View>
      <View style={{ width: 50, height: 50, backgroundColor: '#f5a941' }}>
        <Text>2</Text>
      </View>
      <View style={{ width: 50, height: 50, backgroundColor: '#4ebd7a' }}>
        <Text>3</Text>
      </View>
    </View>
  );
};

export default FlexDemo;

부모가 되는 뷰의 flexDirection은 row로 설정이 되어 있습니다. 자식뷰는 3개가 있는데요. 보기 쉽게 하기위해서 배경색 외에 Text를 넣어서 가각 1, 2, 3으로 숫자가 보이도록 했습니다.

App.js는 아래와 같이 수정을 해서 새로 작성한 flexDirection.js에 있는 뷰가 보이도록 합니다. 앞으로도 계속 이렇게 App.js를 변경해서 새로 작성된 내용이 반영되도록 합니다.


import FlexDemo from './flexDirection';

flexDirection.js에서 flexDirection을 row, column, row-reverse, column-reverse로 변경을 하면서 실행을 해 보면 위에서 설명한대로 배치가 변경되는 것을 확인할 수 있습니다.

Justify Content

justifyContent는 주축을 기준으로 자식뷰를 어떻게 정렬할 것인지를 지정해 줍니다. 주축이 row, column 등으로 바뀜에따라 이 값이 의미하는 것이 변한다는 것에 주의를 해야하는데요. 예를 들어서 이 값을 center로 정한 경우라면 flexDirection이 row이면 주축이 가로방향이기때문에 자식뷰가 가로방향으로 중앙에 놓이게 됩니다. 하지만 flexDirection이 column인 경우라면 세로방향으로 중간에 자식뷰가 놓이게 되는 거죠. justifyContent로 설정할 수 있는 값을 아래와 같습니다.

  • flex-start: 기본 값입니다. 컨테이너의 주축이 시작하는 곳으로 정렬을 합니다. 주축이 row라면 왼쪽에 붙여서 정렬이 됩니다.
  • flex-end: 컨테이너의 주축이 끝나는 곳으로 정렬을 합니다. 주축이 row라면 오른쪽에 붙여서 정렬이 됩니다.
  • center: 컨테이너의 주축의 중앙에 정렬을 합니다.
  • space-between: 컨테이너의 주축 방향으로 자식뷰들이 찿지하고 남은 공간을 자식뷰 사이에 동일하게 분배를 합니다.
  • space-around: 컨테이너의 주축 방향으로 자식뷰들이 차지하고 남은 공간을 각각 자식뷰 양쪽 옆으로 동일하게 분배를 합니다. space-between과 차이는 첫번째 자식뷰의 시작방향과 마지막 자식뷰의 끝 방향으로도 공간을 배분한다는 것입니다.
  • space-evenly: space-around는 각 자식뷰 양쪽 옆으로 남은 공간을 동일하게 분배한다고 했습니다. 그래서 주축의 시작에서 첫번째 뷰까지 공간은 두 뷰 사이 공간의 절반을 차지하게 됩니다. space-evenly는 이와 달리 각 공간이 동일한 크기를 갖도록 분배합니다.

예제 코드는 flexDirection을 column 해서 작성해 보도록 하겠습니다. justifyContent.js 파일을 만들고 아래와 같이 코드를 작성합니다. 실행을 하기위해서는 App.js에서 justifyContent를 import 해야합니다.


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

const FlexDemo = () => {
  return (
    <View
      style={{
        flex: 1,
        flexDirection: 'column',
        margin: 30,
        justifyContent: 'flex-start',
      }}
    >
      <View style={{ width: 50, height: 50, backgroundColor: '#e93e43' }}>
        <Text>1</Text>
      </View>
      <View style={{ width: 50, height: 50, backgroundColor: '#f5a941' }}>
        <Text>2</Text>
      </View>
      <View style={{ width: 50, height: 50, backgroundColor: '#4ebd7a' }}>
        <Text>3</Text>
      </View>
    </View>
  );
};

export default FlexDemo;

아래 화면을 justifyContent를 차례로 flex-start, flex-end, center, space-between, space-around, space-evenly로 했을 때 화면입니다.

Align Items

alignItems은 교차축(cross axis) 방향으로 자식뷰를 어떻게 배치할 지를 정하게 됩니다. justifyContent이 주축 바향으로 정렬을 한다는 점이 차이가 있습니다.

  • stretch: 기본값입니다. 교차축 방향의 공간을 채우기위해 자식뷰를 늘입니다.
  • flex-start: 컨테이너의 교차축 시작으로 정렬을 합니다.
  • flex-end: 컨테이너의 교차축 끝으로 정렬을 합니다.
  • center: 컨테이터의 교차축 중앙으로 정렬을 합니다.
  • baseline: 자식뷰의 공통 베이스라인을 기준으로 정렬을 합니다.

우선 stretch에 대해서 알아보기위해서 alignItems.js 파일을 만들고 아래와 같이 작성을 합니다.


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

const FlexDemo = () => {
  return (
    <View
      style={{
        flex: 1,
        flexDirection: 'column',
        margin: 30,
        justifyContent: 'center',
        alignItems: 'stretch',
      }}
    >
      <View style={{ width: 50, height: 50, backgroundColor: '#e93e43' }}>
        <Text>1</Text>
      </View>
      <View style={{ height: 50, backgroundColor: '#f5a941' }}>
        <Text>2</Text>
      </View>
      <View style={{ height: 100, backgroundColor: '#4ebd7a' }}>
        <Text>3</Text>
      </View>
    </View>
  );
};

export default FlexDemo;

stretch가 효과가 있으려면 width나 height중에서 교차축에 해당하는 값이 지정 안되어 있어야합니다. 예제의 경우 flexDirection이 column이라서 교차축은 가로방향입니다. 첫번째 자식뷰의 경우는 width, height를 모두 지정하고 있어서 stretch에 여향을 받지 않습니다. 아래 두 뷰의 경우는 width가 지정이 되어 있지 않아 부모뷰를 다 채우도록 늘어나 있습니다.

아래는 각각 flex-start와 center입니다. width가 지정되지 않은 자식뷰의 width가 Text가 차지하는 만큼만 차지하고 있는 것도 주의해서 봐야합니다.

baseline을 확인해 보기 위해서 코드를 좀 더 변경해 보겠습니다. 영어에서 베이스라인은 알파벳마다 위치가 다릅니다. 게다가 폰트 크기가 다르다면 베이스라인 위치도 변하게 되겠죠. 아래 코드와 같이 알파벳으로 Text를 넣고 폰트 크기도 변경을 해서 시험해 보겠습니다.


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

const FlexDemo = () => {
  return (
    <View
      style={{
        flex: 1,
        flexDirection: 'row',
        margin: 30,
        justifyContent: 'center',
        alignItems: 'baseline',
      }}
    >
      <View style={{ width: 50, height: 50, backgroundColor: '#e93e43' }}>
        <Text style={{ fontSize: 40 }}>A</Text>
      </View>
      <View
        style={{
          width: 50,
          height: 50,
          backgroundColor: '#f5a941',
          justifyContent: 'center',
        }}
      >
        <Text style={{ fontSize: 20 }}>p</Text>
      </View>
      <View style={{ width: 50, height: 100, backgroundColor: '#4ebd7a' }}>
        <Text style={{ fontSize: 40 }}>E</Text>
      </View>
    </View>
  );
};

export default FlexDemo;

먼저 자식뷰를 가로로 정렬되도록 하기위해서 flexDirection을 row로 변경했습니다. 그러면 alignItems의 기준이 되는 교차축은 세로축이 됩니다. 서로다른 알파벳과 폰트 크기를 조정해서 베이스라인에 따라 세로축으로 정렬이 어떻게 되는지 보겠습니다.

눈으로도 정렬이 베이스라인에 맞춘 것을 확인할 수 있습니다. 위의 이미지는 좀 더 잘 보이도록 베이스라인에 맞춰서 선을 하나 그어봤습니다.

외에서 몇가지 프라퍼티가 있는데요. 이정도가 가장 많이 사용되는 것이라서 다뤄봤습니다. 그 외에 것은 참고자료에 있는 사이트에서 내용을 보시고 예제 코드를 실행해 보시면 도움이 될 거고요.

참고자료