사용한 위젯
- MaterialApp
- Scaffold
- Column
- Row
- Text
- SafeArea
- Image
- Spacer
- Expanded
- Padding
- SizedBox
1️⃣ flutter 프로젝트 생성


- 프로젝트 명은 무조건 소문자 + _(언더스코어 스코어 기법)

프로젝트를 생성 후 프로젝트를 어떤 방식으로 띄울 것인지 물어보는 것에서
This Window 시에 폴더가 나오지 않는다면 새로 프로젝트 만들기 아래에 처럼 보이지 않구 빈 폴더가 보이는 경우가 있는데 버그이기 때문에 새로운 프로젝트를 만들어야 한다.

2️⃣ 기본 코드 작성
처음에 프로젝트를 생성 후
lib/main.dart
에 있는 모든 코드를 지운다.
이후 아래 코드 작성import 'package:flutter/cupertino.dart'; void main() { runApp(MyApp()); }
지금은 MyApp이라는 함수가 없기 때문에 오류가 날 것이다. 그럼 MyApp을 추가.
- Stateless 코드는
stl
이라고만 적으면 자동 완성 해준다
// stl 이라고 적으면 자동완성 활성화 class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const Placeholder(); } }
해당 코드를 작성 후
const
를 아직 제대로 배우기 전까지는 어떤 역할을 하는지 모르기 때문에 모두 지워준다. 지우지 않아도 상관없지만 모르는 오류가 발생 시 const
를 지워보자.App을 만들 때에는 가장 먼저 선택해야하는 것이 있다. 그것은 AndroidApp을 만들 것인지, IOSApp을 만들 것인지 선택해야한다.
AndroidApp을 만들 때에는
MaterialApp
디자인을 사용하고,
IOSApp은 CupertinoApp
디자인을 사용한다.두 디자인은
Class
로 제공을 하고 있다. AndroidApp을 만들기로 선택했다면 그에 맞는 코드를 짜보자.import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp(home: StorePage()); // } } // class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Placeholder(); } }
모두 만들었으면 이제 App을 실행 시킬 수 있다. 현재 StorePage에서
Placeholder
를 return
중인데, 이 때 Placeholder
는 위젯(widget)이다.위젯(Widget)?
위젯이란 UI를 구성하는 기본 단위로, App의 모든 것이 위젯으로 이루어져 있다고 볼 수 있다.
Placeholder
라는 위젯은 빈 박스를 하나 만드는 것이다. 레이아웃을 미리 구성해두고 나중에 채워 넣고 싶을 때 사용한다.아래의 사진은 위에 App을 실행 시켰을 때 나오는 화면이다.

Scaffold
Flutter에서
Scaffold
는 앱의 기본 뼈대(레이아웃 구조)를 제공하는 UI 프레임입니다. 쉽게 말하면 앱 화면의 기본 틀을 만들어주는 위젯입니다. 보통 하나의 화면(Screen)마다 하나의 Scaffold
를 사용한다.class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Scaffold(); } }
Scaffold
는 전체 화면의 구조를 정의하고, 그 안에 AppBar
, body
, FloatingActionButton
등을 포함할 수 있게 합니다.3️⃣ Column 위젯, Row 위젯
Column
위젯은 수직 방향의 레이아웃 구조를 만들어주는 위젯이다.
Column
에는 하위 속성이 child
와 children
속성이 있는데 child를 사용하게 되면, Column
에는 하나의 위젯만 가질 수 있다.children
을 사용하게되면, 여러 가지의 위젯을 Column
위젯이 여러 위젯을 가지고 있을 수 있다.class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Scaffold(body: Column(children: [ Text("Column"), Text("Column"), Text("Column")] )); } }
- 수직 방향으로 배치가 됨

Row
위젯은 수평 방향으로 레이아웃 구조를 만들어주는 위젯이다. Column의 수평 버전인 것이다.class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Scaffold(body: Row(children: [ Text("Column"), Text("Column"), Text("Column")] )); } }

4️⃣ Text 위젯
Text
위젯은 문자열을 담을 수 있는 위젯이다.
아래의 사진과 같이 Text 안에 적은 문자열이 출력된다.class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Row( children: [ Text("Woman"), Text("Kids"), Text("Shoes"), Text("Bag"), ], ), ); } }

