| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- java 출력
- printf
- npm
- abap
- scss
- UI/UX
- 배포
- unity
- react
- firebase
- LLM
- java
- 앱심사
- JS
- riverpod
- 자바 포맷 출력
- ListView
- JQ
- Flutter
- 단축키
- lifecycle
- java 콘솔 출력 차이
- develop
- 자바 출력 방식
- println
- Clean Architecture
- 자바스크립트
- DART
- nodejs
- 엡
- Today
- Total
guricode
Stateless,Stateful/ view 위젯/레이아웃 본문
1. StatelessWidget vs StatefulWidget
| 단계 | StatelessWidget | StatefulWidget |
|---|---|---|
| 생성 | - | 생성자 → createState() |
| 초기화 | - | initState() |
| 의존성 변경 | - | didChangeDependencies() |
| UI 그리기 | build() |
build() |
| 위젯 재구성 | 부모 위젯이 바뀔 때마다 | didUpdateWidget() |
| 상태 변경 반영 | - | setState() → build() 재호출 |
| 임시 제거 | - | deactivate() |
| 완전 제거 | - | dispose() |
1-1 .StatelessWidget 특징
- 단 하나의 메서드만 실행됨:
build() - 위젯 트리에 삽입되거나, 부모 위젯이 변경될 때 호출됨
- UI만 반환하며, 초기화나 정리 코드 삽입 불가
class MyStateless extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('안녕하세요');
}
}
1-2. StatefulWidget 구조와 생명주기
두 부분으로 구성됨:
StatefulWidget클래스State클래스
▶ StatefulWidget 단계
생성자(Constructor):MyStateful({ Key? key }) : super(key: key);createState(): 실제 상태를 관리할 State 객체 생성
▶ State 단계
initState(): 최초 한 번만 호출됨 (예: 초기 API 호출)didChangeDependencies(): InheritedWidget 변경 시 호출build(): UI 렌더링setState(): 상태 변경 후 build 재호출didUpdateWidget(): 부모 위젯의 속성이 변경되었을 때 호출deactivate(): 위젯이 트리에서 임시 제거될 때dispose(): 위젯이 완전히 제거될 때 (예: 리소스 정리)
📌 예제 코드: 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('더하기'),
),
],
),
),
);
}
}
** Stateless Stateful 요약**
- StatelessWidget
- 상태 없음. UI만 담당.
- 메서드:
build()한 개만 존재 (UI 렌더링 전용).
- StatefulWidget
- 내부 상태(State)를 관리할 수 있음.
- 메서드 흐름:
- 생성자 →
createState() - 초기화 →
initState() - 상태 변경 시 →
setState()호출 후build()재호출 - 의존성 변경 →
didChangeDependencies() - 위젯 갱신 →
didUpdateWidget() - 제거 시 →
dispose()
- 생성자 →
- 동적 UI 변경에 적합.
이해가 좀 어려운 부분이 있지만 직접 사용해보면서 익혀야 할 듯함..
2.View 위젯
2-1.page view

