Flutter에서 버튼은 사용자의 행동을 받는 가장 기본적인 위젯이다.
버튼을 누르면 다음과 같은 작업을 할 수 있다.
버튼 클릭 시 할 수 있는 작업
- 화면 이동
- 데이터 저장
- API 요청
- SnackBar 표시
- Dialog 표시
- 상태 변경
그중 화면 이동을 할 때는 주로 Navigator와 Route를 사용한다.
전체 흐름은 다음과 같다.
Button 클릭
|
v
onPressed 실행
|
v
Navigator 호출
|
v
Route 생성
|
v
새 화면으로 이동
Flutter 버튼은 보통 onPressed와 child를 가진다.
ElevatedButton(
onPressed: () {
print('버튼 클릭');
},
child: const Text('확인'),
)
각 속성의 의미는 다음과 같다.
onPressed
- 버튼이 눌렸을 때 실행되는 콜백 함수
child
- 버튼 안에 표시할 위젯
- 보통 Text나 Icon을 넣음
버튼 클릭 흐름은 다음과 같다.
사용자가 버튼 클릭
|
v
onPressed 콜백 실행
|
v
작성한 코드 실행
ElevatedButton은 입체감 있는 버튼이다.
일반적으로 화면에서 중요한 동작에 사용한다.
ElevatedButton(
onPressed: () {
print('로그인');
},
child: const Text('로그인'),
)
사용하기 좋은 상황은 다음과 같다.
ElevatedButton 사용 상황
- 로그인
- 저장
- 확인
- 다음 단계로 이동
- 주요 액션 버튼
즉, 사용자가 반드시 눌러야 하는 중요한 버튼에는 ElevatedButton이 적합하다.
TextButton은 배경이나 입체감이 거의 없는 평면 버튼이다.
텍스트 링크처럼 가볍게 보여줄 때 사용한다.
TextButton(
onPressed: () {
print('회원가입');
},
child: const Text('회원가입'),
)
사용하기 좋은 상황은 다음과 같다.
TextButton 사용 상황
- 회원가입
- 비밀번호 찾기
- 취소
- 더보기
- 중요도가 낮은 액션
예를 들어 로그인 화면에서 “회원가입”이나 “비밀번호 찾기”는 TextButton으로 표현하기 좋다.
IconButton은 아이콘을 버튼처럼 사용할 때 쓰는 위젯이다.
짧고 직관적인 동작에 적합하다.
IconButton(
onPressed: () {
print('검색');
},
icon: const Icon(Icons.search),
)
사용하기 좋은 상황은 다음과 같다.
IconButton 사용 상황
- 검색
- 뒤로가기
- 삭제
- 좋아요
- 설정
- 메뉴 열기
아이콘만 보고 의미를 알 수 있는 동작에 사용하면 좋다.
FloatingActionButton은 화면 위에 떠 있는 둥근 버튼이다.
보통 화면에서 가장 중요한 하나의 작업을 나타낼 때 사용한다.
FloatingActionButton(
onPressed: () {
print('추가');
},
child: const Icon(Icons.add),
)
Scaffold의 floatingActionButton 속성에 넣어서 사용한다.
Scaffold(
appBar: AppBar(
title: const Text('메모'),
),
body: const Center(
child: Text('메모 목록'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
print('메모 추가');
},
child: const Icon(Icons.add),
),
)
사용하기 좋은 상황은 다음과 같다.
FloatingActionButton 사용 상황
- 글쓰기
- 메모 추가
- 항목 추가
- 사진 촬영
- 주요 생성 액션
Navigator는 Flutter에서 화면 이동을 관리하는 클래스다.
Flutter는 화면을 스택 구조로 관리한다.
화면 스택
- 화면이 아래에서 위로 쌓이는 구조
- 새 화면으로 이동하면 스택 위에 추가됨
- 뒤로 가기를 누르면 맨 위 화면이 제거됨
예를 들어 A 화면에서 B 화면으로 이동하면 다음과 같다.
처음 상태
[PageA]
Navigator.push 실행
[PageB]
[PageA]
뒤로 가기를 누르면 PageB가 제거되고 PageA가 다시 보인다.
뒤로 가기
[PageA]
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 사용 예시
- 상세 화면 이동
- 설정 화면 이동
- 게시글 보기
- 상품 상세 보기
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('이동'),
)
두 메서드는 모두 화면 이동에 사용하지만 이전 화면을 남기는지 여부가 다르다.
Navigator.push
- 새 화면을 현재 화면 위에 추가
- 뒤로 가기 가능
- 이전 화면이 스택에 남아 있음
Navigator.pushReplacement
- 현재 화면을 새 화면으로 교체
- 뒤로 가기로 이전 화면에 돌아갈 수 없음
- 이전 화면을 제거함
선택 기준은 다음과 같다.
이전 화면으로 돌아가야 한다
-> Navigator.push
이전 화면으로 돌아가면 안 된다
-> Navigator.pushReplacement
예시로 보면 다음과 같다.
상품 목록 -> 상품 상세
-> Navigator.push
로그인 -> 홈
-> Navigator.pushReplacement
스플래시 -> 메인
-> Navigator.pushReplacement
설정 화면 -> 약관 화면
-> Navigator.push
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 위젯 생성
- 불필요한 재빌드 감소에 도움
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를 기준으로
- 위젯 트리를 거슬러 올라가면서
- 가장 가까운 해당 타입의 위젯이나 데이터를 찾음
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를 이용해 실제 화면을 관리한다.
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 생성
- 현재 위치 기준으로 상위 위젯 탐색 가능
Builder는 새로운 context를 만들어주는 역할이 핵심이다.
다만 경우에 따라 하위 위젯을 분리해서 불필요한 build 범위를 줄이는 데도 도움이 될 수 있다.
하지만 단순히 Builder로 감싼다고 무조건 성능이 좋아지는 것은 아니다.
성능 최적화가 목적이라면 보통 다음 방식도 함께 고려한다.
성능 최적화 방법
- const 생성자 사용
- 위젯을 작은 단위로 분리
- 상태가 필요한 부분만 StatefulWidget으로 분리
- ValueListenableBuilder / StreamBuilder / Consumer 등 사용
- 불필요한 setState 범위 줄이기
정리하면 다음과 같다.
Builder의 주요 역할
- 새로운 BuildContext 제공
Builder가 도움 되는 경우
- 특정 위치의 context가 필요할 때
- 하위 트리를 별도 builder 함수로 분리하고 싶을 때
주의할 점
- Builder만으로 자동 성능 최적화가 되는 것은 아님
Flutter 개발 중 필요한 패키지나 파일을 import해야 할 때 IDE 단축키를 사용할 수 있다.
macOS 기준으로 자주 사용하는 단축키는 다음과 같다.
Option + Enter
- 에러나 경고에 대한 빠른 수정 메뉴 열기
- import 추가 가능
Option + Shift + Enter
- 상황에 따라 자동 수정 또는 import 관련 기능 실행
Windows에서는 보통 다음 단축키를 사용한다.
Alt + Enter
- 빠른 수정 메뉴 열기
- import 추가
- 오류 수정 제안 확인
단축키는 Android Studio 설정이나 OS에 따라 다를 수 있다.
아래 코드는 버튼을 눌렀을 때 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로 교체
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는 그 입력에 따라 화면을 이동시키는 역할을 한다.