9일차에는 페이지 간 이동, 컨트롤러 등 위젯들을 좀 더 다루는 내용을 학습했다.
학습한 내용
- Controller
- Navigator
Route는 하나의 화면(페이지)이다.
Navigator는 모든 앱 페이지를 관리하며 stack이라는 자료구조 형식으로 라우터의 객체들을 관리한다. Navigator는 push()
pop()
등의 메서드를 제공한다.
Navigator.push()
의 인자로 context
와 MaterialPageRoute
위젯을 사용하여 다른 페이지로 이동할 수 있다.
Navigator.pop()
의 인자로는 자신의 페이지 정보인 context만 전달하면 된다.
기본적인 push
pop
메소드의 사용 방법은 아래 링크를 통해 확인할 수 있다.
새로운 화면으로 이동하고, 되돌아오기 - Flutter
아래 코드처럼 Route를 선언할 수 있다. Screen1() 등은 각 화면의 클래스 이름이다.
new MaterialApp(
home: new Screen1(),
routes: <String, WidgetBuilder> {
'/screen1': (BuildContext context) => new Screen1(),
'/screen2' : (BuildContext context) => new Screen2(),
'/screen3' : (BuildContext context) => new Screen3(),
'/screen4' : (BuildContext context) => new Screen4()
},
)
Flutter의 스택 구조는 크게 아래와 같은 두 가지 방식으로 작동한다.
위의 코드처럼 라우트를 선언하면 아래와 같이 pushNamed
메소드를 사용하여 화면을 이동할 수 있다.
new RaisedButton(
onPressed:(){
Navigator.of(context).pushNamed('/screen2');
},
child: new Text("Push to Screen 2"),
),
처음 화면이 Screen1이고 위의 예시 코드처럼 Screen2로 이동했다면 아래 그림처럼 스택이 쌓이게 된다.
이 상태에서 아래 코드처럼 pop
메소드를 사용하면 네비게이터의 스택에서 가장 위 화면인 Screen2가 제거된다.
Navigator.of(context).pop();
또 다른 메소드인 pushReplacementNamed
나 popAndPushNamed
를 사용하면 현재 라우트를 새로운 라우트로 변경할 수도 있다.
Navigator.of(context).pushReplacementNamed('/screen4');
//and
Navigator.popAndPushNamed(context, '/screen4');
인스타그램과 같은 응용 프로그램에서 사용자가 로그인 후에 피드를 스크롤하고, 다른 프로필을 통해 이동하는 등의 활동을 한 뒤 로그아웃을 하게되면 이전 라우트로는 돌아갈 수 없도록 해야한다. 이 경우에는 아래 코드처럼 pushNamedAndRemoveUntil
메소드를 사용할 수 있다.
Navigator.of(context).pushNamedAndRemoveUntil('/screen4', (Route<dynamic> route) => false);
(Route<dynamic> route) => false
는 push
된 라우트 이전의 모든 라우트를 제거하도록 한다.
이외에도 Navigator는 다양한 메소드를 제공하며 스택 구조로 동작한다. 자세한 사용법과 메소드들은 아래 링크를 참고
[Flutter][번역] Flutter Navigator
- ScrollController를 사용해 최상단으로 이동하는 기능 구현
- 입력된 텍스트를 미러링하는 화면 제작
- 1번과제와 2번과제로 이동하는 버튼 페이지 구현
- PageController의 viewportFraction 속성 테스트
- 하나의 TextEditingController를 두 개의 TextField에 똑같이 연결했을 때 결과 테스트
아래와 같은 요구사항으로 FloatingActionButton
을 눌렀을 때 스크롤 위치가 최상단으로 이동하는 화면을 구성하고자 한다.
요구사항
- ListView.builder 위젯을 활용하여 높이가 300인 동물 위젯을 생성합니다.
- 이 때, 사용되는 데이터는 다음과 같습니다.
List animalList = ['강아지', '고양이', '앵무새', '토끼', '오리', '거위', '원숭이'];
- 하단의 FAB(FloatingActionButton)을 누르면, 스크롤 위치가 최상단으로 이동되게합니다.
- 이 때, 사용되는 아이콘 명은 다음과 같습니다.
Icons.vertical_align_top
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// root Widget
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: '9일차 과제'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title; //앱 제목
Widget build(BuildContext context) {
List animalList = ['강아지', '고양이', '앵무새', '토끼', '오리', '거위', '원숭이']; //동물 리스트
var scrollController = ScrollController(); //리스트뷰 스크롤 컨트롤러
return Scaffold(
appBar: AppBar(
title: Text(title),
centerTitle: true,
),
body: ListView.builder(
controller: scrollController, //컨트롤러 연결결
itemCount: animalList.length,
itemBuilder: ((context, index) {
return Container(
height: 300,
alignment: Alignment.center,
child: Text(
animalList[index],
),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => scrollController.jumpTo(0), //스크롤 최상단 이동
child: Icon(Icons.vertical_align_top),
),
);
}
}
MaterialApp
에서 title
을 '9일차 과제'로 넣어 호출한다. MyHomePage
에서는 우선 동물들의 이름이 담긴 리스트인 animalList
와 스크롤 컨트롤러를 선언했다.
본문의 내용은 ListView.builder
로 생성했으며 맨 처음 선언한 scrollController
를 연결했다. itemCount
는 동물 리스트의 길이로 설정했다. itemBuilder
로 컨테이너를 리턴하는데 높이를 300으로 설정했다. child로는 동물의 이름을 출력한다.
FloatingActionButton
은 눌렀을 때 스크롤이 최상단으로 이동하도록 onPressed
에서 scrollController.jumpTo(0)
를 사용했다.
아래와 같은 요구사항으로 입력된 텍스트를 미러링하는 화면을 제작하고자 한다.
요구사항
- TextField에 입력시, 바로 밑에 위치한 하단의 Text위젯에 똑같이 적용되도록 합니다.
- FAB(FloatingActionButton)을 클릭하면, 작성중이던 모든 내용이 사라집니다.
- 이 때, 사용되는 아이콘 명은 다음과 같습니다.
Icons.close
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// root Widget
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: '2번 문제'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title; //앱 제목
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var textController = TextEditingController(); //텍스트필드 컨트롤러
//위젯이 dispose 될 때, 컨트롤러도 dispose
void dispose() {
textController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
centerTitle: true,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//입력 필드
TextField(
controller: textController,
onChanged: (value) => setState(() {}),
),
//압력된 값 출력
Text(textController.text),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
textController.clear();
});
},
child: Icon(Icons.close),
),
);
}
}
화면이 입력되는 값에 따라 계속 바뀌기 때문에 MyHomePage
위젯은 Stateful Widget
으로 만들었다.
TextField
에 연결할 컨트롤러를 MyHomePage
에 생성했다. build
내부에 생성하게 되면 setState
로 화면을 다시 그릴 때마다 build
가 호출되어 컨트롤러가 초기화 되므로 build
밖에 생성해야 한다.
Column
으로 TextField
와 Text
위젯을 구성했다. TextField
는 앞에서 생성한 textController
를 연결하고 onChanged
에 setState
메소드를 호출해 변경될 때마다 화면을 다시 그릴 수 있도록 했다.
Text
위젯에는 textController.text
를 출력했다.
FloatingActionButton
을 생성하고 onPressed
이벤트에 setState
메소드를 호출하고 내부에서 textController.clear()
를 작성해 버튼 클릭 시 컨트롤러의 내용이 모두 지워지도록 했다.
TextEditingConroller
에 대한 자세한 내용은 아래의 공식문서를 참고
TextEditingController class
아래와 같은 요구사항으로 버튼을 클릭하면 페이지가 이동하는 화면을 구현하고자 한다.
요구사항
- 1번 과제를 클릭하면, 1번 과제의 내용 페이지로 이동됩니다.
- 2번 과제를 클릭하면, 2번 과제의 내용 페이지로 이동됩니다.
import 'package:flutter/material.dart';
import 'package:my_app/FirstProblem.dart';
import 'package:my_app/SecondProblem.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// root Widget
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: '9일차 과제'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title; //앱 제목
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//1번과제 이동 버튼
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FirstProblem(title: '1번 과제'),
));
},
child: Text('1번 과제'),
),
SizedBox(height: 200), // 버튼 사이 간격
//2번과제 이동 버튼
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondProblem(title: '2번 과제'),
));
},
child: Text('2번 과제'),
),
],
),
),
);
}
}
우선 앞서 만들었던 1번 과제와 2번 과제의 코드에서 MyHomePage
위젯의 이름을 각각 FirstProblem
과 SecondProblem
으로 바꾸고 FirstProblem.dart
와 SecondProblem.dart
파일을 생성해 옮겨 주었다.
두 버튼은 Column
을 사용해 배치했고, 가운데 위치하도록 Center
위젯으로 감싸주었다.
버튼은 ElevatedButton
을 사용했고, 내용은 child로 Text
위젯을 사용했다.
각 버튼은 onPressed
이벤트에서 Naviagtor.push()
함수를 사용해 눌렸을 때 페이지가 이동하도록 작성했다.
PageView
에 사용할 수 있는 PageController
에는 viewportFraction
이라는 속성이 있다. 이 속성을 설정하면 페이지에서 좌우 여백에 이전과 다음 페이지를 보이게 할 수 있다.
값은 0에서 1사이의 double
값으로 설정하며 기본 값은 1.0으로 설정되어 있다. 1에 가까운 값이 될 수록 현제 페이지가 화면의 전체를 차지하게 된다.
아래와 같이 테스트 코드를 작성해 보았다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// root Widget
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: '9일차 과제'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title; //앱 제목
Widget build(BuildContext context) {
final pageController = PageController(viewportFraction: 0.5);
return Scaffold(
appBar: AppBar(
title: Text(title),
centerTitle: true,
),
body: PageView(
controller: pageController,
children: [
Container(
color: Colors.indigo,
alignment: Alignment.center,
child: Text(
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
'1페이지',
),
),
Container(
color: Colors.black,
alignment: Alignment.center,
child: Text(
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
'2페이지',
),
),
Container(
color: Colors.purple,
alignment: Alignment.center,
child: Text(
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
'3페이지',
),
),
],
),
);
}
}
MyHomePage
위젯에서 build
함수 내에 pageController
를 생성하고, viewportFraction
의 값을 0.5로 설정했다.
본문은 PageView
를 만들고 앞에서 만든 컨트롤러를 연결해 주었다. 다음으로 3개의 페이지를 구성했는데 Container
로 배경 색을 주어 페이지 구분을 쉽게 할 수 있도록 했다.
프로그램을 실행시키면 아래 화면과 같이 양 쪽 페이지가 좌우 여백에 보이는 것을 알 수 있다.
수치를 테스트 해보기 위해 viewportFraction
의 값을 0.8로 변경해 보았다. 아래 화면처럼 좌우 여백에 페이지의 크기가 작아지고 현재 페이지의 크기가 커진 것을 확인할 수 있다.
즉, viewportFraction
값은 커질 수록 현재 페이지의 좌우 크기가 커지고, 여백이 줄어든다. 따라서 기본 값인 1.0이 설정되면 현재 페이지만 사용자에게 보여지게 된다.
각 텍스트필드를 관리하려면 컨트롤러를 따로 생성해서 설정해야 한다. 하지만 컨트롤러를 하나만 생성해서 사용할 수는 없을까? 테스트 코드를 작성해서 확인해 보자
아래와 같이 테스트 코드를 작성해 보았다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// root Widget
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: '9일차 과제'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title; //앱 제목
Widget build(BuildContext context) {
final textController = TextEditingController(); //컨트롤러
return Scaffold(
appBar: AppBar(
title: Text(title),
centerTitle: true,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('두 텍스트 필드에 같은 컨트롤러 연결 테스트'),
SizedBox(height: 40),
//첫 번째 텍스트필드
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: textController,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 3),
),
),
),
),
//두 번째 텍스트필드
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: textController,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 3),
),
),
),
),
],
));
}
}
MyHomePage
위젯에서 build
함수 내에 TextEditingController()
를 생성했다. 본문에서는 테스트를 하기 위해 Column
으로 TextField
위젯을 두 개 생성하고 앞에서 생성한 하나의 컨트롤러를 똑같이 연결해 주었다. 텍스트 필드의 외곽선 등의 UI는 임의로 알아보기 쉽도록 구성해 보았다.
앱을 실행키고 텍스트를 입력하거나 지웠더니 아래와 같은 결과를 얻을 수 있었다.
화면을 보면 위쪽 텍스트 필드를 사용해 텍스트를 입력하거나 지웠을 때 아래쪽 텍스트 필드에도 똑같이 적용되는 것을 확인할 수 있다. 또한 아래쪽 텍스트 필드를 사용해도 마찬가지로 위쪽 텍스트 필드에도 적용된다.
이유는 제목에서도 알 수 있듯이 같은 컨트롤러를 사용했기 때문이다. 컨트롤러는 해당 위젯을 관리하는 것으로 한 쪽의 텍스트 필드를 사용했을 때 컨트롤러의 text
등의 내용이 변경되기 때문에 다른 쪽에도 적용되는 것이다.
따라서 각자 텍스트 필드를 관리하려면 다른 컨트롤러를 생성하여 각각 연결해 주어야한다. (테스트 코드처럼 두 텍스트 필드를 같이 사용하는 경우에는 하나의 컨트롤러로 연결... 근데 이런 경우가 있나..?)
3주차의 첫 번째 날이 시작됐다. 벌써 9일차에 들어갔다. 오늘은 컨트롤러와 네비게이터의 기본적인 내용을 학습하고, 이를 사용해서 여러 화면들을 구성해봤는데 내용이 어렵지 않고, 과제도 생각보다 막히는 부분은 없어서 지금까지 했던 과제 중에서 제일 빨리 끝난거 같다. 오늘은 후기도 짧게!!