#34 #Flutter 기초 총정리

서영·2026년 4월 19일
post-thumbnail

1. Flutter란?

Flutter는 Google이 만든 크로스 플랫폼 앱 개발 프레임워크이다. 일반적으로 앱을 개발하려면 Android는 Kotlin, iOS는 Swift를 따로 써야 한다. 하지만 Flutter를 사용하면 Dart 언어 하나로 Android, iOS, 웹, 데스크톱 앱을 동시에 만들 수 있다.

Flutter의 가장 큰 장점 중 하나는 Hot Reload다. 코드를 수정하고 저장하면 앱이 처음부터 재시작되는 게 아니라 바뀐 부분만 즉시 화면에 반영된다. 덕분에 UI를 조금씩 다듬으면서 빠르게 작업하기에 탁월하다.

한 줄 요약: Dart 하나로 Android + iOS + Web + Desktop, Hot Reload로 빠른 UI 개발.


2. 프로젝트 폴더 구조

① lib 폴더

우리가 작성하는 모든 Dart 코드는 이 폴더 안에 들어간다. main.dart 파일이 앱의 시작점으로, 앱을 실행하면 가장 먼저 이 파일이 실행된다.

화면이 여러 개라면 lib/screen/ 같은 하위 폴더를 만들어 화면별로 파일을 나눠 관리하는 것이 일반적인 패턴이다.

lib/
  ├── screen/
  │   └── home_screen.dart
  └── main.dart

② pubspec.yaml 파일

프로젝트 전체의 설정 파일이다. 외부 패키지(라이브러리)를 등록하거나, 이미지 같은 에셋(assets)을 등록할 때 사용한다.

dependencies:
  flutter:
    sdk: flutter
  webview_flutter: ^4.4.2    # 외부 패키지 등록

flutter:
  assets:
    - asset/img/             # 이미지 폴더 등록

⚠️ 주의: pubspec.yaml을 수정하면 반드시 Pub get을 실행해야 변경 사항이 적용된다.


3. 위젯(Widget)

Flutter에서 화면에 보이는 모든 것 — 글자, 버튼, 이미지, 여백 — 은 전부 위젯(Widget) 이다. 작은 위젯들을 조립해서 화면을 만드는 방식이다.

위젯은 트리(tree) 구조로 중첩된다. 위젯 안에 위젯이 들어가고, 그 안에 또 위젯이 들어가는 식이다.

Scaffold(
  appBar: AppBar(
    title: Text('다연쌤 최고'),
  ),
  body: WebViewWidget(
    controller: webViewController,
  ),
)

위젯은 상태(state)의 유무를 기준으로 두 종류로 나뉜다.

① StatelessWidget (상태 없는 위젯)

한 번 그려지면 변하지 않는 위젯이다. 데이터가 바뀔 일이 없는 정적인 화면에 사용한다. build() 메서드 하나만 존재하며, 이 메서드가 화면을 그리는 역할을 담당한다.

class HomeScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(...);
  }
}

② StatefulWidget (상태 있는 위젯)

사용자의 조작이나 시간에 따라 화면이 바뀌어야 할 때 사용한다. Flutter가 내부적으로 위젯을 효율적으로 관리하기 위해 클래스 2개로 나뉜다.

  • HomeScreen — 껍데기(위젯 정의)
  • _HomeScreenState — 실제 내용과 상태 관리
class HomeScreen extends StatefulWidget {
  
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  DateTime firstDay = DateTime.now(); // 상태값

  
  Widget build(BuildContext context) {
    return Scaffold(...);
  }
}

상태를 변경할 때는 반드시 setState() 안에서 값을 바꿔야 Flutter가 변경을 감지하고 화면을 다시 그린다.

StatelessWidget vs StatefulWidget 비교

구분StatelessWidgetStatefulWidget
화면 변화없음 (정적)있음 (동적)
setState()불필요필수
클래스 수1개2개
사용 예소개 화면, 고정 UI카운터, 입력 폼, D-day

4. 앱이 실행되는 흐름

Flutter 앱은 항상 아래 순서로 시작된다.

void main() {              // ① 앱의 시작점
  runApp(                  // ② 위젯을 화면에 띄우는 함수
    MaterialApp(           // ③ 앱 전체를 감싸는 최상위 위젯
      home: HomeScreen(),  // ④ 첫 화면 지정
    ),
  );
}
  • main() — Dart 프로그램의 시작점. Java의 public static void main과 동일한 역할.
  • runApp() — 괄호 안에 넣은 위젯을 화면에 표시.
  • MaterialApp — 앱 전체를 감싸는 최상위 위젯. Google Material Design 적용. theme, home 등을 설정.
  • Scaffold — 각 화면의 뼈대. appBar(상단바), body(본문), floatingActionButton(하단 버튼) 제공.

전체 구조를 트리로 나타내면 다음과 같다.

main()
  └── runApp()
        └── MaterialApp   // 앱 전체 설정
              └── HomeScreen   // 첫 화면
                    └── Scaffold   // 화면 뼈대

5. 레이아웃 위젯 — 화면 배치하기

① Column — 세로 배치

children 배열에 위젯을 넣으면 위에서 아래로 순서대로 배치된다.

