https://www.youtube.com/watch?v=6oGl_nkyDAU
1. 이건 꼭 알아야 한다[^1]
[? 질문] Flutter 앱을 만들 때, 로그인 전/후에 서로 다른 화면(로그인 페이지 vs 탭 페이지)을 어떻게 구조적으로 설계하면 좋은가[^12]
[= 답] 최상위에 **루트 페이지(RootPage)**를 두고, 그 안에서 “로그인 여부” 조건에 따라 로그인 페이지(LoginPage) 또는 **탭 페이지(TabPage)**를 보여주도록 제어한다.[^15]
[? 질문] 하단 탭(3개)을 눌렀을 때, 선택 표시와 화면 내용이 바뀌게 하려면 무엇이 필요한가[^27]
[= 답] 탭 선택 상태를 저장하는 변수(예: selectedIndex)를 StatefulWidget에 두고, 탭 클릭 시 setState()로 값을 변경해 UI를 다시 그리며, BottomNavigationBar.currentIndex와 body를 selectedIndex에 연동한다.[^29]
[? 질문] 기능(로그인 처리 등)을 완성하기 전에 UI부터 만들면 왜 유리한가[^11]
[= 답] 로그인 기능부터 붙이면 복잡해지므로, 먼저 페이지/화면의 **뼈대(UI와 화면 흐름)**를 만들어두고 이후 기능을 하나씩 추가하는 방식이 작업을 단순화한다.[^11]
2. 큰 그림[^2]
이 콘텐츠는 Flutter로 새 프로젝트를 만든 뒤, 앱 실행 시 보여줄 화면 흐름을 먼저 설계하고, 그중 핵심인 **탭 기반 메인 화면(하단 BottomNavigationBar)**의 기본 뼈대를 구현하는 과정을 따라간다.[^12] 또한 로그인 전/후 화면을 분기하기 위한 루트 페이지 개념을 도입해, 향후 기능 추가(로그인 처리)를 쉽게 확장할 수 있는 구조로 만든다.[^15]
- UI를 먼저 만들고 기능을 나중에 추가하는 개발 순서를 제안한다.[^11] 로그인부터 구현하면 복잡도가 올라가니, 화면 설계와 뼈대 작성으로 진행한다.[^11]
- 로그인 여부에 따라 화면을 갈라야 하므로 RootPage가 상위에서 제어하도록 설계한다.[^15]
MyApp → RootPage → (TabPage 또는 LoginPage)흐름을 만든다.[^16] - 탭 UI는 BottomNavigationBar + selectedIndex + setState + body 리스트로 구성된다.[^25] 탭 클릭 시 선택 표시만 바뀌는 문제를 상태 관리로 해결하고, 본문(body)도 함께 바뀌게 연결한다.[^29]
3. 하나씩 살펴보기[^3]
3.1 새 Flutter 프로젝트 생성과 기본 설정[^3]
영상은 “새 플로터 프로젝트를 만들어 보겠다”는 말로 시작하며, Flutter 프로젝트 생성 마법사 흐름을 그대로 따라간다.[^3]
- “Application”을 선택하고 Next로 진행한다.[^4]
- 프로젝트 이름을 적당히 작성한다.[^5]
- 설명(description)도 함께 입력하는데, 예시로 “인스타그램 … 프로젝트” 같은 식으로 적는다고 말한다.[^6]
- 다음 단계에서 회사 도메인(company domain)을 설정한다.[^7]
- 실제 출시용이라면 기본값(예: example.com 형태)을 그대로 쓰면 안 된다고 주의를 준다.[^7]
- 본인이 소유/사용 중인 도메인이 있으면 그 도메인을 쓰고, 없다면 본인 이름 이니셜 등의 식별자로 대체하라고 안내한다.[^8]
- 여기서는 연습이므로 그대로 진행한다고 한다.[^9]
- 이후 Kotlin/Swift 관련 체크 옵션을 켠 상태로 진행한다고 언급한다.[^10] (플러터 프로젝트 생성 옵션 단계에서 플랫폼 관련 설정을 포함하는 흐름을 밟는 것으로 보인다.)[^10]
- 프로젝트 생성 후
main.dart가 열려 있는 상태를 확인한다.[^10]
[!NOTE] 왜 프로젝트 초반에 도메인을 신경 쓰나[^7]
콘텐츠에서는 “실제로 출시”를 전제로 할 때 기본 도메인을 그대로 쓰면 안 된다고 말한다.[^7] 즉, 앱 번들 식별자/패키지 네임과 연관되는 설정이므로 초반에 정리해두라는 맥락이다.[^7]
3.2 기본 템플릿 정리: HomePage 부분 삭제[^10]
프로젝트가 열리면 기본으로 들어있는 템플릿(카운터 앱 등) 중 하단의 “HomePage” 관련 부분을 삭제하겠다고 한다.[^10] 이 단계는 “기본 샘플을 유지한 채 수정”이 아니라, 앱에 필요한 화면 구조를 직접 설계하고 뼈대를 만들기 위해 템플릿을 걷어내는 정리 단계로 진행된다.[^10]
3.3 기능보다 UI/페이지 설계를 먼저: 로그인부터 하면 복잡해진다[^11]
여기서 개발 순서에 대한 원칙을 제시한다.[^11]
- 먼저 로그인 기능부터 만들면 복잡해질 수 있으니[^11]
- UI를 먼저 만들어 놓고, 이후에 기능을 하나씩 추가하는 게 편하다고 말한다.[^11]
- 따라서 지금은 “페이지 설계를 하면서” 진행하는 것이 좋겠다고 방향을 잡는다.[^12]
[c 로그인 기능 구현을 늦추고 화면 뼈대(페이지 구조)를 먼저 확정하는 것이 이 영상의 핵심 전략이다.][^11]
3.4 화면 흐름(IA) 정의: LoginPage → TabPage, 그리고 RootPage 도입[^12]
앱을 실행했을 때의 화면 흐름을 먼저 정의한다.[^12]
- 앱 최초 실행 시 나타나야 하는 페이지는 **로그인 페이지(LoginPage)**라고 한다.[^12]
- 로그인 페이지에서 “로그인 클릭”을 하면 다음 페이지로 넘어가야 하며, 그 다음 페이지는 하단에 3가지 탭이 있는 화면이라고 설명한다.[^13]
- 이 “하단에 3가지가 있는 화면”을 **탭 페이지(TabPage)**라고 이름 붙이면 좋겠다고 한다.[^14]
그런데 Flutter 기본 프로젝트는 MyApp 위젯이 있고, 그 아래 body에 페이지를 작성하는 방식으로 시작한다는 점을 상기시킨다.[^14] 여기서 상태에 따라 처음 보여줄 페이지가 달라진다:
- 로그인을 안 했으면 로그인 페이지가 나오는 게 맞다.[^14]
- 로그인을 한 상태라면 탭 페이지가 보여지는 게 맞다.[^15]
따라서 “이것을 제어해야 되는 상위 페이지가 하나 더 있는 게 좋겠다”고 결론 내리고, 그 상위 페이지를 **루트 페이지(RootPage)**로 만들겠다고 한다.[^15] 루트 페이지 내부에서 조건을 보고:
- 로그인 했으면 TabPage로[^15]
- 로그인 안 했으면 LoginPage로 보내도록[^15]
코드를 작성하면 될 것이라고 정리한다.[^15]
정리된 호출 구조는 다음 흐름이다.
MyApp아래에서 RootPage를 호출하고[^16]- RootPage 안에서 TabPage 또는 LoginPage를 호출한다.[^16]
3.5 파일 분리: TabPage / LoginPage / RootPage 생성[^17]
이제 필요한 화면들을 파일로 만든다.[^17]
lib폴더에서 New → Dart file을 생성한다.[^17]- 파일을 3개 만든다:
tab_page.dart(영상에서는 “탭 언더바 페이지”로 발화)[^17]login_page.dart[^18]root_page.dart(두 개를 제어할 파일)[^18]
이 단계는 “화면을 위젯 단위로 파일 분리”하여 구조를 명확히 하는 작업이다.[^18]
3.6 RootPage 구현 방향: 상태가 자주 변하지 않으니 StatelessWidget 선택[^19]
RootPage에 대해 성격을 설명한다.[^19]
- RootPage는 “이 화면을 보여줄까, 저 화면을 보여줄까”를 결정하는 부분이라고 말한다.[^19]
- “한 번 보여주면 보여준 상태에서 또 상태가 변하거나 그러지 않는 페이지”라고 설명한다.[^19]
- 그래서 StatelessWidget으로 만드는 게 더 좋겠다고 한다.[^20]
즉, RootPage는 앱 전역 상태(로그인 여부)에 따라 초기 분기를 담당하는 컨테이너 역할로 보고, 잦은 인터랙션 상태를 직접 들고 가지는 화면으로 보지 않는다.[^20]
이후 RootPage를 RootPage extends StatelessWidget 형태로 만들고 그 안에 화면을 구성하면 된다고 말한다.[^20]
- 로그인 했는지/안 했는지 처리는 “나중에” 할 것이고[^21]
- 일단은 TabPage를 보여주게 만들겠다고 한다.[^21]
3.7 TabPage는 왜 StatefulWidget인가: 탭/페이지 전환은 상태 변화가 필요[^22]
TabPage는 다음 이유로 StatefulWidget으로 만든다고 설명한다.[^22]
- “페이지가 여러 개가 있을 거고”[^22]
- “상태가 변하면서 다른 페이지로 변경해주는 행위”가 필요하다고 말한다.[^22]
즉, 탭을 눌러 선택된 페이지가 바뀌는 것은 UI 상태 변경이므로, 그 상태를 저장하고 다시 그리는 구조가 필요하다는 논리다.[^22]
초기 확인을 위해:
Container에Text("Tab Page")같은 텍스트를 넣어서 화면이 뜨는지 확인한다.[^23]- 그런 다음 RootPage에서 TabPage를 불러오도록 연결한다.[^23]
연결 시 빨간 줄(에러)이 뜨는 이유는 import가 안 되었기 때문이라고 설명한다.[^24] 윈도우 기준으로 Alt+Enter를 누르면 “Import library”로 자동 추가된다고 안내한다.[^24]
3.8 LoginPage 뼈대도 함께 생성하고, MyApp의 홈을 RootPage로 변경[^25]
로그인 페이지는 아직 내용이 없어도 파일과 위젯을 만들어 둔다.[^25]
material.dart를 import하고[^25]- LoginPage는 일단 StatelessWidget으로 만들어도 된다고 말한다.[^25]
- 나중에 확인하기 위해 화면에 텍스트 등을 임시로 넣어둔다(“이런 식으로 해 놓게…”라는 맥락).[^26]
그리고 main.dart(혹은 앱 시작 위젯)에서 홈을 RootPage로 고치면 될 것이라고 말한다.[^26] 이 과정에서도 Alt+Enter로 import를 해결할 수 있다고 덧붙인다.[^26]
이후 실행해서 “TabPage까지 가는 것”을 확인한다.[^26] 즉, 앱이 정상 빌드되고 RootPage→TabPage 라우팅이 되었는지 검증한다.[^26]
3.9 TabPage를 머티리얼 화면으로: Scaffold + BottomNavigationBar 배치[^27]
이제 TabPage를 “예쁘게”, 즉 머티리얼 디자인 구조로 만든다고 말한다.[^27]
- 기존
Container는 삭제한다.[^27] Scaffold를 사용해야 한다고 한다.[^27] (머티리얼 앱의 기본 화면 골격)
그리고 하단의 탭 UI를 구현한다:
- 하단에 3개 탭이 있고, 하나씩 누르면 화면이 변경되어야 한다고 목표를 말한다.[^28]
- 이를 위해
Scaffold.bottomNavigationBar에 BottomNavigationBar를 둔다고 설명한다.[^28] BottomNavigationBar.items에BottomNavigationBarItem리스트를 넣어 구성한다.[^28]
각 탭 아이템 구성 방식:
- 아이콘을 배치할 수 있고, 예로
Icons.home를 쓰면 홈 그림이 들어간다고 한다.[^28] - 타이틀(라벨) 글자는
Text로 작성하면 들어간다고 말한다.[^28] - 이를 3개 복사해서 탭 3개를 만든다.[^28]
- 1번:
home, 라벨 “Home”[^28] - 2번: 아이콘
search, 라벨 “Search”[^28] - 3번: 아이콘
account_circle, 라벨은 계정 관련으로 구성했다고 말한다.[^28]
- 1번:
저장 후 화면에 3개 탭이 생긴 것을 확인한다.[^28]
3.10 “클릭 효과만 있고 화면이 안 바뀜” 문제 진단: 상태 변경과 재빌드 필요[^28]
탭을 클릭하면 “클릭 효과는 하지만 변경이 안 된다”고 말한다.[^28] 이어서 그 이유를 설명한다:
- 변하지 않는 이유는 “상태가 변경되면서 다시 그려주는 행위가 필요”하기 때문이라고 말한다.[^29]
즉, BottomNavigationBar만 배치해두면 UI는 그려지지만, “현재 선택된 탭” 상태를 앱이 저장/반영하지 않으면 실제 전환이 일어나지 않는다.[^29]
3.11 selectedIndex 도입과 currentIndex 연결[^29]
이를 해결하기 위해 상태 변수를 추가한다.[^29]
- 정수형으로 “현재 페이지 상태를 가질 수 있는 변수”를 만들고 이름을
selectedIndex로 둔다.[^29] - 초기값은 0(0번째)로 둔다.[^29]
그리고 BottomNavigationBar 속성 중:
currentIndex를 지정할 수 있다고 설명하며[^29]currentIndex: selectedIndex로 연결한다.[^29]
이렇게 저장하면 일단 겉보기엔 비슷하지만, 이제 선택 상태를 코드가 제어할 준비가 된 것이다.[^29]
3.12 탭 클릭 시 인덱스 변경: onTap → 메서드 → setState[^29]
다음 단계는 “클릭했을 때 인덱스 값을 바꿔주기”이다.[^29]
BottomNavigationBar.onTap을 사용한다.[^29]- onTap 콜백의 인자
int index는 “탭이 된 아이템의 번호”라고 설명한다.[^29]
구현 방식:
- 메서드를 하나 만들겠다고 하며 이름을
onItemTapped로 한다.[^29] - Alt+Enter로 메서드 생성을 도와줄 수 있다고 말한다.[^29]
- 이 메서드로 탭된 인덱스가 들어오며, 여기서 할 일은 “상태를 변경하는 것”이라고 한다.[^29]
setState()로 감싼 뒤selectedIndex = index;로 값을 바꾼다.[^29]
저장 후 클릭하면 “이렇게 상태가 변해요”라고 하며, 하단 탭의 선택 표시가 바뀌는 것을 확인한다.[^30] 이로써 “밑에는 이제 됐어요”라고 말해, 하단 선택 상태 반영까지 완료되었음을 확인한다.[^30]
[!TIP] 탭 UI가 안 바뀔 때 가장 먼저 볼 것[^29]
BottomNavigationBar에currentIndex가 연결돼 있는지,onTap에서setState()로 인덱스를 바꾸는지 확인하라는 흐름으로 문제를 해결한다.[^29]
3.13 본문(body)도 탭에 따라 바꾸기: pages 리스트 + selectedIndex로 선택[^30]
이제 진짜 목표인 “탭을 클릭했을 때 여기 있는 내용(화면 본문)이 변경”되어야 한다고 말한다.[^30]
- 현재는
Scaffold.body를 설정하지 않았으므로, body를 설정해줘야 한다고 한다.[^30] - 3가지 탭이므로 body도 “3가지가 변경”되어야 하고, 상태에 따라 다른 값이 오도록 코드를 짜야 한다고 설명한다.[^30]
구체적인 구현 방법:
- “페이지들을 미리 가지고 있도록” 리스트를 만든다.[^30]
- 연습이므로 복잡한 위젯 대신 텍스트로 된 페이지를 만들어 둔다고 한다.[^30]
Page1,Page2,Page3같은 텍스트 위젯을 준비하는 방식이다.[^30]
- 그리고 body를
pages[selectedIndex]형태로 두면[^30]- selectedIndex가 0이면 0번째 페이지, 1이면 1번째, 2면 2번째가 선택된다고 설명한다.[^30]
또한 탭을 누를 때마다:
setState()가 호출되면서 다시 그려지게 된다고 말한다.[^30]- 즉
selectedIndex변경 → rebuild → body가 다른 위젯으로 바뀌는 연결 구조를 강조한다.[^30]
3.14 텍스트가 안 보이는 문제 해결: Center로 감싸기[^30]
저장 후, “맨 꼭대기 보이지 않는 부분에 글자가 있는데…”라고 하며 텍스트가 원하는 위치에 잘 안 보이는 상황을 언급한다.[^30] 이를 해결하기 위해:
body를Center로 묶어서 다시 실행해보겠다고 한다.[^30]
그 결과:
Page1이 보이고[^30]- 클릭하면
Page2,Page3로 바뀌는 것을 확인한다.[^30] - 작아서 안 보일 수도 있지만 변경은 된다고 말한다.[^30]
3.15 결론: 탭 페이지 뼈대 완성, 이제 각 페이지 구현/연결만 남음[^30]
마지막으로 “일단은 뼈대는 완성이 된 것 같아요”라고 정리한다.[^30] 즉, 현재까지 완성한 것은:
- TabPage에 BottomNavigationBar 3개 탭 배치[^28]
- 탭 선택 상태 관리(selectedIndex, currentIndex)[^29]
- 탭에 따라 body가 변경되는 구조(pages 리스트 + 인덱싱)[^30]
이제 해야 할 일은:
- 이 페이지(각 탭)에 해당하는 실제 페이지들을 구현하고[^30]
- 각각 연결해주면 된다고 마무리한다.[^30]
4. 핵심 통찰[^31]
-
로그인 전/후 화면이 갈리는 앱은 **최상위 제어 화면(RootPage)**를 둬야 구조가 깔끔해진다.[^15]
- 로그인 상태 체크 로직을 RootPage에 넣으면
MyApp과 개별 페이지가 단순해진다는 전개다.[^16]
- 로그인 상태 체크 로직을 RootPage에 넣으면
-
탭 전환 UI는 “컴포넌트 배치”만으로는 완성되지 않고, 반드시 **상태(selectedIndex) + setState()**가 필요하다.[^29]
- 선택 표시(currentIndex)와 본문(body)을 모두 상태에 묶어야 “진짜 전환”이 된다.[^30]
-
기능 구현은 뒤로 미루고 UI/페이지 설계부터 가면 복잡도를 낮출 수 있다.[^11]
- 로그인 기능을 먼저 붙이는 대신, 화면 흐름(로그인→탭)과 뼈대를 먼저 만든 뒤 확장한다는 작업 전략이다.[^12]
실행 관점에서의 행동 항목:
MyApp의home은 곧바로 “실제 첫 화면”이 아니라 RootPage로 두고, RootPage에서 분기 로직을 관리한다.[^16]- 탭 화면은
BottomNavigationBar에currentIndex와onTap을 반드시 연결하고,body도selectedIndex기반으로 바뀌게 만든다.[^29] - 실제 페이지 구현 전에는
Text("Page1")같은 더미 페이지로 구조 검증을 먼저 한다.[^30]
5. 헷갈리는 용어 정리[^32]
RootPage(루트 페이지): 로그인 여부 같은 조건을 보고 어떤 페이지를 보여줄지 결정하는 상위 페이지로, MyApp 아래에서 먼저 호출되며 LoginPage/TabPage를 분기한다.[^15]
TabPage(탭 페이지): 하단에 여러 탭이 있고 탭 선택에 따라 본문이 바뀌는 메인 화면으로, 상태 변화가 많아 StatefulWidget이 적합하다고 설명한다.[^22]
BottomNavigationBar(바텀 내비게이션 바): 하단 탭 UI 컴포넌트로, items로 탭을 구성하고 currentIndex, onTap으로 선택 상태를 연결한다.[^28]
selectedIndex: 현재 선택된 탭/페이지의 인덱스를 저장하는 상태 변수로, 클릭 시 setState()로 변경해 화면을 다시 그리게 한다.[^29]
setState(): 상태가 바뀌었음을 Flutter에 알리고 위젯을 다시 그리게(rebuild) 하는 호출로, 탭 선택/본문 변경에 핵심이라고 설명한다.[^29]
Scaffold(스캐폴드): 머티리얼 디자인 화면의 기본 골격 위젯으로, body, bottomNavigationBar 같은 영역을 제공한다.[^27]
참고(콘텐츠 정보)[^1]
- 제목: Flutter 입문. 안드로이드, iOS 개발을 한 번에 #24 화면 설계, 뼈대 작성[^1]
- 채널: 오준석의 생존코딩[^1]
- 길이: 10분 2초[^1]
- 링크: https://www.youtube.com/watch?v=6oGl_nkyDAU[^1]
[^1]: 제공된 콘텐츠 메타데이터: "Flutter 입문... #24 화면 설계, 뼈대 작성", 채널 "오준석의 생존코딩", 길이 10분 2초, 링크 https://www.youtube.com/watch?v=6oGl_nkyDAU
[^2]: @[00:04] "어플리케이션 넥스트 하시구요"
[^3]: @[00:00] "그럼 새로운 플로터 프로젝트를 만들어 보도록 하겠습니다"
[^4]: @[00:04] "어플리케이션 넥스트 하시구요"
[^5]: @[00:04] "그리곤 프로젝트 이름을 적당하게..."
[^6]: @[00:10] "설명도 ... 이런식으로 작성"
[^7]: @[00:14] "실제로 출시를 하신다면 ... 풀쩜컴 ... 하면 안 되니까"
[^8]: @[00:25] "도메인이 있다면 작성을... 아니라면 ... 이니셜... 대체"
[^9]: @[00:33] "저는 뭐 연습 이니까 그냥..."
[^10]: @[00:39] "코틀린 스위프트 ... 체크 ... 메인 점 다트 파일..." / @[00:44] "홈페이지 부분은 다 삭제"
[^11]: @[00:52] "먼저 로그인 부분 부터 만들려고 하면 복잡해 지니까 ui를 만들어 놓고..."
[^12]: @[01:01] "페이지 설계를 하면서 가는게 좋을 것 같아요" / @[01:06] "처음에 앱을 실행했을 때 ... 로그인 페이지"
[^13]: @[01:12] "로그인 ... 클릭 ... 다음에 이렇게 페이지"
[^14]: @[01:18] "하단에 ... 3가지" / @[01:30] "마이 앱이라는 위젯이 기본"
[^15]: @[01:47] "로그인을 한 상태라면 ... 탭 페이지" / @[01:52] "상위의 페이지" / @[01:59] "루트 페이지 ... 조건을 보고"
[^16]: @[02:14] "마이 앱 아래의 ... 루트 페이지 ... 루트 페이지 안에서 탭 페이지나 로그인 페이지"
[^17]: @[02:32] "필요한 화면들을 파일로..." / @[02:36] "lib 폴더 ... 다트 파일 탭 페이지"
[^18]: @[02:44] "로그인 ... 루트 ..."
[^19]: @[02:54] "루트 페이지 ... 이 화면을 보여 줄까..."
[^20]: @[03:17] "스테이트리스 ... 만드는게 더 좋겠습니다" / @[03:25] "루트 페이지로 이름"
[^21]: @[03:32] "로그인을 했는지 안했는지 처리는 나중" / @[03:39] "일단 탭 페이지를 보여"
[^22]: @[03:47] "얘는 스테이트풀" / @[03:52] "페이지가 여러 개 ... 상태가 변하면서"
[^23]: @[04:03] "컨테이너 ... 텍스트 ... 탭 페이지" / @[04:06] "루트 페이지에서 부르도록"
[^24]: @[04:13] "빨간불 ... 인포트를 안했기" / @[04:19] "알트 엔터 ... 자동으로 추가"
[^25]: @[04:27] "로그인 페이지는 아직... 만들어 놓을게요" / @[04:34] "마테리얼 다트 임포트" / @[04:38] "스테이트리스"
[^26]: @[04:55] "루트 페이지로 고치면" / @[05:02] "임포트 라이브러리" / @[05:04] "실행... 탭 페이지까지"
[^27]: @[05:16] "머티리얼 디자인 ... 컨테이너 삭제" / @[05:22] "스케폴드"
[^28]: @[05:26] "하단에 ... 탭" / @[05:37] "바탐 내비게이션 바" / @[06:03] "홈 아이콘" / @[06:19] "서치" / @[06:26] "어카운트 서클" / @[06:36] "3개 생겼어요 ... 변경이 안됩니다"
[^29]: @[06:43] "상태가 변경되면서 다시 그려주는" / @[06:57] "selectedIndex" / @[07:18] "currentIndex" / @[07:33] "onTap ... index" / @[08:03] "setState ... selectedIndex 값을 index로"
[^30]: @[08:12] "클릭하면 ... 상태가 변해요" / @[08:25] "내용이 변경" / @[08:47] "리스트 ... pages" / @[09:12] "setState ... 다시 그려" / @[09:39] "센터로 묶어서" / @[09:52] "뼈대는 완성"
[^31]: @[09:54] "각각 페이지를 구현 ... 연결"
[^32]: @[05:37] "바탐 나비게이션 바" / @[07:12] "currentIndex" / @[08:03] "setState"