Flutter 코딩셰프 맛집 정리: Button과 Router

Hyunsoo Jung·2024년 11월 3일

Flutter

목록 보기
4/13

Flutter에서 버튼은 사용자의 행동을 받는 가장 기본적인 위젯이다.
버튼을 누르면 다음과 같은 작업을 할 수 있다.

버튼 클릭 시 할 수 있는 작업
- 화면 이동
- 데이터 저장
- API 요청
- SnackBar 표시
- Dialog 표시
- 상태 변경

그중 화면 이동을 할 때는 주로 NavigatorRoute를 사용한다.

전체 흐름은 다음과 같다.

Button 클릭
   |
   v
onPressed 실행
   |
   v
Navigator 호출
   |
   v
Route 생성
   |
   v
새 화면으로 이동

1. 버튼의 기본 구조

Flutter 버튼은 보통 onPressedchild를 가진다.

ElevatedButton(
  onPressed: () {
    print('버튼 클릭');
  },
  child: const Text('확인'),
)

각 속성의 의미는 다음과 같다.

onPressed
- 버튼이 눌렸을 때 실행되는 콜백 함수

child
- 버튼 안에 표시할 위젯
- 보통 Text나 Icon을 넣음

버튼 클릭 흐름은 다음과 같다.

사용자가 버튼 클릭
   |
   v
onPressed 콜백 실행
   |
   v
작성한 코드 실행

2. ElevatedButton

ElevatedButton은 입체감 있는 버튼이다.

일반적으로 화면에서 중요한 동작에 사용한다.

ElevatedButton(
  onPressed: () {
    print('로그인');
  },
  child: const Text('로그인'),
)

사용하기 좋은 상황은 다음과 같다.

ElevatedButton 사용 상황
- 로그인
- 저장
- 확인
- 다음 단계로 이동
- 주요 액션 버튼

즉, 사용자가 반드시 눌러야 하는 중요한 버튼에는 ElevatedButton이 적합하다.


3. TextButton

TextButton은 배경이나 입체감이 거의 없는 평면 버튼이다.

텍스트 링크처럼 가볍게 보여줄 때 사용한다.

TextButton(
  onPressed: () {
    print('회원가입');
  },
  child: const Text('회원가입'),
)

사용하기 좋은 상황은 다음과 같다.

TextButton 사용 상황
- 회원가입
- 비밀번호 찾기
- 취소
- 더보기
- 중요도가 낮은 액션

예를 들어 로그인 화면에서 “회원가입”이나 “비밀번호 찾기”는 TextButton으로 표현하기 좋다.


4. IconButton

IconButton은 아이콘을 버튼처럼 사용할 때 쓰는 위젯이다.

짧고 직관적인 동작에 적합하다.

IconButton(
  onPressed: () {
    print('검색');
  },
  icon: const Icon(Icons.search),
)

사용하기 좋은 상황은 다음과 같다.

IconButton 사용 상황
- 검색
- 뒤로가기
- 삭제
- 좋아요
- 설정
- 메뉴 열기

아이콘만 보고 의미를 알 수 있는 동작에 사용하면 좋다.


5. FloatingActionButton

FloatingActionButton은 화면 위에 떠 있는 둥근 버튼이다.

보통 화면에서 가장 중요한 하나의 작업을 나타낼 때 사용한다.

FloatingActionButton(
  onPressed: () {
    print('추가');
  },
  child: const Icon(Icons.add),
)

ScaffoldfloatingActionButton 속성에 넣어서 사용한다.

Scaffold(
  appBar: AppBar(
    title: const Text('메모'),
  ),
  body: const Center(
    child: Text('메모 목록'),
  ),
  floatingActionButton: FloatingActionButton(
    onPressed: () {
      print('메모 추가');
    },
    child: const Icon(Icons.add),
  ),
)

사용하기 좋은 상황은 다음과 같다.

FloatingActionButton 사용 상황
- 글쓰기
- 메모 추가
- 항목 추가
- 사진 촬영
- 주요 생성 액션

