DAY 10 | Flutter의 대표적인 layout widget들을 살펴보자

Pt J·6일 전
post-thumbnail

DAY 10 | Flutter의 대표적인 layout widget들을 살펴보자

주요 layout widget 훑어보기

화면 구성에 사용되는 가장 기본적인 widget Container

Flutter는 계층적인 widget들로 구성된다. 어떤 widget은 그 자체로 가장 atomic한 형태로 존재하고, 어떤 widget은 다른 widget을 child (혹은 children)으로 받아 계층을 이룬다. 화면을 구성하는 가장 기초적인 widget으로 Container widget이 있다. 이것은 사각형의 투명한 박스라고 생각할 수 있다.

Container widget의 크기는 직접 지정해 줄 수도 있고 내부에 담긴 child 에 따라 결정되도록 둘 수도 있다. 직접 지정해 주는 게 가장 우선순위가 높다. child 가 있을 경우 딱 그것만 들어가는 크기로 수축한다. shrink 된다고 표현한다. child 가 없을 경우 차지할 수 있는 가장 큰 크기로 확장된다. expand 된다고 표현한다.

Flutter에서 크기 단위는 logical pixel이다. 이것은 기기의 실제 물리 픽셀이 아닌 소프트웨어적으로 계산된 가상의 단위다. 기기의 화면 밀도에 따라 물리 픽셀로 자동 변환되어 화면에 그려진다.

구분물리 픽셀로지컬 픽셀
정의디스플레이의 실제 하드웨어 화면 점UI 레이아웃을 잡는 가상의 소프트웨어 단위
기준기기 제조사의 하드웨어 명세Flutter 등의 앱 개발 플랫폼
타 플랫폼 표현pxdp (안드로이드) / pt (iOS)
크기 변동성기기 화면 밀도에 따라 실제 크기가 달라짐기기 밀도가 달라도 화면 상의 실제 크기는 일정함
개발자 역할직접 제어하지 않음 (고해상도 이미지 처리 제외)코드에서 위젯의 크기(width, height) 지정 시 사용
Flutter 코드 조회View.of(context).physicalSizeMediaQuery.of(context).size

Android Studio에서 어떤 widget을 다른 widget으로 감싸고 싶을 땐, Context Actions 기능(💡)을 사용하면 된다. widget 이름에 마우스 호버 시 💡가 뜨는데, 그걸 눌러 뜨는 것 중 [Wrap with widget]을 누르고 widget 이라고 써 있는 부분을 원하는 widget으로 대치하면 된다.

가령 다음과 같은 코드가 있을 때,

lib/main.dart

class LandingPage extends StatelessWidget {
  const LandingPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Colors.yellow,
        child:Text('My perfect workout mate'),
      ),
    );
  }
}

Container 에 마우스를 올려 다음과 같이 SafeArea widget으로 감쌀 수 있다. 이렇게 어떤 widget을 다른 widget으로 감싸는 걸 wrap 한다고 한다.

lib/main.dart

class LandingPage extends StatelessWidget {
  const LandingPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          color: Colors.yellow,
          child:Text('My perfect workout mate'),
        ),
      ),
    );
  }
}

Container widget의 padding, border, margin 은 웹이나 디자인에서 이야기하는 것과 완전히 일치하기에 따로 익힐 건 없다.

헷갈린다면, padding 은 패딩을 껴입는 걸 생각하자. 피터가 패딩을 입었다면 피터 본인과 패딩 부피만큼을 차지하고 있다. 패딩 겉면이 피터와 외부 사이의 경계선이고, 그게 border 다. margin은 “마진을 남기다”를 생각하자. 원가를 제하고 남은 이익을 흔히 마진이라고 한다. 그러니까 본체 외의 플러스 알파 영역인 거다.