1. stlye 속성
Text
위젯은 stlye속성을 사용하여 디자인을 할 수 있다.
Text
위젯에 Ctrl + 마우스 왼쪽 클릭
을 하게 되면 아래와 같은 코드가 보인다
style, styrutStlye 등 과 같은 것을 속성이라고 부른다. 속성이란 어떤 대상을 구성하고 있는 요소라고 생각하면 된다.
Text
뿐만 아니라 많은 위젯이 속성을 가지고 있다.style 속성을 이용하여 글자의 굵기를 굵게 만들어보겠다.
- 해당 글자가 더 굵어진 모습이다.

5️⃣ SafeArea 위젯
SafeArea
는 휴대폰 기기별로 조금씩 다른 상태바StatusBar
영역에 여백padding
을 넣어주는 역할은 한다. 상태바와 같이 개발자가 건드릴 수 없는 영역을 inset
영역이라고 한다.
가장 위에 상태바와 같은 영역을 top inset
, 가장 아래에 뒤로가기 버튼 또는 키보드가 나오는 영역은 bottom inset
, 그리고 휴대폰 가장자리 전체의 영역은 edge inset
이라고 한다.SafeArea
위젯이 생기기 전 까지는 휴대폰 기종마다 inset
영역이 다르기 때문에 개발자들이 엄청나게 긴 코드를 작성해야 했는데 이제는 SafeArea
위젯을 설정해주면 어떠한 기종이든 알아서 inset 영역에 여백을 넣어준다.SafeArea
전 App에는 상태바와Row
위젯이 겹쳐서 보인다.

SafeArea
를 적용하여 상태바와Row
위젯의 여백이 생긴 모습

class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Row( children: [ Text("Woman"), Text("Kids"), Text("Shoes"), Text("Bag"), ], ), ), ); } }
코드 삽입 시 유용한 팁
SafeArea
를 입력 할 때에는 직접 작성하게 되면 오타 또는 괄호의 영역이 잘못 지정 될 수 있어서 위험하다. 그럴 때는 Row
위젯 위에서 Alt + Enter
를 하게 되면 아래의 사진이 나온다.
SafeArea
가 있으면 가장 좋지만 없기 때문에, 가장 위에 Wrap With widget
을 선택해준다.
그러면 아래 같은 코드가 될 것이다.@override Widget build(BuildContext context) { return Scaffold( body: widget( child: Row( children: [ Text("Woman"), Text("Kids"), Text("Shoes"), Text("Bag"), ], ), ), ); } }
이 상태에서
body: widget
에서 widget
부분을 SafeArea
로 바꿔주게 되면 실수 할 일이 사라지기 때문에 이 방식에 습관이 들면 좋다.6️⃣ Spacer 위젯
Spacer
위젯은 위젯과 위젯 사이의 간격을 조정하는 사용한다.
사용법은 간단하다. 조정하고 싶은 위젯 사이에 Spacer()
를 입력하면된다.class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Row( children: [ Text("Woman", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Kids", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Shoes", style: TextStyle(fontWeight: FontWeight.bold)), Text("Bag", style: TextStyle(fontWeight: FontWeight.bold)), ], ), ), ); } }
Shoes와 Bag 사이에는 Spacer()가 들어가지 않아서 위젯이 서로 붙어있다.

7️⃣ Debug 배너 해제
에뮬레이터로 나의 App을 실행 해보니 계속해서 오른쪽 위에 Debug 라는 배너가 있다.
배너가 테스트를 방해하기 때문에 없애보겠다.
방법은 MaterialApp 속성 중에
debugShowCheckedModeBanner
를 이용하면 된다.class MyApp extends StatelessWidget { MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: StorePage(), debugShowCheckedModeBanner: false, ); // } }