6. Navigator란?

Navigator는 Flutter에서 화면 이동을 관리하는 클래스다.

Flutter는 화면을 스택 구조로 관리한다.

화면 스택
- 화면이 아래에서 위로 쌓이는 구조
- 새 화면으로 이동하면 스택 위에 추가됨
- 뒤로 가기를 누르면 맨 위 화면이 제거됨

예를 들어 A 화면에서 B 화면으로 이동하면 다음과 같다.

처음 상태

[PageA]

Navigator.push 실행

[PageB]
[PageA]

뒤로 가기를 누르면 PageB가 제거되고 PageA가 다시 보인다.

뒤로 가기

[PageA]

7. Navigator.push

Navigator.push는 새로운 화면을 현재 화면 위에 추가한다.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const PageB(),
  ),
);

각 코드의 의미는 다음과 같다.

Navigator.push
- 새 화면을 스택 위에 추가

context
- 현재 위젯의 위치 정보

MaterialPageRoute
- 새 화면으로 이동하기 위한 Route

builder
- 이동할 화면을 생성하는 함수

const PageB()
- 이동할 새 화면

흐름은 다음과 같다.

PageA
   |
   | Navigator.push
   v
PageB가 PageA 위에 쌓임
   |
   v
뒤로 가기 가능

push는 이전 화면으로 돌아갈 수 있어야 할 때 사용한다.

push 사용 예시
- 상세 화면 이동
- 설정 화면 이동
- 게시글 보기
- 상품 상세 보기

8. Navigator.pushReplacement

Navigator.pushReplacement는 현재 화면을 새로운 화면으로 교체한다.

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => const PageB(),
  ),
);

push와 다른 점은 기존 화면을 스택에서 제거한다는 것이다.

Navigator.push

[PageB]
[PageA]

Navigator.pushReplacement

[PageB]

즉, pushReplacement를 사용하면 PageB로 이동한 뒤 PageA로 돌아갈 수 없다.

로그인 후 로그인 화면으로 다시 돌아가면 안 되는 경우에 사용하기 좋다.

로그인 화면
   |
   | 로그인 성공
   v
Navigator.pushReplacement
   |
   v
홈 화면

뒤로 가기를 눌러도 로그인 화면으로 돌아가지 않음

사용 예시는 다음과 같다.

ElevatedButton(
  onPressed: () {
    Navigator.pushReplacement(
      context,
      MaterialPageRoute(
        builder: (context) => const PageB(),
      ),
    );
  },
  child: const Text('이동'),
)

9. Navigator.push와 pushReplacement 차이

두 메서드는 모두 화면 이동에 사용하지만 이전 화면을 남기는지 여부가 다르다.

Navigator.push
- 새 화면을 현재 화면 위에 추가
- 뒤로 가기 가능
- 이전 화면이 스택에 남아 있음

Navigator.pushReplacement
- 현재 화면을 새 화면으로 교체
- 뒤로 가기로 이전 화면에 돌아갈 수 없음
- 이전 화면을 제거함

선택 기준은 다음과 같다.

이전 화면으로 돌아가야 한다
-> Navigator.push

이전 화면으로 돌아가면 안 된다
-> Navigator.pushReplacement

예시로 보면 다음과 같다.

상품 목록 -> 상품 상세
-> Navigator.push

로그인 -> 홈
-> Navigator.pushReplacement

스플래시 -> 메인
-> Navigator.pushReplacement

설정 화면 -> 약관 화면
-> Navigator.push

10. MaterialPageRoute

MaterialPageRoute는 새로운 화면을 만들 때 사용하는 Route 클래스다.

화면 전환 애니메이션도 함께 제공한다.

MaterialPageRoute(
  builder: (context) => const PageB(),
)

각 속성의 의미는 다음과 같다.

MaterialPageRoute
- Material Design 방식의 화면 이동 Route

builder
- 이동할 화면을 생성하는 함수

context
- 현재 위젯 트리의 위치 정보