padding 이나 margin 에는 EdgeInsets 를 대입하여 여백을 지정한다. 상하좌우 모두 동일한 여백을 줄지, 상하와 좌우로 나누어 줄지 등에 따라 다른 constructor를 사용할 수 있다. constructor마다 적절한 parameter를 사용한다.

EdgeInsets 의 constructor

constructorparameter설명
allpositional 숫자 하나상하좌우 모두 동일하게 여백 설정
symmenticvertical, horizonal상하 여백과 좌우 여백을 따로 설정
fromLTRBpositional 숫자 넷상하좌우 여백을 전부 개별적으로 설정
onlyleft, top, right, bottom상하좌우 여백 중 원하는 것만 개별적으로 설정

한 방향으로 widget을 담는 widget Column & Row

Column widget과 Row widget은 방향만 다를 뿐 같은 성질을 가졌다. 아무튼 한 방향으로 widget을 담는 리스트 형태의 layout widget이다. widget이 쌓이는 main axis 방향으로는 기본적으로 expand 하며, 그것과 수직인 cross axis 방향으로는 기본적으로 shrink 한다.

뭐가 Column 이고 뭐가 Row 인지 헷갈린다면, 첫 글자인 [C]와 [R]의 시작점과 끝점의 상대적 위치로 기억하자. [C]의 끝점은 시작점의 아래에 있고 Column 은 widget을 아래로 쌓는다. [R]의 끝점은 시작점의 오른쪽에 있고 Row 는 widget을 오른쪽으로 쌓는다. (물론 아래서 위로, 오른쪽에서 왼쪽으로 방향을 바꿀 수 있지만 가로 세로가 그렇다는 거다.)

화면을 구상할 땐 그리드 형태로 기획하는 게 편하다. Column 은 가로가 한 칸이고 세로로 여러 칸 배치할 수 있는 그리드다. Row 는 세로가 한 칸이고 가로로 여러 칸 배치할 수 있는 그리드다. 중첩해서 사용하면 기본적으로는 가로가 한 칸이고 세로로 여러 칸 배치할 수 있는 그리드이지만 특정 영역에서는 가로로 여러 칸으로 나뉘는 등의 형태를 만들 수 있다.

ColumnRow 는 기본적으로 크기를 지정할 수 없다. 단지 그 안에 담긴 widget들에 의해 크기가 결정된다. 크기를 지정하고 싶다면 ContainerSizedBox 같은 widget으로 감싸거나 내부에 있는 widget의 크기를 늘려 그만큼의 공간을 차지하게 할 수 있다.

Column 은 기본적으로 가로로는 shrink하는데, 그 안에 가로로 expand하는 Row 를 넣으면 그 Row 를 담기 위해 Column 도 expand 되어 차지할 수 있는 최대 한도로 넓어진다.

main axis 방향으로는 start 정렬, cross axis 방향으로는 center 정렬되는 게 기본이지만 mainAxisAlignmentMainAxisAlignment object를 대입하거나 crossAxisAlignmentCrossAxisAlignment object를 대입하여 정렬을 변경할 수 있다. 이것도 padding, margin 개념과 마찬가지로 디자인 공통 영역이다. 특정 위치로 몰아주는 것과 남은 여백을 상대적인 크기로 고르게 나누어 채우는 것, 그리고 텍스트 같은 건 baseline 기준으로 정렬할 수 있고 하는 것 등은 다른 데에서 쓰는 것과 동일하다.

한 방향으로 다 안 들어가면 다음 줄로 넘어가는 Wrap

Column 이나 Row 는 한 방향으로 widget을 쌓아가다가 화면이 가득 차면 overflow되거나 스크롤이 생긴다. 이 때 Wrap widget을 사용하면 한 방향으로 가득 찼을 때 다음 줄로 넘어가도록 배치할 수 있다.

화면의 크기에 따라 네 장씩 배치하기도 하고 세 장씩 배치하기도 하고 아무튼 다음 건 다음 줄에 있는 갤러리 같은 걸 생각하면 쉽게 이해할 수 있다.

