#위젯생명주기 #PageView #Timer #SystemChrome #StatefulWidget
이미지 5개를 롤링해 보여주는 액자 앱 만들기
- 좌우 스와이프 기능
- 특정 주기마다 반복적인 함수 실행
9.1 사전 지식
9.1.1 위젯 생명주기
- StatelessWidget
빌드되면 생성자가 실행되고, build() 함수가 실행되어 build()가 반환한 위젯이 화면에 렌더링됨
모든 위젯은 Widget 클래스를 상속하고, Widget 클래스는 불변 특성을 갖는다. (한 번 생성하면 속성 변경 x) -> 한번 생성된 인스턴스의 build() 함수는 재실행되지 않음
- StatefulWidget
외부에서 위젯 생성자의 매개변수를 변경해주면 위젯이 새롭게 생성되고 build()가 실행되기까지는 Stateless와 동일
위젯 내부에서 자체적으로 build()를 재실행해야 할 때 StatefulWidget 사용.
Widget 클래스와 State 클래스로 구성.
1) 상태 변경이 없는 생명주기
위젯이 화면에 나타나며 생성되고 화면에서 사라지며 삭제되는 과정. 중간에 위젯 상태 변경 X
StatefulWidget 생성자 실행 ➡️ createState() 함수 실행, State 생성 ➡️ initState() 실행 ➡️ didChangeDependencies() 실행 ➡️ State의 상태가 dirty로 설정됨 ➡️ build() 실행, UI 반영 ➡️ 상태가 clean으로 변경 (화면 변화 없으면 이 상태 유지) ➡️ 위젯이 위젯트리에서 사라지면 deactivate() 실행 ➡️ dispose() 실행 |
* createState() : StatefulWidget에서 필수로 오버라이드해야 하는 함수. 위젯과 연동되는 State를 생성함
* initState() : State가 생성되는 순간 단 한 번 실행되고 다시 실행되지 않음
* didChangeDependencies() : BuildContext가 제공되고 State가 의존하는 값이 변경되면 재실행됨
* dirty 상태 : build()가 재실행되어야 하는 상태
* deactivate() : State가 일시적 or 영구적으로 삭제될 때 실행
* dispose() : 위젯이 영구적으로 삭제될 때 실행
2) StatefulWidget 생성자의 매개변수가 변경됐을 때 생명주기
StatefulWidget 생성자 실행 ➡️ State의 didUpdateWidget() 함수 실행 ➡️ State가 dirty 상태로 변경 ➡️ build() 실행 ➡️ Statte가 clean 상태로 변경 |
3) State 자체적으로 build()를 재실행할 때 생명주기
StatefulWidget에서 State 클래스는 setState()를 실행해서 build()를 자체적으로 재실행할 수 있음
setState() 실행 ➡️ State가 dirty 상태로 변경 ➡️ build() 실행 ➡️ clean 상태로 변경 |
9.1.2 Timer
특정 시간이 지난 후에 일회성 또는 지속적으로 함수 실행
Timer() : Timer의 기본 생성자. 대기 기간과 해당 기간이 끝난 후 실행할 콜백함수 입력(일회성)
Timer.periodic() : 주기와 콜백함수를 매개변수로 입력해 주기적으로 함수 실행
- 주기: Duration에 주기 설정. days, hours, minutes, seconds, ...
- 콜백함수: 매개변수에 현재 실행중인 Timer 객체 제공됨
9.4 구현하기
9.4.1 페이지뷰 구현하기
PageView : 여러 개의 위젯을 단독 페이지로 생성하고, 가로나 세로 스와이프로 페이지를 넘길 수 있게 해주는 위젯. children으로 리스트를 받아 위젯으로 매핑할 수 있다.
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
children: [1, 2, 3, 4, 5]
.map(
(number) => Image.asset('asset/img/image_$number.jpeg'),
)
.toList(),
),
);
}
}
이미지 이름이 image_1, image_2 ... 니까 map을 사용해 에셋 경로를 하나씩 넣어주고, toList()로 리스트로 만든다
여담인데 원래 연습예제들을 예제라는 폴더에 넣어두는데, 경로에 한국어 있으면 안드로이드 스튜디오가 안 돌아가더라... 폴더이름 바꿈
여러 비율의 화면에 대응할 수 있에 이미지 핏 조절
Image.asset에서 fit을 추가해준다.
body: PageView(
children: [1, 2, 3, 4, 5]
.map(
(number) => Image.asset(
'asset/img/image_$number.jpeg',
fit: BoxFit.cover,
),
)
.toList(),
),
* BoxFit 속성
contain 이미지가 잘리지 않는 선에서 최대한 크게 늘림. 이미지의 부모위젯이 이미지 크기와 다르다면 여백이 생길 수 있음
cover 부모 위젯 전체를 덮는 선에서 최소한 크기로 조절. 여백은 안 생기지만 이미지가 잘릴 수 있음
fill 부모위젯의 이미지 비율대로 이미지 크기 조절. 원래 비율 무시
fitHeight 비율 유지한 채로 부모위젯의 높이에 이미지 높이를 맞춤
fitWidth 비율 유지한 채로 부모위젯의 넓이에 이미지 넓이 맞춤
none 원본 이미지 크기와 비율 그대로 사용 (->부모의 밖으로 튀어나올수도)
scaleDown none설정에 이미지를 중앙 정렬하고 부모위젯이 이미지보다 작으면 이미지 크기를 줄여서 부모 위젯에 맞춤 (안 튀어나오게)
9.4.2 상태바 색상 변경하기
SystemChrome : 시스템 UI의 그래픽 설정을 변경하는 기능 제공. services.dart를 임포트해서 사용
* SystemChrome 함수
setSystemUIOverlayStyle() 시스템 UI 색상 변경
setEnabledSystemUIMode() 앱의 풀스크린 모드 지정 (핸드폰 상단 시간, 배터리 잔량 가림)
setPreferredOrientations() 앱 실행 방향 지정. 가로 / 가로 좌우반전 / 세로 / 세로 좌우반전
setSystemUIChangeCallback() 시스템 UI 변경시 콜백함수 실행
9.4.3 타이머 추가하기
Timer을 추가하려면 StatefulWidget으로 변경해야 함
(StatelessWidget 사용시 Timer을 build()에 등록해야 하는데, 그러면 위젯이 새로 생성될 때마다 매번 새로운 Timer가 생성되어 메모리 누수 발생. StatefulWidget에서는 initState()를 사용해 State 생성 시 딱 한번만 Timer 생성 가능)
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
void initState() {
super.initState();
Timer.periodic(
Duration(seconds: 3),
(timer) {
print('실행');
},
);
}
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
return Scaffold(
body: PageView(
children: [1, 2, 3, 4, 5]
.map(
(number) => Image.asset(
'asset/img/image_$number.jpeg',
fit: BoxFit.cover,
),
)
.toList(),
),
);
}
}
StatefulWidget으로 변경해주고 initState()를 등록. (모든 initState() 함수는 부모의 initState() 함수 실행 필요)
Timer.periodic()으로 3초마다 터미널에 '실행' 이 출력되도록 콜백함수 작성
initState()는 State가 생성될 때 딱 한 번만 실행되므로, 이미 State가 생성된 상태에서는 실행되지 않음. 즉 핫리로드에 반영이 안 되므로 리스타트 필요
PageView의 PageController을 사용해 조작
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final PageController pageController = PageController();
void initState() {
super.initState();
Timer.periodic(
Duration(seconds: 3),
(timer) {
int? nextPage = pageController.page?.toInt(); //현재 페이지 가져오기
if (nextPage == null) { //예외처리
return;
}
if (nextPage == 4) { //마지막 페이지일 경우 처음으로
nextPage = 0;
} else {
nextPage++;
}
pageController.animateToPage( //페이지 변경
nextPage,
duration: Duration(milliseconds: 500),
curve: Curves.ease,
);
},
);
}
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
return Scaffold(
body: PageView(
controller: pageController,
children: [1, 2, 3, 4, 5]
.map(
(number) => Image.asset(
'asset/img/image_$number.jpeg',
fit: BoxFit.cover,
),
)
.toList(),
),
);
}
}
pageController.page 게터를 사용해 PageView의 현재 페이지를 가져온다. 페이지가 변경 중인 경우 소수점으로 표현되는데, animateToPage() 에서 정수값을 넣어줘야 하므로 toInt()를 사용해 정수값으로 변환한다.
마지막 페이지일 경우 첫번째 페이지로 돌아가도록 설정한다.
animateToPage() 로 PageView의 현재 페이지를 변경한다.
duration으로 페이지 이동 시 소요될 시간을 지정한다.
curve로 페이지 변경 애니메이션의 작동방식을 정한다.
9.5 테스트하기
컴플릿
'[Must Have 코드팩토리의 플러터 프로그래밍]' 카테고리의 다른 글
11 디지털 주사위 (0) | 2024.07.01 |
---|---|
10 만난 지 며칠 U&I (1) | 2024.05.04 |
08 블로그 웹 앱 (1) | 2024.05.02 |
07 앱을 만들려면 알아야 하는 그 밖의 지식 (0) | 2024.04.30 |
06 기본 위젯 알아보기 (1) | 2024.04.30 |