본문은 요즘IT와 번역가 Chase가 함께 만든 해외 번역 콘텐츠입니다. 필자인 Obumuneme Nwabude는 Angular(앵귤러)와 Flutter(플러터) 테크 커뮤니티를 운영하고 있으며, 개발자를 위한 앵귤러와 플러터 관련 글을 지속해서 올리고 있습니다. 이번 글은 이미지를 포함한 모든 종류의 UI를 Flutter 코드로 변환하는 방법에 관해 소개하고 있으며, 특히 다양한 UI의 기본 개념과 구현 방법을 알려주고 있습니다.
본문은 요즘IT와 번역가 Chase가 함께 만든 해외 번역 콘텐츠입니다. 필자인 Obumuneme Nwabude는 Angular(앵귤러)와 Flutter(플러터) 테크 커뮤니티를 운영하고 있으며, 개발자를 위한 앵귤러와 플러터 관련 글을 지속해서 올리고 있습니다. 이번 글은 이미지를 포함한 모든 종류의 UI를 Flutter 코드로 변환하는 방법에 관해 소개하고 있으며, 특히 다양한 UI의 기본 개념과 구현 방법을 알려주고 있습니다.
이 글에서는 이미지를 포함한 모든 종류의 UI를 Flutter 코드로 변환하는 방법에 대해 소개할 예정입니다. 미리 말하자면 이 글은 앱 개발 튜토리얼이 아니며, 다양한 UI의 기본적인 개념과 구현 방법을 간략히 소개하는 가이드입니다.
Flutter 소개
Flutter(플러터)는 하나의 코드 베이스로 모바일, 웹, 데스크톱에서 네이티브로 컴파일되는 구글의 아름다운 UI 툴킷입니다. (출처 : flutter-ko.dev)
웹 프런트엔드 개발에 HTML, CSS, Javascript가 쓰이고, 안드로이드 개발에 Kotlin(또는 Java)과 XML이 사용되는 등 대부분의 프레임워크에는 다수의 개발 언어가 요구됩니다. 하지만 데스크탑, 모바일, 웹 애플리케이션을 모두 개발할 수 있는 Flutter는 오로지 Dart를 기반으로 하며, Flutter 앱 내에서 볼 수 있는 이미지, 아이콘, 글자 등 모든 것이 위젯 형태로 구성되어있습니다.
위의 코드를 보면 Scaffold가 부모(parent)로서 appBar, body, floatingActionButton 파라미터를 받는 것을 확인할 수 있습니다. 그리고 AppBar는 텍스트값을 가진 Title을 파라미터로 다시 받는 연속적인 트리 구조입니다.
Flutter 위젯 트리 구조 예시
위젯 트리는 부모(parent)[3]와 자손(descendants)[4] 또는 자식(child/children)으로 구분되는 계층 구조로 이루어져 있습니다. 이런 트리 구조에서는 자식 위젯이 많아질수록 위젯 트리가 점차 넓어지는데, 신경 쓰지 않고 개발하다 보면 코드에 들여쓰기가 계속 중첩되어 마치 “>” 부등호를 옆에 두고 코드를 작성하는 모양이 되기도 합니다.
팁 : 과도한 들여쓰기는 코드를 리팩터링해야 한다는 신호입니다. 일부 위젯 계층을 떼어내어 별도의 위젯으로 추출해야 할 수 있습니다.
Flutter에서 UI를 구현하는 방법
1. 개발 방향
각각의 위젯마다 화면에 적절한 배치 위치가 다르기 때문에 상단에 구현되는 항목들에 대한 코드를 먼저 작성한 후 아래 방향으로 개발을 진행해야 합니다.
가로 방향은 지금 여러분이 이 글을 읽는 것처럼 왼쪽에서 오른쪽으로 코드를 작성하면 됩니다. 단, 오른쪽에서 왼쪽으로(RTL, right-to-left) 개발이 요구되는 경우에는 요건에 맞춰 개발하면 됩니다.
2. 위젯 선택하기
개발 방향을 숙지한 이후에는 구현하는 UI에 맞는 위젯을 선정해야 합니다. Flutter에서 위젯의 이름은 그 기능과 모양을 사용자들이 이해하기 쉽도록 작명됩니다. 그렇기 때문에 정확히 어떤 위젯을 사용할지 모르는 채로 프로젝트를 시작했더라도 간단한 검색을 통해 적합한 위젯을 찾을 수 있습니다.
3. 위젯 그룹 사용하기
위젯들을 그룹화해서 세로로 정렬하려면 Column을, 가로로 정렬은 Row를 사용하면 됩니다. 그리고 만약 위젯들이 서로 겹쳐야 하는 구조라면 Stack과 Positioned를 사용하면 됩니다.
a. Column/Row
Column과 Row는 CSS의 display: flex;와 유사한 개념입니다. Column이나 Row 안에서는 MainAxisAlignment와 CrossAxisAlignment 프로퍼티를 통해 위젯이 주축(Main axis) 또는 횡축(Cross axis)에서 정렬되는 방식을 조정할 수 있습니다. 횡축의 정렬 방식은 Center, Start, End, Stretch가 있고, 주축에는 Center, Start, End, SpaceEvenly, SpaceAround, SpaceBetweeen 방식이 있습니다.
주축과 횡축은 위젯에 따라 달라질 수 있습니다. Column에서는 세로축이 주축이고 가로축이 횡축입니다. 반대로 Row에서는 가로축이 주축, 세로축이 횡축입니다.
Column 혹은 Row에서 어떤 특정한 자식 위젯에 최대 가용 면적을 할당하려면 해당 위젯을 Expanded 위젯으로 감싸면 됩니다.
b. Stack 위젯
Stack은 자식(Children) 프로퍼티에 정의된 위젯 목록 중 가장 상위 항목이 가장 아래에 깔리고 하위 위젯일수록 위에 위치하도록 위젯들을 겹칩니다. Stack 안에서 위젯들의 배치는 alignment 프로퍼티의 topCenter, center, bottomEnd로 조정할 수 있습니다.
Stack의 크기는 배치되지 않은(non-positioned)[5] 위젯을 기준으로 계산됩니다. 따라서 stack 안에 최소 한 개의 배치되지 않은 위젯이 있어야 하고, 만약 그렇지 않다면 반드시 크기를 명시하는 부모 위젯으로 stack을 감싸야 합니다.
Positioned는 bottom, top, left, right 프로퍼티를 이용해서 하위 위젯의 위치를 stack에 맞춰 조정할 수 있습니다. 여기서 주의할 점은 프로퍼티 값을 음수로 설정해서 위젯을 역방향으로 배치하는 경우 위젯의 일부가 잘려 나갈 수 있다는 것입니다. Positioned 된 위젯이 잘리지 않게 하려면 반드시 clipBehavior: Clip.none 프로퍼티를 별도로 설정해야 합니다.
GestureDetector는 탭이 한 번만 감지되면 onTap에 할당된 기능을 실행하고, 두 번 감지되면 onDoubleTap의 기능을 실행하는 등 감지되는 사용자 동작에 따라 다른 콜백[8]을 실행해서 위젯과 사용자가 상호작용하도록 합니다.
팁 : 사용자가 GestureDetector의 하위 영역에 있는 빈 공간을 터치할 때는 별도의 콜백[8]이 호출되지 않습니다. 이에 반응하도록 하려면 HitTestBehavior 프로퍼티를 transpective로 설정해야 합니다.
GestureDetector(
// set behavior to detect taps on empty spaces
behavior: HitTestBehavior.translucent,
child: Column(
children: [
Text('I have space after me ...'),
SizedBox(height: 32),
Text('... that can detect taps.'),
],
),
onTap: () => print('Tapped on empty space.'),
)
GestureDetector의 사용 예시
InkWell은 사용자의 동작을 감지하고 기능을 실행한다는 점에서 GestureDetector와 유사합니다. 하지만 상대적으로 제한적인 수의 동작에만 반응하며, 물결이 퍼져나가는 애니메이션을 구현할 수 있다는 차이가 있습니다.
CustomPaint는 사용 빈도가 높지 않기 때문에 이 글에서는 UI 구현에 상당히 높은 유연성을 주는 요소로 정도로 알아두고 넘어가겠습니다.
요약
모바일, 웹, PC의 플랫폼을 망라하는 애플리케이션 개발에서 UI 구현은 가장 핵심이 되는 개발 요소 중 하나입니다. 여기에 있어 선언형 UI 프레임워크이자 크로스 플랫폼 개발 툴인 Flutter를 사용하면 몇 가지 위젯의 조합을 통해 다양한 플랫폼에서 작동하는 애플리케이션의 UI를 손쉽게 구현할 수 있습니다.
[1] 상태(동적으로 변화 가능한 데이터)를 포함한 위젯 [2] 상태(동적으로 변화 가능한 데이터)를 불포함한 위젯 [3] 부속 트리(subtree)를 가진 노드 [4] 부모에 속하는 부속노드 [5] Positioned 처리된 부모에 속하지 않는 자손 위젯 [6] 위젯이 작성될 때 동기적으로 읽을 수 있으며 위젯의 수명 동안 변경될 수 있는 정보 [7] 주어진 함수를 호출할 때 지정할 필요 없는 변수 [8] 어떤 이벤트가 발생한 후 수행되는 함수 [9] 구글이 지향하는 Material Design을 사용할 수 있게 해주는 위젯