8️⃣ Padding 위젯
Padding
은 자식 위젯 주위에 빈 공간을 만들어 줍니다.
아래의 사진과 같이 하늘색의 부분이 여백Padding
이라고 합니다.
지금까지 만든 App에 Row 위젯에 Padding 위젯으로 감싸보겠다.
SafeArea와 같이
Alt + Enter
를 사용하여 실수와 편의성을 늘리면 좋습니다.
@override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Padding( padding: EdgeInsets.all(8.0), child: Row( children: [ Text("Woman", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Kids", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Shoes", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Bag", style: TextStyle(fontWeight: FontWeight.bold)), ], ), ), ), ); } }

Padding을 더 주고 싶다면,
(8.0)
을 더 큰 값을 넣어주면 된다.
지금 상태는 상하좌우 모두 padding이 들어가 있는 모습이다. 하지만 상황에 따라서는 왼쪽만 주고, 또는 상하만 주고 그러고 싶을 때가 있을 것이다. 그러한 경우는 어떻게 해야하나?
EdgeInsets.all을 바꾸어 주면된다.
EdgeInsets.all
: 상, 하, 좌, 우 모든 방향에 여백을 줄 때 사용
EdgeInsets.only
: 4방향 중 내가 원하는 곳만 여백을 줄 때 사용 ex )EdgeInsets.only(top: 8.0)
EdgeInsets.symmetric
: 수평이나 수직 중 선택하여 여백을 줄 때 사용 ex )EdgeInsets.symmetric(horizontal: 25.0)
9️⃣ Image 위젯
Image 위젯을 사용하기 전 사전에 세팅해야 할 것이 있다.
1. Image 파일 생성
JAVA에서는 공통으로
Img
파일을 만들어 img파일들을 모았다면, Flutter에서는 assets
파일에 img들을 모은다. 해당 파일을 만드는 위치는 다음과 같이 프로젝트 바로 아래에 만들면 된다.project/ ├── lib/ ├── assets

이후에는 해당 폴더를 인식할 수 있게 해줘야한다.
2. pubspec.yaml
수정
pubspec.yaml
해당 파일에 assets를 인식 할 수 있게 경로를 적어준다.
assets/
로 적게 되면 인식되는 이유는 pubspec.yaml
은 항상 프로젝트 루트에 위치하고, 그 안에서 지정하는 경로들도 루트 기준 상대 경로로 해석하기 때문이다.루트란?
프로젝트 구조에서 가장 상위에 있는 시작점을 의미한다.
루트를 기준으로 상대 경로로 해석 하기 때문에
assets/
앞에 나의 프로젝트 명인 flutter_recipe2
가 생략 되어있는 것이다. 실제로 인식 하는 경로는 flutter_recipe2/assets/
인것이다.assets: - assets/
경로를 추가 한 뒤에 모든 App을 종료하고
Pub get
을 클릭하여 오류가 없으면 인식 완료!
꼭 모든 App을 종료해야한다. 이유는 asset을 처음 추가하거나 경로를 바꿨을 때는 앱을 재시작해야 반영됩기 때문이다.
3. 이미지 추가
assets
폴더에 내가 사용하고 싶은 폴더를 넣기.Img.asset(”파일 경로”)
를 추가해주면 이미지가 나온다
// class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( children: [ Padding( padding: EdgeInsets.all(25.0), child: Row( children: [ Text("Woman", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Kids", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Shoes", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Bag", style: TextStyle(fontWeight: FontWeight.bold)), ], ), ), Image.asset("assets/bag.jpeg"), Image.asset("assets/bag.jpeg") ], ), ), ); } }

🔟 Expanded 위젯
Expanded 위젯은 남은 공간을 위젯의 크기를 확장하여 공간을 채울 수 있도록 하는 위젯이다.
현재 만들어진 App은 아래의 공간이 많이 남는다. 이 공간을 위젯의 크기를 확장하여 공간을 채워보겠다.
class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( children: [ Padding( padding: EdgeInsets.all(25.0), child: Row( children: [ Text("Woman", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Kids", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Shoes", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Bag", style: TextStyle(fontWeight: FontWeight.bold)), ], ), ), Expanded(child: Image.asset("assets/bag.jpeg")), Expanded(child: Image.asset("assets/cloth.jpeg")), ], ), ), ); } }

확장된 모습이다. 하지만 먼가 이상하지 않은가? 아까까지는 이미지가 서로 붙어 있엇는데, 서로 떨어졌다. 그리고 남은 공간을 전부 채우고 싶은데 이렇게 되면 중간에 공간이 또 비어있게 된다. Expanded의 공간이 어떻게 잡혀 있는지 확인해보자.
1️⃣1️⃣ Flutter Devtools
App을 개발하고 디버깅 할 때 사용하는 시각적 도구 모음이다. 이것을 사용하게 되면 시각적으로 해당 위젯들의 크기도 알 수 있고, 디거빙, 성능분석 등등 많은 것을 할 수 있다.
Flutter Devtools를 키는 법은 간단하다.

콘솔 창에 빨간 네모 버튼만 눌러주면 된다. 그러면 웹 브라우저가 하나 열리게 된다.

해당 창이 보이게 된다면 위에서 2번째 탭인
Flutter Inspector
를 클릭하게 될 경우 아래의 사진 처럼 보인다.
여기서 내가 작성한 위젯들을 한 번에 볼 수 있다. 그리고 해당 위젯들의 영역을 보고 싶다면, 보고 싶은 위젯을 클릭 후
Select Widget Mode
를 클릭하면 된다.
누르게 되면 내가 실행한 App에서 보라색 영역으로 표시 된다.
여기서 확인을 해보면 Expanded 위젯의 영역은 넓은데 이미지가 꽉 차지 않은 모습이다.
이것은 Img 위젯의 속성을 추가해주면 해결 가능하다.
1️⃣2️⃣ Image 위젯 속성
Image 위젯과 속성을 같이 다루고 싶었지만 상황을 보게 되면 더 이해하기 쉬울 것 같아서 뒤 쪽에 정리하였다.
Image 기본 특성으로 비율이라는 것이 있다. 기본적으로는 이 비율을 부술 수 없다. 하지만 속성을 이용하게 되면 비율을 부수어 화면에 꽉차게 보이게도 할 수 있다.
Image 속성에 대해 가장 많이 사용하는 fit 속성 3가지를 알아보겠다.
BoxFit.contain
: 원본사진의 가로 세로 비율 변화 없이 이미지를 추가한다. 비율 변화 없이 이미지를 추가하기 때문에 박스 크기와 맞지 않으면 빈 공간이 생긴다.
BoxFit.fill
: 원본사진의 비율을 무시하고 지정한 영역에 사진을 맞춘다. 비율을 무시하기 때문에 사진이 화질또는 이런 것이 망가진다.
BoxFit.cover
: 원본사진의 가로 세로 비율을 유지한 채로 지정한 영역에 사진을 맞춘다. 비율을 유지 할 수 있는 장점이 있지만, 사진이 지정한 크기를 벗어나면 잘릴 수 있다.
아래의 속성 예시를 붙이겠다.
BoxFit.contain

BoxFit.fill

BoxFit.cover

1️⃣3️⃣ SizedBox
Flutter에서 width 혹은 height 크기를 가지는 빈 상자이다.
이번 프로젝트에서는 이미지 사이에 빈 공간을 주기 위해 사용했다.
class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( children: [ Padding( padding: EdgeInsets.all(25.0), child: Row( children: [ Text("Woman", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Kids", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Shoes", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Bag", style: TextStyle(fontWeight: FontWeight.bold)), ], ), ), Expanded(child: Image.asset("assets/bag.jpeg", fit: BoxFit.cover)), SizedBox(height: 2), Expanded(child: Image.asset("assets/cloth.jpeg", fit: BoxFit.cover)), ], ), ), ); } }
- 약간의 빈 공간이 생긴 모습

1️⃣4️⃣ 최종코드
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: StorePage(), debugShowCheckedModeBanner: false, ); // } } // class StorePage extends StatelessWidget { StorePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( children: [ Padding( padding: EdgeInsets.all(25.0), child: Row( children: [ Text("Woman", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Kids", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Shoes", style: TextStyle(fontWeight: FontWeight.bold)), Spacer(), Text("Bag", style: TextStyle(fontWeight: FontWeight.bold)), ], ), ), Expanded(child: Image.asset("assets/bag.jpeg", fit: BoxFit.cover)), SizedBox(height: 2), Expanded(child: Image.asset("assets/cloth.jpeg", fit: BoxFit.cover)), ], ), ), ); } }
출처
최주호 , 김근호 , 이지원 저자(글) / (2023) / 만들면서 배우는 플러터 앱 프로그래밍 (7가지 모바일 앱 UI 제작 & RiverPod) / 앤써북
Share article