>
## StatelessWidget & StatefulWidget
```dart hl:21-28,15 title:"StatelessWidget"
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: FirstMyWidget(),
),
);
}
}
class FirstMyWidget extends StatelessWidget{
const FirstMyWidget({super.key});
@override
Widget build(BuildContext context) {
return Text('나의 첫번째 위젯입니다.');
}
}
```
- 변경 불가능한 위젯이며, 단순하고 예측가능하여 코드의 유지관리와 테스트/디버그에 유리함.
- StatelessWidget을 생성하려면 만들고자 하는 클래스를 만들고(위의 경우 FirstMyWidget 임), StatelessWidget을 상속받아 build 함수를 재정의 하면 된다.(라인24~26)

- 플러터에서 StatelessWidget의 경우 상태를 갖지 않는 위젯이다. 따라서 라이프사이클이 간단하다.
- 생성(constructor)과 동시에 Build함수가 호출이되고, 끝나게 된다.
```dart hl:12,15,21-55 point1:21,26 title:"StatefulWidget"
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: MyWidget(),
),
);
}
}
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('StatefulWidget 예제'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Counting : $_counter'),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('더하기'),
),
],
),
),
);
}
}
```
- StatefulWidget은 앱 작동 중에 모양과 동작을 변경할 수 있는 위젯으로, 동적인 바로 반영이 되는 UI를 구성할 수 있고, 애니메이션처리나 네트워크요청, 복잡한 데이터 처리와 같이 상태 업데이트 처리를 위한 로직을 만들 때 유연성을 제공함.
- StatefulWidget의 경우 2가지 클래스가 만들어진다.
1. StatefulWidget을 상속 받고 만들어진 MyWidget 클래스.(라인 21)
2. MyWidget의 상태(라인26, state)를 가진 (언더바)MyWidgetState 클래스. ► 여기서 상태가 정의되어지고, 상태관리가 되게 된다.