Navigator와 함께 사용하면 다음처럼 화면을 이동할 수 있다.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const PageB(),
  ),
);

이때 const PageB()PageB 화면 인스턴스를 생성한다.

const를 붙이면 해당 위젯이 불변일 때 불필요한 재생성을 줄이는 데 도움이 된다.

const PageB()
- 변경되지 않는 PageB 위젯 생성
- 불필요한 재빌드 감소에 도움

11. BuildContext란?

BuildContext는 위젯 트리 안에서 현재 위젯이 어디에 있는지 알려주는 객체다.

쉽게 말해 위젯의 위치 정보다.

BuildContext
- 위젯 트리 안에서 현재 위젯의 위치 정보
- 상위 위젯을 찾을 때 사용
- Navigator, Theme, ScaffoldMessenger, Provider 등을 찾을 때 사용

예를 들어 다음 코드에서 context는 현재 버튼이 위젯 트리 어디에 있는지를 알고 있다.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const PageB(),
  ),
);

Navigator.push(context, ...)는 context를 기준으로 위쪽 위젯 트리를 올라가면서 가장 가까운 Navigator를 찾는다.

현재 context
   |
   v
위젯 트리를 위로 탐색
   |
   v
가장 가까운 Navigator 찾기
   |
   v
화면 이동 실행

of(context)가 붙는 메서드도 비슷하게 동작한다.

Navigator.of(context)
ScaffoldMessenger.of(context)
Theme.of(context)
Provider.of<TaskViewModel>(context)

의미는 다음과 같다.

.of(context)
- 현재 context를 기준으로
- 위젯 트리를 거슬러 올라가면서
- 가장 가까운 해당 타입의 위젯이나 데이터를 찾음

12. 위젯 트리의 흐름

Flutter는 위젯을 화면에 그릴 때 여러 단계를 거친다.

크게 보면 다음과 같다.

Widget Tree
   |
   v
Element Tree
   |
   v
Render Tree
   |
   v
화면에 UI 표시

각 단계의 역할은 다음과 같다.

Widget Tree
- 개발자가 작성한 UI 선언
- StatelessWidget, StatefulWidget, Text, Button 등

Element Tree
- Widget과 실제 화면 객체를 연결
- 기존 위젯을 재사용할지 판단

Render Tree
- 실제 크기, 위치, 그리기 담당
- 화면에 픽셀로 표시되는 단계

흐름으로 정리하면 다음과 같다.

1. 위젯 생성
- UI를 구성할 위젯들이 선언됨
- 부모와 자식 관계로 위젯 트리 형성

2. build 실행
- Flutter가 build() 메서드 호출
- 화면에 필요한 자식 위젯 정의

3. Element Tree 업데이트
- 기존 Element를 재사용할지 판단
- 변경된 부분만 반영

4. Render Tree 생성 및 업데이트
- 크기와 위치 계산
- 실제 화면에 그림

즉, Flutter에서 우리가 작성하는 것은 Widget이고, Flutter 내부에서는 Element와 RenderObject를 이용해 실제 화면을 관리한다.


13. Builder 위젯이란?

Builder는 새로운 BuildContext를 만들어주는 위젯이다.

특정 위치의 context가 필요할 때 사용할 수 있다.

Builder(
  builder: (context) {
    return ElevatedButton(
      onPressed: () {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('버튼 클릭'),
          ),
        );
      },
      child: const Text('SnackBar 표시'),
    );
  },
)

Builder를 사용하는 이유는 현재 위치에 맞는 context가 필요할 때가 있기 때문이다.

예를 들어 ScaffoldMessenger, Navigator, Theme 등을 찾을 때 context의 위치가 중요할 수 있다.

Builder 사용 전
- 원하는 상위 위젯을 찾지 못하는 context일 수 있음

Builder 사용 후
- Builder 아래에서 새로운 context 생성
- 현재 위치 기준으로 상위 위젯 탐색 가능

14. Builder와 성능 최적화

Builder는 새로운 context를 만들어주는 역할이 핵심이다.