그리드를 무시하고 사용하는 Stack

Stack widget은 한 방향으로 순차적으로 쌓이지 않고 전부 위치를 지정해 주어야 한다. 서로 겹칠 수도 있고 완전히 별개의 좌표에 존재할 수도 있다. 여러 해상도를 고려하여 적절한 위치에 배치하기 쉽지 않으니 자주 쓰이진 않는다.

랜딩 페이지를 작성해 보자

수업 시간의 라이브 코딩 때는 랜딩 페이지의 기본 뼈대를 잡아 보았다. widget들을 적절한 계층과 순서로 사용하여 목표 화면과 동일하게 만드는 거다.

그리고 과제 Challenge

수업을 마친 후 실습 시간에는 Layout Challenge의 화면 구성대로 랜딩 페이지를 보완하는 것을 과제로 내주셨다. 수업 시간에 명확하게 다루지 않은 부분에 대해서는 힌트가 나와 있어서 해 나가는 데 어렵지 않았다.

Container widget을 생성할 때 decoration parameter에 어떤 object를 넣어야 하는지도 약간만 삽질하면 IDE 자체 기능이 힌트를 줘서 무난하게 따라갈 수 있었다. '이런 방식을 사용하면 될 것 같은데 그걸 Flutter에서는 어떻게 하지?' 하는 것만 검색해 보았다. Container 로 화면을 가득 채우고 거기에 이미지를 가득 채우면 될 것 같은데, 하면서 말이다.

고민, 그리고 또 고민

기획은 어렵다. 늘 그래왔다. 아이디어는... 글쎄. 나 자신을 설득시킬 수 없는 아이디어는 심도 깊은 기획으로 이어지지 않는다. 일단 나와 있는 아이디어 가지고 wireframe이나 잡아봐야지 했는데 Figma를 건드리다 보니 최소한의 디자인은 들어간 prototype이 작성되었다. 잘은 모르겠다. 페이지는 약 열 페이지 정도 되는데 기능이 이것저것 있는 게 아니라 비슷한 유형이 반복되는 것들이기도 하고.

지금은 백엔드가 내려갔지만 감정 단어 사전 때 살펴본 434개의 감정 단어 목록을 활용해 볼까. 《한국어 감정 단어의 목록 작성과 차원 탐색》 논문 기반으로 정리한 감정 단어 목록은 있긴 한데 이걸 그대로 사용해도 되는지는 모르겠다. 아무래도 저작권이 어쩌고 하는 이슈가 있으니 말이다. 개인 실습이야 비상업적으로 개인이 해본 거니까 괜찮아도 앱 배포는 다른 문제다.

지금의 기획에도 러셀의 2차원 감정 원형 모형이 사용되긴 하지만 그건 그냥 넣는 김에 기능의 복잡도를 조금이나마 높이기 위해 넣은 것뿐이고.

기획이 나오면 그걸 기반으로 prototype을 작성하고 구현하는 건 할 만한데 기획이 늘 어렵다. Figma로 prototype을 만드는 것부터는 거부감이 크지 않다. 충분히 할 수 있다. 하지만 아무튼 기획, 고놈의 기획... 와닿지 않는 기획 말고.

AI와 브레인스토밍을 하다 보면 '그건 웹으로 있어도 쓸까 말까 한데 앱으로 다운로드 받진 않을 것 같다' 싶은 아이디어나 '그런 건 있어도 안 쓸 것 같다' 싶은 아이디어나 '누군가에게는 니즈가 있을 순 있지만 난 그게 왜 필요한지 납득되지 않는다' 싶은 아이디어를 많이 제시하더라. 역시 잘 모르겠다.

profile
Peter J Online Space - since July 2020 | 아무데서나 채용해줬으면 좋겠다 (지금은 학생 때 하던 거 아무거나 공부하고 있고요, 취업시켜 주시면 그 분야로 공부할게요)

0개의 댓글