- StatefulWidget은 생성(constructor) 후 여러 단계를 거쳐 화면을 그리게 된다(build 함수)
- createState : 생성(constructor) 됨과 동시에 다음번 이벤트가 발생하는 것이 상태 생성(createState)이다. 상태 클래스는 바로 이 createState를 통해 생성된다. 즉 State 객체를 생성한다.(라인23)
- initState : 상태 객체가 생성된 다음에 바로 호출된 것으로, 최초 한 번만 호출된다. 상태를 초기화하는 데 사용된다. 변수/애니메이션 초기화 등에 상태된다. 반드시 super.initState() 를 호출해야 한다.
- didChangeDependencies : initState처럼 생성된 다음 한 번 호출되는 객체다. 또는 State 객체의 종속성이 변결될 때 호출된다.
- build(BuildContext context) : 화면에 위젯을 그리는 역할을 함. State가 변경될 때 다시 한 번 호출되고, 부모로부터 새로운 값을 받을 때도 호출되게 된다.
- setState : 상태를 변경하고 화면을 다시 그려야할 때 호출된다. 라인30~32처럼 1개씩 숫자를 카운트 된 것을 다시 화면에 그릴 때 호출될 수 있다.
- didUpdateWidget : 부모로부터 새로운 값을 받을 때는 didUpdateWidget을 사용한다. 이전 상태와 현재 업데이트 받은 상태를 비교해서, build를 할지 말지 결정할 수 있다.
## 뷰위젯 - PageView
```dart title:"PageView - 스와이프, 가로스크롤" hl:15 point1:17-25
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: PageView(
children: [
Container(
color: Colors.red,
child: const Center(
child: Text(
"1",
style: TextStyle(fontSize: 50, color: Colors.white),
),
),
),
Container(
color: Colors.blue,
child: const Center(
child: Text(
"2",
style: TextStyle(fontSize: 50, color: Colors.white),
),
),
),
Container(
color: Colors.yellow,
child: const Center(
child: Text(
"3",
style: TextStyle(fontSize: 50, color: Colors.white),
),
),
),
],
)),
);
}
}
```
```dart title:"PageView - 세로 스크롤(가로스크롤에서 1줄만 추가하면됨. 스크롤디렉션)" hl:16
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: PageView(
scrollDirection: Axis.vertical,
children: [
Container(
color: Colors.red,
child: const Center(
child: Text(
"1",
style: TextStyle(fontSize: 50, color: Colors.white),
),
),
),
Container(
color: Colors.blue,
child: const Center(
child: Text(
"2",
style: TextStyle(fontSize: 50, color: Colors.white),
),
),
),
Container(
color: Colors.yellow,
child: const Center(
child: Text(
"3",
style: TextStyle(fontSize: 50, color: Colors.white),
),
),
),
],
)),
);
}
}
```
```dart title:"PageView - Controller(페이지이동, 얼럿창, 페이지스내핑, 온페이지체인지 등)" hl:31,51-56,68 point1:69,60-67
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(body: SampleWidget()));
}
}
class SampleWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => _SampleWidgetState();
}
class _SampleWidgetState extends State<SampleWidget> {
final _controller = PageController();
@override
void initState() {
super.initState();
_controller.addListener(() {
if (_controller.position.maxScrollExtent == _controller.offset) {
showDialog(
context: context,
builder: (context) => const CupertinoAlertDialog(
content: Text('마지막에 도달했습니다.'),
),
);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.all(15.0),
child: ElevatedButton(
onPressed: () {
_controller.jumpToPage(1);
},
child: Text('2페이지로 가기'),
),
),
Expanded(
child: PageView(
onPageChanged: (int index) {
showDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
content: Text('$index 페이지 활성화'),
),
);
},
pageSnapping: false,
scrollDirection: Axis.vertical,
controller: _controller,
children: [
Container(
color: Colors.red,
child: const Center(
child: Text(
"1",
style: TextStyle(fontSize: 50, color: Colors.white),
),
),
),
Container(
color: Colors.blue,
child: const Center(
child: Text(
"2",
style: TextStyle(fontSize: 50, color: Colors.white),
),
),
),
Container(
color: Colors.yellow,
child: const Center(
child: Text(
"3",
style: TextStyle(fontSize: 50, color: Colors.white),
),
),
),
],
),
),
],
),
));
}
}
```
- 라인31 : 컨트롤러의 포지션(현재 위치)의 맥스 스크롤(최대 스크롤)과 현재 위치의 스크롤(offset)이 일치한다면, showDialog해서 메세지를 띄워라.
- 라인51-56 : 엘리베이터버튼이 눌렸을 때(onPressed), 2페이지로 이동됨.
- 라인60-67 : 해당 페이지에 도달했을 때 showDialog로 특정 텍스트 띄우기.
- 라인68 : 해당 줄을 [삭제]or[true] 값으로 변경하면 페이지가 딱딱 맞춰지고, [false]값으로 넣으면, 화면이 멈춘 지점에서 멈춰진다. 즉, true 값이면 틱톡처럼 다음 아이템으로 넘어가고, false값이면 블로그처럼 내리다 멈춰진다.
- 라인 69 : 컨트롤러를 사용해 화면 움직임을 조정할 때 필수 코드임. 없으면 작동 안됨.(이벤트 처리가 아예 처리가 안됨.)
## 뷰위젯 - ListView
```dart hl:16 point1:17 title:"ListView - scrollDirection, reverse"
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: ListView(
reverse: true,
scrollDirection: Axis.horizontal,
children: List.generate(
10,
(index) => Container(
width: 100,
height: 100,
margin: const EdgeInsets.all(5),
color: Colors.red.withAlpha((index + 1) * 25),
),
),
),
),
);
}
}
```
- 라인16 : 기본 값은 false임. 아이템들의 방향을 반대로 바꾸려면 false로 노출.
- 라인17 : axis(축)이 horizontal(가로)이다. 즉, 가로 방향으로 스크롤 하도록 만들겠다는 의미. 해당 라인을 없애면 기본값(default)은 vertical(세로)이다.
```dart title:"ListView - controller, physics" point4:40,53-54 hl:48
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SampleWidget(),
),
);
}
}
class SampleWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => _SampleWidgetState();
}
class _SampleWidgetState extends State<SampleWidget> {
final _controller = ScrollController(); // 1번
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
SizedBox(
height: 50,
child: ElevatedButton(
onPressed: () {
_controller.jumpTo(330); // 3번
},
child: const Text('3번영역으로 이동'),
),
),
Expanded(
child: ListView(
controller: _controller, // 2번
physics: const ClampingScrollPhysics(), //다른 설정의 클래스를 넣어주시면 됩니다.
children: List.generate(
10,
(index) => Container(
width: 100,
height: 100,
margin: const EdgeInsets.all(5),
color: Colors.red.withAlpha((index + 1) * 25),
child: Center(child: Text(index.toString())),
),
),
),
),
],
),
),
);
}
}
```
- 라인40/53-54 : 화면 위치 이동시 ► pageview의 경우 몇번 페이지인지 인덱스를 확인해줬지만, listview의 경우 스크롤의 위치(position)을 확인해줘야한다. 이 경우 height가 100이고, margin이 위아래로 5씩이니까, 한개의 아이템당 110씩 더해야 위치가 맞춰질 것이다.
- 라인48 : physics는 화면의 끝에 도달했을 때, 스크롤의 끝에 도달했을 때 나타나는 어떤 현상으로, 살짝 바운스 하듯 화면이 움직인다거나 하는 것이 이에 해당된다.(BouncingScrollPhysics) 혹은 스크롤가능한 영역을 넘어 스크롤을 시도할 때 끝부분에서 잘림(clamp)효과가 발생하는 것도 이에 해당한다.(ClamScrollPhysics). NeverScrollableScrollPhysics은 스크롤링 자체가 안되도록 막는 기능이다.(예를 들면, 어떤 이벤트가 발생된 다음에야 스크롤 가능한 경우 사용)
- 번외로 라인47에(리스트뷰 안에) 여러 코드를 추가해 추가 기능을 줄 수 잇따.
- [padding: EdgeInsets.all(20),] 과 같은 코드를 추가해, 전체 아이템 리스트에 패딩을 줄 수 있다.
- 혹은 [cacheExtent : 1000,]은 1000 스크롤까지 캐싱시켜놓는 건데, 아마 1000px까지 기억해놓도록 한다는 의미이다.
## 뷰위젯 - GriedView
```dart title:"SliverGridDelegateWithFixedCrossAxisCount 예제" hl:16 point1:17-19
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 2,
crossAxisSpacing: 2,
),
children: List.generate(
100,
(index) => Center(
child: Container(
color: Colors.grey,
child: Center(child: Text(index.toString())),
),
),
),
),
),
);
}
}
```
- 그리드위젯의 필수 : gridDelegate
- 라인16 - SliverGridDelegateWithFixedCrossAxisCount
- 라인17 - crossAxisCount : 몇분할 할 것인지 여부
- 라인18 - mainAxisSpacing : 위/아래 그리드의 간격
- 라인19 - crossAxisSpacing : 옆 그리드의 간격
- 라인16 - SliverGridDelegateWithMaxCrossAxisExtent
- 라인17 - maxCrossAxisExtent : 유동적으로 분할을 나눌 때 사용. 하나의 그리드가 최대 000px을 넘어갈 때, 반응형으로 변경됨.
- 라인18 - mainAxisSpacing : 상동
- 라인19 - crossAxisSpacing : 상동
- 라인15~16 사이
- scrollDirection: Axis.horizontal ► 스크롤 방향 설정
- reverse: true ► 아이템들의 순서를 거꾸로 할건지 설정
```dart title:"controller, padding 예제" hl:11-17,33-43,60 point1:59
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(body: const SampleWidget()),
);
}
}
class SampleWidget extends StatefulWidget {
const SampleWidget({super.key});
@override
State<SampleWidget> createState() => _SampleWidgetState();
}
class _SampleWidgetState extends State<SampleWidget> {
final _controller = ScrollController();
@override
void initState() {
super.initState();
_controller.addListener(() {
// 1번
if (_controller.position.maxScrollExtent == _controller.offset) {
showDialog(
context: context,
builder: (context) => const CupertinoAlertDialog(
content: Text('마지막에 도달했습니다.'),
),
);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
ElevatedButton(
onPressed: () {
_controller.jumpTo(800);
},
child: const Text('28번째로 이동')),
Expanded(
child: GridView(
padding: EdgeInsets.all(30),
controller: _controller,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 5,
crossAxisSpacing: 5,
),
children: List.generate(
100,
(index) => Center(
child: Container(
color: Colors.grey,
child: Center(child: Text(index.toString())),
),
),
),
),
),
],
),
),
);
}
}
```
## 뷰위젯 - TapbarView
```dart title:"탭바" hl:35,51-53 point1:47-49 point2:56-74
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(body: const SampleWidget()),
);
}
}
class SampleWidget extends StatefulWidget {
const SampleWidget({super.key});
@override
State<SampleWidget> createState() => _SampleWidgetState();
}
class _SampleWidgetState extends State<SampleWidget>
with TickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(
length: 3,
vsync: this,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TabBar(
controller: _tabController,
labelColor: Colors.blue,
unselectedLabelColor: Colors.grey,
labelPadding: const EdgeInsets.symmetric(vertical: 20),
tabs: const [
Text('메뉴1'),
Text('메뉴2'),
Text('메뉴3'),
],
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
Container(
color: Colors.blue,
child: Center(child: Text('메뉴1 페이지 ')),
),
Container(
color: Colors.blue,
child: Center(child: Text('메뉴2 페이지 ')),
),
Container(
color: Colors.blue,
child: Center(child: Text('메뉴3 페이지 ')),
),
],
),
),
],
),
);
}
}
```
- 라인35 : 몇개의 탭이 구성될 것인지 지정
- 라인51-53 : 각 탭의 명칭 지정. 라인35의 length와 같아야함.
- 라인47-49 : 선택된 탭(라벨)컬러는 블루, 선택되지 않은 라벨(탭)의 컬러는 그레이, 각 라벨(탭)의 패딩은 위 아래로 20px씩 넣음.
## 레이아웃 위젯 - Container
```dart title:"컨테이너" hl:16-19 point1:20-38 point2:39-40
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromARGB(255, 255, 59, 98).withOpacity(0.7),
Color.fromARGB(255, 255, 59, 98)
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Color.fromARGB(255, 255, 59, 98).withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: Offset(0, 3), // changes position of shadow
),
],
),
width: 200,
height: 150,
child: Center(
child: Text(
'Container',
style: TextStyle(color: Colors.white),
)),
),
),
);
}
}
```
- 라인16-19 : 컨테이너 내부 간격 조절
- 라인39-40 : 컨테이너 너비(width), 높이(height) 조절
- 라인20-38 : 그라데이션, 그림자효과, 모서리둥근설정, 컬러 등 고급스러운 UI 구성
- colors : 컨테이너에 들어갈 컬러지정. begin 지점과 end 지점 지정 가능
- borderRadius : 모서리 둥근 설정
- boxShadow : 박스 그림자를 통해 입체적 느낌을 줌.
- spreadRadius, blurRadius : 번짐효과를 줌.
- 모든 위젯에는 child가 존재하여 자식 Widget을 지정할 수 있음.
## 레이아웃 위젯 - SizedBox
```dart title:"위아래 간격"
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
color: Colors.red,
width: 100,
height: 40,
),
const SizedBox(height: 10),
Container(
color: Colors.blue,
width: 100,
height: 40,
),
],
),
),
);
}
}
```
- 간격 조절할 때 SizedbBox를 사용함.
```dart title:"좌/우 간격"
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
color: Colors.red,
width: 100,
height: 40,
),
const SizedBox(width: 10),
Container(
color: Colors.blue,
width: 100,
height: 40,
),
],
),
),
);
}
}
```
## 레이아웃 위젯 - Row/Column
```dart title:"로우(가로)" hl:17 point1:18
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: List.generate(
5,
(index) => Container(
width: 40,
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
),
),
),
);
}
}
```
- 라인17(MainAxisAlignment) : start, center, end, spaceBetween(시작과 끝에 고정하고, 동일한 간격으로 나머지 아이템을 나눔), spaceEvenly(모든 아이템을 동일한 간격으로 나눔), spaceAround(각 아이템의 margin값을 기준으로 겹치지 않게 아이템을 배치함. 즉, 2~n-1 아이템의 간격은 1&n개보다 두껍고, 1번째의 왼쪽, n번째의 오른쪽보단 얇다.)
- 라인18(CrossAxisAlignment) : baseline, center, end, start, values, stretch
- 세로의 영역을 어떻게 가져갈 것인지에 대한 명령어
- stretch : 부모의 사이즈만큼 쭉 늘려서 가져갈 것이다.
- start : 포함된 영역의 상단 시작점에 배치함
- end : 포함된 영역의 하단 끝점에 배치함
```dart title:"컬럼(세로)" hl:17 point1:18
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: List.generate(
5,
(index) => Container(
width: 40,
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
),
),
),
);
}
}
```
## 레이아웃 위젯 - Expanded
```dart title:"Row에서 Expanded" hl:19-25
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: Container(
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
...List.generate(
4,
(index) => Container(
width: 40,
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
],
),
),
),
);
}
}
```
- 라인19-25 : 가로(로우) 정렬 된 아이템들을 배치하고, 남은 공간만큼 확장(expanded)하겠다는 의미.
```dart title:"Expanded 옵션의 flex 기능 사용"
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
flex: 1, // 1
child: Container(
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
Expanded(
flex: 3, // 2
child: Container(
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
Expanded(
flex: 2, // 1
child: Container(
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
],
),
),
),
);
}
}
```
- 모든 아이템이 expanded를 사용할 경우, flex를 통해 몇 등분의 영역을 가져갈지 정하는 것. 위 예제는 flex가 1/3/2 이기에 1/6, 3/6, 2/6 만큼 각각 영역을 나눠 가져가는 것임.
```dart title:"Column에서 Expanded 옵션"
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
flex: 1, // 1
child: Container(
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
Expanded(
flex: 3, // 2
child: Container(
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
Expanded(
flex: 2, // 1
child: Container(
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
],
),
),
),
);
}
}
```
## 레이아웃 위젯 - Stack
```dart title:"스택 위젯" hl:16 point1:18-24 point2:25-36
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
const CircleAvatar(
radius: 50,
child: Icon(
Icons.person,
size: 40,
),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
padding: const EdgeInsets.all(7),
decoration: const BoxDecoration(
shape: BoxShape.circle, color: Colors.white),
child: const Icon(
Icons.camera_enhance,
size: 24,
),
),
)
],
),
),
);
}
}
```
- 라인18-24 : 가장 하단에 쌓일 아이템
- 라인25-36 : 그 위에 쌓일 아이템
- 라인26-27 : 밑에 놓인 아이템을 기점으로 좌측(left), 우측(right), 위(top), 아래(bottom)을 지정하며, 해당 영역에서 몇px 떨어질 것인지 지정함. 단, 상하좌우 모두 0px로 둘 경우, 전체를 꽉 채워서 아이템이 들어감.
- 라인17 : 부모 위젯만큼 위젯을 확장 시킴. 기본값이 loose임. 확장할 때 외에는 쓸 일 없음
## 기능성 위젯 - Text, TextField, Switch, Slider, GestureDetector, Image 등
```dart title:"Text - Style" hl:18-27
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Text(
'텍스트의 스타일을 봐보자',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
fontStyle: FontStyle.italic,
letterSpacing: 2.0,
wordSpacing: 4.0,
decoration: TextDecoration.underline,
fontFamily: 'Roboto',
),
)),
),
);
}
}
```
- 라인20 : fontWeight - 폰트 두께
- 라인22 : fontStyle - 폰트 기울기 여부
- 라인23 : letterSpacing - 자간 간격
- 라인24 : wordSpacing - 단어 간격
- 라인25 : decoration - 밑줄, 취소선 등
```dart title:"TextField - 기본"
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextField(
decoration: InputDecoration(
labelText: '이름을 입력해주세요',
),
)),
),
);
}
}
```
```dart title:"TextField - 옵션 설정"
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('TextField Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: MyCustomForm(),
),
),
);
}
}
class MyCustomForm extends StatefulWidget {
@override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
final TextEditingController myController = TextEditingController();
@override
void initState(){
super.initState();
myController.addListener(() {
print(myController.text);
});
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
TextField(
maxLength: 20, //텍스프 필드 내 글자 수 제한
readOnly: false, //읽기전용인지 여부
textAlign: TextAlign.left, // 텍스트 정렬(좌/우)
decoration: InputDecoration(
labelText: 'Enter your name',
hintText: 'John Doe',
border: OutlineInputBorder(),
counterText: '', // This hides the counter below the TextField
),
controller: myController,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 텍스트 필드의 값 출력
print(myController.text);
},
child: Text('Submit'),
),
],
);
}
@override
void dispose() {
myController.dispose();
super.dispose();
}
}
```
```dart title:"Switch 위젯" hl:22-30
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var state = false;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Switch(
value: state,
onChanged: (bool newValue) {
// 토글 이벤트 처리
setState(() {
state = newValue;
});
},
),
),
),
);
}
}
```
```dart title:"slider위젯" hl:22-32
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var state = 50.0;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Slider(
value: state,
min: 0,
max: 100,
onChanged: (double newValue) {
// 슬라이더 변경값에 따른 이벤트 처리
setState(() {
state = newValue;
});
},
)),
),
);
}
}
```
```dart title:"GestureDetector - 제스쳐를 감지하는 위젯" hl:17-20 point1:21-23
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: GestureDetector(
onTap: () { //1회 클릭/탭 시
// 탭 이벤트 발생
print('클릭');
},
onDoubleTap: () { //2회 클릭/탭 시
print('더블클릭');
},
child: Container(
color: Colors.blue,
width: 100,
height: 100,
),
)),
),
);
}
}
```
```dart title:"Image"
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.blue,
body: Center(
child: Image.network(
'https://sudar-life.github.io/images/logo_white_mode.png',
width: 200,
height: 200,
),
),
),
);
}
}
```
[부트캠프] 1일차 (0) | 2025.03.03 |
---|---|
[부트캠프] 사업계획서 목차 (0) | 2025.02.21 |
[부트캠프] 관련 용어 정리 (0) | 2025.02.20 |
[부트캠프] 1주차 강의 (0) | 2025.02.18 |
[부트캠프] 창업을 하고 싶은 이유 및 10가지 질문(w. 스파르타코딩클럽) (0) | 2025.02.10 |