다만 경우에 따라 하위 위젯을 분리해서 불필요한 build 범위를 줄이는 데도 도움이 될 수 있다.

하지만 단순히 Builder로 감싼다고 무조건 성능이 좋아지는 것은 아니다.

성능 최적화가 목적이라면 보통 다음 방식도 함께 고려한다.

성능 최적화 방법
- const 생성자 사용
- 위젯을 작은 단위로 분리
- 상태가 필요한 부분만 StatefulWidget으로 분리
- ValueListenableBuilder / StreamBuilder / Consumer 등 사용
- 불필요한 setState 범위 줄이기

정리하면 다음과 같다.

Builder의 주요 역할
- 새로운 BuildContext 제공

Builder가 도움 되는 경우
- 특정 위치의 context가 필요할 때
- 하위 트리를 별도 builder 함수로 분리하고 싶을 때

주의할 점
- Builder만으로 자동 성능 최적화가 되는 것은 아님

15. import 단축키

Flutter 개발 중 필요한 패키지나 파일을 import해야 할 때 IDE 단축키를 사용할 수 있다.

macOS 기준으로 자주 사용하는 단축키는 다음과 같다.

Option + Enter
- 에러나 경고에 대한 빠른 수정 메뉴 열기
- import 추가 가능

Option + Shift + Enter
- 상황에 따라 자동 수정 또는 import 관련 기능 실행

Windows에서는 보통 다음 단축키를 사용한다.

Alt + Enter
- 빠른 수정 메뉴 열기
- import 추가
- 오류 수정 제안 확인

단축키는 Android Studio 설정이나 OS에 따라 다를 수 있다.


16. 버튼과 라우터 전체 예시

아래 코드는 버튼을 눌렀을 때 PageB로 이동하는 예시다.

import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Page A'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pushReplacement(
              context,
              MaterialPageRoute(
                builder: (context) => const PageB(),
              ),
            );
          },
          child: const Text('Page B로 이동'),
        ),
      ),
    );
  }
}

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Page B'),
      ),
      body: const Center(
        child: Text('Page B 화면'),
      ),
    );
  }
}

이 코드는 다음 순서로 동작한다.

PageA 화면 표시
   |
   v
사용자가 버튼 클릭
   |
   v
onPressed 실행
   |
   v
Navigator.pushReplacement 호출
   |
   v
MaterialPageRoute로 PageB 생성
   |
   v
PageA를 PageB로 교체

17. 정리

Flutter에서 버튼과 라우터는 사용자 액션과 화면 이동을 연결하는 핵심 개념이다.

버튼 종류는 다음과 같다.

ElevatedButton
- 중요한 액션 버튼

TextButton
- 가벼운 텍스트 버튼

IconButton
- 아이콘 버튼

FloatingActionButton
- 화면의 주요 생성 액션 버튼

화면 이동은 다음처럼 정리할 수 있다.

Navigator.push
- 새 화면을 현재 화면 위에 추가
- 뒤로 가기 가능

Navigator.pushReplacement
- 현재 화면을 새 화면으로 교체
- 뒤로 가기 불가능

MaterialPageRoute
- 이동할 화면을 생성하는 Route

BuildContext는 다음처럼 이해하면 된다.

BuildContext
- 현재 위젯의 위치 정보
- Navigator.of(context), Theme.of(context)처럼 상위 위젯을 찾을 때 사용
- 화면 이동, SnackBar, 상태 관리에서 자주 사용

마지막으로 핵심 흐름은 다음과 같다.

버튼 클릭
   |
   v
onPressed 실행
   |
   v
Navigator 호출
   |
   v
MaterialPageRoute 생성
   |
   v
새 화면 표시

즉, Flutter에서 버튼은 사용자의 입력을 받고,
Navigator와 Route는 그 입력에 따라 화면을 이동시키는 역할을 한다.

profile
Jetpack Compose 리팩터링&운영 경력 및 Flutter 경험을 보유한 앱 개발자

0개의 댓글