Column(
  children: [
    Text('U&I'),
    Text('우리 처음 만난 날'),
    IconButton(...),
    Text('D+365'),
  ],
)

② Row — 가로 배치

children 배열에 위젯을 넣으면 왼쪽에서 오른쪽으로 나란히 배치된다.

Row(
  children: [
    Icon(Icons.star),
    Text('별점'),
    Text('4.5'),
  ],
)

③ 정렬 옵션 (mainAxis / crossAxis)

Column과 Row에는 두 가지 정렬 축이 존재한다.

  • mainAxis(주축) — 위젯이 나열되는 방향 (Column: 세로, Row: 가로)
  • crossAxis(교차축) — 주축의 반대 방향
Column(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [...],
)

자주 쓰는 mainAxisAlignment 값 정리:

설명
start위(또는 왼쪽)에 몰아서 배치
center가운데에 모아서 배치
spaceBetween첫·끝 위젯은 양 끝에 붙이고, 나머지 간격 균등 분배
spaceEvenly모든 간격(양 끝 포함)을 균등하게
spaceAround양 끝 간격은 절반, 위젯 사이 간격은 균등

6. 자주 쓰는 기본 위젯

① SizedBox — 빈 공간 만들기

Flutter에서는 CSS의 margin이나 padding 대신 SizedBox를 끼워 넣어 간격을 만드는 방식을 많이 사용한다.

// 세로 16px 여백
const SizedBox(height: 16),

// 가로 16px 여백
const SizedBox(width: 16),

② SafeArea — 안전 영역 확보

요즘 스마트폰에는 상단에 노치(카메라 영역)가 있고, 하단에 홈 인디케이터가 있다. SafeArea로 감싸면 그 영역을 자동으로 피해서 콘텐츠를 배치할 수 있다.

SafeArea(
  top: true,      // 위쪽 노치 영역 피하기
  bottom: false,  // 아래쪽은 피하지 않기
  child: Column(...),
)

③ Center — 가운데 정렬

자식 위젯을 부모 영역의 정가운데에 배치한다.

Center(
  child: Image.asset('asset/img/j.jpg'),
)

7. 이미지 넣기

Flutter에서 로컬 이미지를 사용하려면 총 3단계를 거쳐야 한다.

① 폴더 만들기 — 프로젝트 루트에 asset/img/ 폴더를 만들고 이미지 파일을 넣는다.

② pubspec.yaml 등록 — 아래와 같이 경로를 등록한 뒤 반드시 Pub get을 실행한다.

flutter:
  assets:
    - asset/img/   # 폴더 안 모든 파일 사용 가능

③ 코드에서 사용Image.asset()으로 경로를 지정하면 된다.

Image.asset('asset/img/whtjdcks.jpg')

⚠️ pubspec.yaml 수정 후 Pub get을 빠뜨리면 이미지가 로드되지 않는다.


8. 스타일 한 곳에서 관리하기 (Theme)

텍스트 스타일을 각 Text 위젯마다 직접 적으면, 나중에 폰트 크기를 바꿀 때 전부 찾아서 수정해야 한다. MaterialApptheme에 한 번만 정의해두면 main.dart 한 곳만 수정해도 앱 전체에 일괄 반영된다.

MaterialApp(
  theme: ThemeData(
    textTheme: TextTheme(
      displayLarge: TextStyle(
        color: Colors.white,
        fontSize: 80,
        fontWeight: FontWeight.w700,
      ),
      displayMedium: TextStyle(color: Colors.white, fontSize: 50),
      bodyLarge: TextStyle(color: Colors.white, fontSize: 30),
      bodyMedium: TextStyle(color: Colors.white, fontSize: 20),
    ),
  ),
  home: HomeScreen(),
)

각 화면에서는 Theme.of(context)로 정의된 스타일을 가져와 사용한다.

final textTheme = Theme.of(context).textTheme;

Text('U&I', style: textTheme.displayLarge),
Text('우리 처음 만난 날', style: textTheme.bodyLarge),

💡 context는 "내가 위젯 트리에서 어디에 있는지"를 알려주는 정보다. 이를 통해 상위에 설정된 Theme 정보를 가져올 수 있다.


9. 위젯을 클래스로 분리하기

한 파일에 모든 코드를 넣으면 길어질수록 읽기가 어려워진다. 화면의 각 부분을 의미 단위로 잘라 별도의 위젯 클래스로 만드는 것이 Flutter의 일반적인 패턴이다.

class _DDay extends StatelessWidget {
  final GestureTapCallback onHeartPressed;
  final DateTime firstDay;

  _DDay({required this.onHeartPressed, required this.firstDay});

  
  Widget build(BuildContext context) { ... }
}

생성자에서 값을 받아 부모 위젯 → 자식 위젯으로 데이터를 전달하는 방식이다. required 키워드를 붙이면 해당 값이 반드시 전달되도록 강제할 수 있다.

정리: 화면을 _DDay, _CoupleImage 같은 작은 위젯으로 쪼개면 코드 가독성이 높아지고, 각 위젯을 독립적으로 수정할 수 있어 유지보수에 유리하다.

profile
시대를 따라가는 개발자가 아닌, 시대를 이끄는 개발자

0개의 댓글