예제 코드는 아래 더보기 클릭
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp()); // 앱 실행, MyApp을 루트 위젯으로 사용
}
// 최상위 위젯: StatelessWidget
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false, // debug 배너 숨기기
home: Scaffold(
body: SampleWidget(), // SampleWidget을 화면에 띄움
),
);
}
}
// SampleWidget: 상태를 가지는 페이지 뷰 예제
class SampleWidget extends StatefulWidget {
@override
State createState() => _SampleWidgetState();
}
// 실제 상태와 로직을 관리하는 State 클래스
class _SampleWidgetState extends State {
// PageView 제어용 컨트롤러
final _controller = PageController();
@override
void initState() {
super.initState();
// PageController에 리스너 추가
_controller.addListener(() {
// 현재 스크롤 위치가 마지막 페이지일 때
if (_controller.position.maxScrollExtent == _controller.offset) {
// 다이얼로그로 알림
showDialog(
context: context,
builder: (context) =>
const CupertinoAlertDialog(content: Text('마지막에 도달했습니다.')),
);
}
});
}
@override
void dispose() {
_controller.dispose(); // 컨트롤러 해제
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
// 노치나 상태바 영역을 피해 배치
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 상단 버튼: 2페이지로 즉시 이동
Padding(
padding: const EdgeInsets.all(15.0),
child: ElevatedButton(
onPressed: () {
_controller.jumpToPage(1); // 인덱스 1 페이지로 점프
},
child: const Text('2페이지로 가기'),
),
),
// 남은 영역을 PageView가 채움
Expanded(
child: PageView(
controller: _controller, // 위에서 만든 컨트롤러 연결
scrollDirection: Axis.vertical, // 수직 스와이프 모드
// 페이지 전환 시마다 호출
onPageChanged: (int index) {
showDialog(
context: context,
builder: (context) =>
CupertinoAlertDialog(content: Text('$index 페이지 활성화')),
);
},
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),
),
),
),
],
),
),
],
),
),
);
}
}
코드를 보면서 어떤 기능을 하는지 생각 하자
pageView의 자주쓰는 옵션
- children: 슬라이드할 위젯 목록을 전달(이미지, 레이아웃 등 자유롭게 사용)
- scrollDirection: 스와이프 방향 설정(기본 Axis.horizontal, 세로는 Axis.vertical)
- controller: PageController 연결로 코드 내 페이지 이동(jumpToPage 등)·위치 감지(addListener) 가능
- pageSnapping: 스와이프 후 페이지 단위 고정 여부(기본 true이고, false면 이동한 만큼만 스크롤)
- onPageChanged: 페이지 변경 시 호출되는 콜백, 현재 페이지 인덱스를 받아 인디케이터 제어나 이벤트 트리거에 활용
2-2.List view
이해가 안간다면 한번씩 사용해보자!
자주 사용하는 옵션
- scrollDirection (`Axis.vertical` 기본, `horizontal` 가로 스크롤)
- reverse (`false` 기본, `true`면 순서 역전 → 채팅 앱에 유용)
- controller (`ScrollController`로 위치 제어·감시: `jumpTo()`, `addListener()`)
- physics (`BouncingScrollPhysics`, `ClampingScrollPhysics` 등으로 스크롤 동작 커스터마이징)
- padding (리스트 내부 여백 설정: `EdgeInsets`)
- cacheExtent (미리 렌더링 거리 지정, 성능 최적화)
2-3.GridView
자주 사용하는 옵션
- padding
그리드 뷰 내부 여백 설정 (셀 가장자리와 컨테이너 사이의 간격). - controller
ScrollController연결로- 특정 위치로 스크롤 이동 (
jumpTo,animateTo) - 현재 스크롤 위치 실시간 감지 (
addListener) - 무한 스크롤링이나 페이지 로드 트리거에 사용
- 특정 위치로 스크롤 이동 (
- reverse
스크롤 순서 반전 (false가 기본).true로 설정 시 마지막 아이템이 가장 위(왼쪽)에 배치됨.
- scrollDirection
스크롤 축 설정 (Axis.horizontal,Axis.vertical).- 가로 스크롤과 세로 스크롤 중 선택 가능.
- mainAxisSpacing & crossAxisSpacing
셀 간 수평/수직 간격을 설정. - gridDelegate 그리드 레이아웃 정의,그리드뷰 같은 경우에는 이 옵션을 꼭 넣어줘야함
SliverGridDelegateWithFixedCrossAxisCountcrossAxisCount: 고정 열 수
SliverGridDelegateWithMaxCrossAxisExtentmaxCrossAxisExtent: 최대 타일 너비, 가용 너비에 따라 열 수 자동 계산
2-4.Tabbar
탭바 위젯은 우리가 흔히 쓰는 UI이다. 상단이나 하단에 메뉴를 넣고 해당 메뉴로 이동하고 싶을때 주로 사용한다.
이때 메뉴가 들어가는 상단이나 하단은 tabbar위젯을 사용하게 되고 그 컨텐츠는 tabbar view를 사용하게 된다.
예제
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 {
//애니메이션 사용 규칙 , vsync와 같이사용함
late TabController _tabController;
//다른 스크롤 컨트롤과 다르게 controller을 생성할 때에 초기값을 설정해 줘야 합니다.
@override
void initState() {
super.initState();
_tabController = TabController(
length: 3,
vsync:
this, //렌더링을 장치 디스플레이의 수직 동기화와 동기화할지 여부를 결정, this를 넣어주면 됩니다. 또한 이를 this 클래스에 매칭하기 위해서는 TickerProviderStateMixin라는 클래스를 with라는 키워드로 포함시켜야 합니다.
);
}
@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 페이지 ')),
),
],
),
),
],
),
);
}
}
3. 레이아웃위젯
3-1.Container
플러터에서 가장 많이 쓰는 레이아웃 위젯
예제를 직접 써보며 확인하자.
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: 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)),
),
),
),
),
);
}
}
3-2. SizedBox
위젯과 위젯사이에 간격을 주고 싶을때 사용
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( //Column도 사용 가능
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
Row
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, //start , end, spaceBetween,spaceEvenly, spaceAround
crossAxisAlignment: CrossAxisAlignment.stretch,//start. end
children: List.generate(
5,
(index) => Container(
width: 40,
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
),
),
),
);
}
}
Column
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, //end, spaceBetween, spaceAround, spaceEvenly, center
crossAxisAlignment: CrossAxisAlignment
.start, //end, spaceBetween, spaceAround, spaceEvenly , center
children: List.generate(
5,
(index) => Container(
width: 40,
height: 40,
color: Colors.red,
margin: const EdgeInsets.all(5),
),
),
),
),
),
);
}
}
3-2.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),
),
),
],
),
),
),
);
}
}
'앱 > Flutter&Dart' 카테고리의 다른 글
| 상태관리의 정의와 setState의 한계 (1) | 2025.06.19 |
|---|---|
| Flutter의 기능성 위젯 (1) | 2025.06.18 |
| Stack & Positioned (0) | 2025.06.17 |
| Dart 공부 정리 - 타입 / 함수 파라미터 / 동기 비동기 (1) | 2025.06.16 |
| [Flutter/Android] Gradle 빌드 오류: Failed to create Jar file (jars-9.lock) 해결 방법 (0) | 2025.06.16 |