작년 플러터 스터디가 끝났고, 이어서 스터디를 더 하고 싶은 사람들끼리 모여서 올해 다시 새롭게 플러터 스터디를 시작했습니다.
올해 플러터 스터디의 첫 과제는
이렇게 색상 변경이 가능하도록 세개의 화면을 구현해야 합니다.
## APP NAME
- ColorManipulator
# 1. 정의
- 현재 페이지와 다른 페이지의 색상을 조작할 수 있다.
# 2. 목적
- 현재 페이지 뿐만 아니라 다른 페이지의 데이터까지 같이 관리하는 방법을 익힌다.
# 3. 관리항목
- 선택 옵션(라디오 버튼)
- 배경색
# 4. 기본 SPEC
- 페이지 버튼을 누르면 해당 페이지로 이동한다.
- 현재 페이지는 버튼이 비활성화 된다.
- 라디오 버튼을 구현한다.
- 버튼을 조작해 선택 옵션을 변경하면, 다른 페이지의 라디오 버튼 옵션과 배경색이 그에 따라 자동으로 변경된다.
# 5. 2차 버전
- A페이지 하위에 2개의 페이지가 더 추가된다.
# 6. 3차 버전
- A페이지 하위의 하위에 2개의 페이지, B페이지 하위에 1개의 페이지가 더 추가된다.
# 7. 사용 Widget 및 Package
- Provider(사용안해 가능)
# 8. MVC
- M : 색상데이터
- V : HOME, pageA, pageB
- C : ?
# 9. 화면 설계
<img src="HOME.png">
<img src="pageA.png">
<img src="pageB.png">
# 10. 화면 상세설계서
```dart
1. 앱을 열면 HOME 화면이 열린다.
2. 페이지 텍스트버튼
- 버튼을 누르면 각 페이지로 이동한다.
3. 페이지 텍스트버튼 옆 라디오버튼
- 기본으로 체크되어 있는 값은 red 이다.
- 선택 옵션을 변경하면, 다른 각 페이지의 라디오 버튼 옵션과 배경색이 현재 체크한 옵션에 따라 자동으로 변경된다.
- 예를 들어 page A의 라디오 버튼을 red에서 green으로 변경하면, HOME, page A, page B에서 page A 버튼 옆의 라디오 버튼도 green으로 변경된다. 그리고 page A 의 배경색만 초록색으로 변경된다.
4. 배경색
- page A, page B의 배경색의 기본색은 red이다.
이번엔 상태관리를 위해 provider를 사용해 보겠습니다.
provider를 오랜만에 사용해서, 코딩셰프님의 provider 영상을 보면서 provider의 개념을 다시 한번 보았습니다.
우선 colors라는 이름으로 플러터 프로젝트를 만들었습니다.
build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => Home(),
'/a': (BuildContext context) => PageA(),
'/b': (BuildContext context) => PageB()
},
);
}
Widget
네비게이션 구현을 위해 named navigator routes를 사용했습니다.
MaterialApp 안에서 routes를 정의합니다.
그럼 앞으로 Navigator.pushNamed(context, '/b');
이런 식으로 네비게이션을 사용할 수 있습니다.
enum ACharacter { red, green }
enum BCharacter { red, green }
class Home extends StatefulWidget {
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
ACharacter? _aCharacter = ACharacter.red;
BCharacter? _bCharacter = BCharacter.red;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('HOME'),
backgroundColor: Colors.black,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () {
Navigator.pushNamed(context, '/a');
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blueAccent),
),
child: const Text(
'page A',
style: TextStyle(color: Colors.white),
),
),
Radio<ACharacter>(
value: ACharacter.red,
groupValue: _aCharacter,
onChanged: (ACharacter? value) {
setState(() {
_aCharacter = value;
});
},
),
const Text('red'),
Radio<ACharacter>(
value: ACharacter.green,
groupValue: _aCharacter,
onChanged: (ACharacter? value) {
setState(() {
_aCharacter = value;
});
},
),
const Text('green'),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () {
Navigator.pushNamed(context, '/b');
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blueAccent),
),
child: const Text(
'page B',
style: TextStyle(color: Colors.white),
),
),
Radio<BCharacter>(
value: BCharacter.red,
groupValue: _bCharacter,
onChanged: (BCharacter? value) {
setState(() {
_bCharacter = value;
});
},
),
const Text('red'),
Radio<BCharacter>(
value: BCharacter.green,
groupValue: _bCharacter,
onChanged: (BCharacter? value) {
setState(() {
_bCharacter = value;
});
},
),
const Text('green'),
],
),
],
),
);
}
}
새롭게 알게된 게 있는데, AppBar에서 centerTitle: true
를 설정하면 title이 중앙정렬이 됩니다.
https://api.flutter.dev/flutter/material/Radio-class.html
플러터에서 제공하는 라디오 클래스를 사용했습니다.
버튼을 누르면 화면을 이동합니다.
TextButton(
onPressed: () {
Navigator.pushNamed(context, '/a');
},
...
page A, page B의 기본 UI 코드는 현재 화면의 버튼을 비활성화 하는 걸 제외하고는 HOME과 동일합니다.
TextButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.grey),
),
child: const Text(
'page A',
style: TextStyle(color: Colors.white),
),
),
현재 화면의 버튼은 onPressed를 비워서 눌러도 아무것도 실행되지 않게 했습니다.
그리고 backgroundColor: MaterialStateProperty.all(Colors.grey)
로 회색을 적용했습니다.
color_model.dart
import 'package:flutter/material.dart';
class ColorA with ChangeNotifier {
Color color;
ColorA({
required this.color,
});
void changeColor() {
if (color == Colors.red) {
color = Colors.green;
} else {
color = Colors.red;
}
notifyListeners();
}
}
class ColorB with ChangeNotifier {
Color color;
ColorB({
required this.color,
});
void changeColor() {
if (color == Colors.red) {
color = Colors.green;
} else {
color = Colors.red;
}
notifyListeners();
}
}
a, b 페이지 각각 색 데이터을 저장하도록 모델 클래스를 따로 만들었습니다.
dependencies:
provider: ^6.0.5`
build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => ColorA(color: Colors.red),
),
ChangeNotifierProvider(
create: (context) => ColorB(color: Colors.green),
),
],
child: MaterialApp(
...
Widget
이제 setState를 사용하지 않기 때문에 StatelessWidget으로 변경하겠습니다. (HOME, pageA, pageB 모두)
class Home extends StatelessWidget {
그리고 라디오 버튼에 Provider로 가져온 값을 적용합니다.
Radio<Color>(
value: Colors.red,
groupValue: Provider.of<ColorA>(context).color,
onChanged: (Color? value) {
Provider.of<ColorA>(context, listen: false).changeColor();
},
),
const Text('red'),
Radio<Color>(
value: Colors.green,
groupValue: Provider.of<ColorA>(context).color,
onChanged: (Color? value) {
Provider.of<ColorA>(context, listen: false).changeColor();
},
),
const Text('green'),
이제 HOME에서 pageA의 라디오 버튼을 green에 체크하고
-> pageA로 이동하면 pageA의 라디오 버튼 옵션이 자동으로 green으로 체크되어 있는걸 확인할 수 있습니다!! 🙌
이제 마지막으로 각 화면의 라디오버튼 옵션에 따라 각 배경색을 변경해 보겠습니다.
class PageA extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Provider.of<ColorA>(context).color,
class PageB extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Provider.of<ColorB>(context).color,
backgroundColor에 Provider로 가져온 값을 지정합니다.
이제 배경색도 자동으로 변경됩니다 🎉