기본 위젯 2 (입력,다이얼로그,이벤트,에니메이션,쿠퍼티노)
Updated:
1.입력용 위젯
1-1.TextField
- 글자를 입력받는 위젯입니다. InputDecoration 클래스와 함께 사용하면 힌트 메시지나 외곽선 등의 꾸밈 효과를 간단히 추가 할 수 있습니다.
-decoration
프로퍼티를 활영하면 다양한 효과를 줄 수 있습니다. 여기서는 InputDecoration
클래스의 labelText
프로퍼티를 활용하여 힌트를 나타 낼 수 있습니다.
InputDecoration
클래스의 border 프로퍼티에OutlineInputBorder
클래스의 인스턴스를 지정하면 외곽선과 힌트를 표현하는 material UI 가 나타 납니다.
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
children: const <Widget>[
TextField(),
SizedBox(
height: 40,
),
TextField(
decoration: InputDecoration(
labelText: 'ID를 입력하세요', // 힌트
),
),
SizedBox(
height: 40,
),
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password 도 입력하세요',
)),
],
),
),
)
1-2.CheckBox / Switch
-
설정 화면 등에 많이 사용되는 체크박스, 라디오 버튼, 스위치를 표현하는 위젯입니다.
-
Checkbox 와 Switch 는 모양만 다를 뿐 사용방법은 동일합니다.
-
상태를 나타낼 boolean type 의 변수가 필요하며, 이 변수를 value 프로퍼티에 설정합니다.
onChanged
이벤트는 체크값이 변할 때마다 발생하는데 변경된 값이 boolean value 인수로 넘어 오면서 setState() 함수를 통해 value 프로퍼티에 지정한 변수값을 변경해서 UI 를 다시 그립니다. -
상태를 나타내는 변수가 사용됨으로 StatefulWidget 이여야 합니다.
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Switch(
value: _isChecked,
onChanged: (value) {
setState(() {
_isChecked = value;
});
})
],
),
),
),
1-3.Radio / RadioListTile
-
선택 그룹 중 하나를 선택 할 때 사용하는 위젯입니다. 어디까지를 터치 영역으로 볼것이냐에 따라서
Radio
를 사용하거나RadioListTitle
을 사용합니다 -
Radio
는 그룹내에서 하나만 선택할 때 사용합니다. 그룹이 되는 항목을enum
으로 정의 한 후,groupValue
프로퍼티에enum
으로 정의 된 변수를 지정하고onChanged
이벤트에서 변경된 값을 반양합니다. -
ListTile
대신에RadioListTitle
을 사용하면 가로 전체가 터치 영역이 됩니다.
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ListTile(
title: Text('남자'),
leading: Radio(
value: Gender.MAN,
groupValue: _gender,
onChanged: (value) {
setState(() {
_gender = value;
});
},
),
),
ListTile(
title: Text('여자'),
leading: Radio(
value: Gender.WOMEN,
groupValue: _gender,
onChanged: (value) {
setState(() {
_gender = value;
});
},
),
),
SizedBox(
height: 40,
),
RadioListTile(
title: Text('남자'),
value: Gender.MAN,
groupValue: _gender,
onChanged: (value) {
setState(() {
_gender = value;
});
},
),
RadioListTile(
title: Text('여자'),
value: Gender.WOMEN,
groupValue: _gender,
onChanged: (value) {
setState(() {
_gender = value;
});
},
),
],
),
),
),
1-4.DropDownButton
-
여러 아이템 중 하나를 고를 수 있는 콤보박스 형태의 위젯입니다.
-
value
프로퍼티에 표시할 값을 지정합니다.items
프로퍼티에는 표시할 항목을DropdownMenuItem
클래스의 인스턴스들을 담은 리스트로 지정해야 합니다. -
map()
함수를 사용해서 _valueList 리스트 문자열 3개를DropdownMenuItem
인스턴스 3개로 반환합니다 -
toList()
함수를 사용하여 다시 리스트로 변환시켜 items 프로퍼티에 리스트를 저장합 -
주의할점은
setState(){}
에서 value 값이 type error 가 발생됩니다.String?
_selectedValue 에서 null 값도 포함을 시키고DropdownButton<String>
처럼 type 지정을 해줘야 합니다 -
type error 해결 다른 방법은
setState(){}
의value
를value as String
으로 직접적으로 type 을 지정할 수 있습니다.
class _MyHomePageState extends State<MyHomePage> {
// State 클래스 필드에 작성
final _valueList = ['첫번째', '두번쩨', '세번째'];
String? _selectValue = '첫번째';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('DropDownButton'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: DropdownButton<String>(
value: _selectValue,
items: _valueList
.map((value) => DropdownMenuItem(
value: value,
child: Text(value),
))
.toList(),
onChanged: (value) {
setState(() {
_selectValue = value;
});
}),
),
),
2.다이얼로그
다이얼로그는 사용자의 확인을 요구하거나 메시지를 표시하는 용도로 자주 사용합니다.
2-1.AlertDialog
-
meterial UI 유저 확인용 다이얼 로그 입니다
-
AlertDialog
표시 하려면showDialog()
함수의 builder 프로퍼티에AlertDialog
클래스의 인스턴스를 반환하는 함수를 작성하면 됩니다.showDialog()
함수의barrierDismissible
프로퍼티는 다이얼로그 바깥 부분의 탭을 해도 닫히게 할 것인지 정합니다.
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AlertDialog'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
_neverSatisfied(context);
},
child: const Text('Alert Dialog'),
),
),
);
}
}
Future<void> _neverSatisfied(BuildContext context) async {
return showDialog<void>(
context: context,
// 사용자가 다이얼로그 바깥을 터치하면 닫히지 않음
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('제목'),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text('Alert Dialog 입니다'),
Text('Ok 를 눌러 닫습니다'),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('OK'),
onPressed: () {
// 다이얼로그 닫기
Navigator.of(context).pop();
},
),
TextButton(
onPressed: () {
// 다이얼로그 닫기
Navigator.of(context).pop();
},
child: const Text('Cancel'))
],
);
});
}
2-2.DatePicker
-
날짜를 선택할 때 사용합니다.
-
showDatePicker()
함수를 호출하면 달력이 표시되며 날짜를 선택하고 ‘OK’ 버튼을 누르면 날짜를 변환합니다.
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DateTime? _selectedTime;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('DatePicker'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
children: <Widget>[
ElevatedButton(
onPressed: () {
Future<DateTime?> selectedDate = showDatePicker(
context: context, // context 인수전달
initialDate: DateTime.now(), // 초깃값
firstDate: DateTime(2021), // 시작일 2021년 1월 1일
lastDate: DateTime(2030), // 마지막일 2030년 1월 1일
builder: (BuildContext context, Widget? child) {
return Theme(
// 따로 정의 하지 않으면 default 값이 설정 됨
data: ThemeData.dark(), // 다크테마
child: child as Widget,
);
},
);
selectedDate.then((dateTime) {
setState(() {
_selectedTime = dateTime;
});
});
},
child: Text('Date Picker'),
),
Text('$_selectedTime')
],
),
),
),
);
}
}
2-3.TimePicker
-
showTimePicker()
함수를 호출해서 timePicker 를 표시할 수 있습니다. -
TimeOfDay
클래스에는 시간(hour), 분(minute) 정보가 들어 있습니다.
class _MyHomePageState extends State<MyHomePage> {
var _selectedTime;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TimePicker'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
children: <Widget>[
ElevatedButton(
onPressed: () {
Future<TimeOfDay?> selectedTime = showTimePicker(
context:
context, // context 는 Future 타입으로 TimeOfDay 타입의 값을 반환 합니다
initialTime: TimeOfDay.now(), // 프로퍼티에 초깃값을 지정합니다.
);
selectedTime.then((value) {
setState(() {
_selectedTime = '${value?.hour} : ${value?.minute}';
});
});
},
child: Text('Time Picker'),
),
Text('$_selectedTime'),
],
),
),
),
);
}
}
3.이벤트
GestureDetector / InWell
-
글자나 그림 같이 이벤트 프로퍼티가 없는 위젯에 이벤트를 적용할 때 사용하는 위젯입니다.
-
GestureDetector
와InWell
은 터치 이벤트를 발생시킵니다.onTap
프로퍼티를 가지고 있어서 child 프로퍼티에 어떠한 위젯이 와도 클릭 이벤트를 만들 수 잇습니다. Text, Image 등에 위젯에도 간단히 클릭 이벤트를 추가할 수 있습니다.
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GestureDetector / InWell'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () {
print('GestureDetoctor 클릭');
},
child: Text('GestureDetoctor 클릭'),
),
const SizedBox(
height: 40,
),
InkWell(
onTap: () {
print('InKWell 클릭!');
},
child: Text('Inkwell 클릭'),
)
],
),
),
),
);
}
}
4.에니메이션
4-1.Hero
-
Hero
위젯은 화면전환시 자연스럽게 연결되는 에니메이션을 지원합니다. 이전 화면으로 돌아 갈때고 자연스럽게 애니메이션이 동작합니다. -
사용방법은 에니메이션 효과의 대상이 되는 양쪽 화면의 위젯을
Hero
위젯으로 감싸고,tag
프로퍼티를 반드시 동일하게 지장해야 합니다.
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Hero'),
),
body: Center(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const HeroDetailPage()),
);
},
child: Hero(
tag: 'image', // 여기서 작성한 태그와 두 번째 페이지의 태그가 동일해야 함
child: Image.asset(
'assets/sample.png',
width: 100,
height: 100,
)),
),
),
);
}
}
class HeroDetailPage extends StatelessWidget {
const HeroDetailPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Hero Detail'),
),
body: Hero(
tag: 'image', // 여기서 작성한 태그와 첫 번째 페이지의 태그가 동일해야 함
child: Image.asset('assets/sample.png'),
),
);
}
}
4-2.AnimatedContainer
-
Hero 위젯이 화면 전환시 애니메이션 효과를 지원했다면 AnimatedContainer 위젯은 한 화면 내에서 setState() 함수 화면을 새로 그릴 때 변경된 프로퍼티에 의해 애니메이션이 되도록 해줍니다.
-
Container 위젯과 쓰임새는 비슷하지만 duration, curve 등의 애니메이션 관련 프로퍼티가 있습니다. duration 프로퍼티는 필수이며 에니메이션되는 데 걸리는 시간을 Duration 클래스를 사용해 정의할 수 있습니다. Curves 클래스에는 더 많은 미리 정의된 여러 애니메이션 효과가 있습니다.
import 'dart:math'; // Random 클래스 사용에 필요
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _size = 100.0; // 초기값 100.0 을 가지고 있음
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AnimatedContainer"),
),
body: Center(
child: GestureDetector(
onTap: () {
final random = Random(); // Random 클래스 사용 준비
setState(() {
// 클래할 때마다 100.0 ~ 299.0 사이의 실수를 랜덤하게 얻기
_size = random.nextInt(200).toDouble() +
100; // 사이즈를 클릭할 때마다 랜덤하게 100.0 ~ 299.0 사이의 값이 됩니다.
});
},
child: AnimatedContainer(
// 1초동안 fastOutSlowIn 효과를 random 한 width 와 height 으로 변경 시킵니다
duration: const Duration(seconds: 1),
width: _size,
height: _size,
child: Image.asset('assets/sample.png'),
curve: Curves.fastOutSlowIn,
),
),
),
);
}
}
랜덤 계산식 check !!
1.nextInt(200)
: 0~199 사이의 난수를 정수로 반환
2.toDouble()
: 정수를 실수로 변환 => 0.0 ~ 199.0
+100
: 100 더해서 범위를 변경 => 100.0 ~ 299.0
4-3.SilverAppBar /SilverFillRemaining
-
SliverAppBar
와SilverFillRemaining
은 화면 헤더를 동적으로 표현하는 위젯입니다. 헤더를 위로 스크롤하면 에더 부분이 작아지면서 해더 하단에 있던 정적인 내용만 보이는AppBar
형태로 에니메이션이 되는데 이것을Silver
효과라고 합니다 -
pinned
: 축소될때 상단에 AppBar 가 고정될지 사라질지 설정합니다. -
expandedHeight
: 확대 될때의 최대 높이를 정합니다 -
flexibleSpace
: 확대 / 축소되는 영역의 UI를 작성합니다.
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar 를 지정하지 않고 CustomScrollView 인스턴스를 지정함
body: CustomScrollView(
slivers: [
SliverAppBar(
// 헤더 영역
pinned: true, // 축소시 상단에 AppBar 가 고정되는지 설정
expandedHeight: 180.0, // 해더의 최대 높이
flexibleSpace: FlexibleSpaceBar(
// 늘어나는 영역의 UI 정의
title: const Text('Silver'),
background: Image.network(
'https://images.unsplash.com/photo-1611329532992-0b7ba27d85fb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80',
fit: BoxFit.cover,
),
),
),
const SliverFillRemaining(
// 내용 영역
child: Center(
child: Text('Center'),
),
)
],
),
);
}
}
4-4.SliverAppBar / SliverList
SliverFillRemaining
위젯은 하나의 정적인 화면을 구성할 때 사용하는 반면ListView
를 사용해서Silver
효과를 넣고자 하면ListView
대신SilverList
를 사용하면 됩니다.
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key}) : super(key: key);
// 0 부터 49 표시하는 ListTile을 담은 리스트
final _items = List.generate(
50,
(index) => ListTile(
title: Text('No. $index'),
));
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
expandedHeight: 180.0,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Silver'),
background: Image.network(
'https://images.unsplash.com/photo-1611329532992-0b7ba27d85fb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80',
fit: BoxFit.cover,
),
),
),
SliverList(
// delegate 프로퍼티에 SilverChildListDelegate 클래스를 인스턴스로 지정
// SilverChildListDelegate 클래스의 생성자에 표시할 위젯 리스트 (_items) 를 인수로 전달해야 합니다.
delegate: SliverChildListDelegate.fixed(_items),
),
],
),
);
}
}
5.쿠포티노 디자인
좀 더 아이폰 스러운 디자인을 적용하려면 쿠퍼티노 디자인을 사용합니다. flutter/cupertino.dart
패키지에는 다양한 쿠퍼티노 디자인용 UI 위젯이 준비됩니다.
5-1.쿠퍼티노 기본 UI
- 쿠퍼티노 디자인에서는
AppBar
대신CupertinoNavigationBar
를 사용하며,CupertinoSwitch
,CupertinoButton
class _MyHomePageState extends State<MyHomePage> {
var _isOn = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CupertinoNavigationBar(
// material AppBar에 대응
middle: Text('쿠퍼티노 디자인'), // material AppBar의 title 에 대응
),
body: Center(
child: Column(
children: [
CupertinoSwitch( // material 의 Switch
value: _isOn,
onChanged: (bool value) {
setState(() {
_isOn = value;
});
}),
CupertinoButton( // ElevatedButton 에 대응
child: Text('쿠퍼티노 AlertDialog'),
borderRadius: BorderRadius.circular(16.0),
onPressed: () {},
),
CupertinoButton(
child: Text('쿠퍼티노 Picker'),
onPressed: () {},
),
],
),
),
);
}
}
5-2.CupertinoAlertDialog
class _MyHomePageState extends State<MyHomePage> {
var _isOn = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CupertinoNavigationBar(
// material AppBar에 대응
middle: Text('쿠퍼티노 디자인'), // material AppBar의 title 에 대응
),
body: Center(
child: Column(
children: [
CupertinoSwitch(
// material 의 Switch
value: _isOn,
onChanged: (bool value) {
setState(() {
_isOn = value;
});
}),
CupertinoButton(
// ElevatedButton 에 대응
child: const Text('쿠퍼티노 AlertDialog'),
borderRadius: BorderRadius.circular(16.0),
onPressed: () {
_showCupertinoDialog();
},
),
CupertinoButton(
child: const Text('쿠퍼티노 Picker'),
onPressed: () {},
),
],
),
),
);
}
_showCupertinoDialog() {
showDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: const Text('제목'),
content: const Text('내용'),
actions: [
const CupertinoDialogAction(
child: Text('Cancel'),
),
CupertinoDialogAction(
child: const Text('OK'),
onPressed: () {
Navigator.of(context).pop(); // 다이얼로그 닫기
},
)
],
),
);
}
}
5-3.CupertinoPicker
- iOS 에서 자주 사용되는 피커 입니다. 위 아래로 스크롤하고 피커 바깥을 클릭하면 선택한 값이 적용됩니다.
class _MyHomePageState extends State<MyHomePage> {
var _isOn = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CupertinoNavigationBar(
// material AppBar에 대응
middle: Text('쿠퍼티노 디자인'), // material AppBar의 title 에 대응
),
body: Center(
child: Column(
children: [
CupertinoSwitch(
// material 의 Switch
value: _isOn,
onChanged: (bool value) {
setState(() {
_isOn = value;
});
}),
CupertinoButton(
// ElevatedButton 에 대응
child: const Text('쿠퍼티노 AlertDialog'),
borderRadius: BorderRadius.circular(16.0),
onPressed: () {
_showCupertinoDialog();
},
),
CupertinoButton(
child: const Text('쿠퍼티노 Picker'),
onPressed: () {
_showCupertinoPicker();
},
),
],
),
),
);
}
_showCupertinoDialog() {
showDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: const Text('제목'),
content: const Text('내용'),
actions: [
const CupertinoDialogAction(
child: Text('Cancel'),
),
CupertinoDialogAction(
child: const Text('OK'),
onPressed: () {
Navigator.of(context).pop(); // 다이얼로그 닫기
},
)
],
),
);
}
// Future 사용을 위해 async 사용
_showCupertinoPicker() async {
// 0부터 9까지의 숫자 리스트 생성
final _items = List.generate(10, (index) => index);
var result = _items[0]; // 기본값 0
// showCupertinoModalPopup() 함수는 Future 타입을 반환하기 때문에 await 피커가 닫힐때까지 대기한 후, result 변수의 값을 출력합니다.
await showCupertinoModalPopup(
context: context,
builder: (context) => Container(
height: 200.0,
child: CupertinoPicker(
children: _items.map((e) => Text('No. $e')).toList(),
itemExtent: 50.0,
onSelectedItemChanged: (int value) {
result = _items[value];
},
),
),
);
}
}
🔶 🔷 📌 🔑
Reference
-
Flutter official site widget docs - https://flutter.dev/docs/development/ui/widgets-intro
-
플러터 생존코딩 - https://book.jacobko.info/#/book/1162244372
Leave a comment