플러터(Flutter)는 구글에서 만든 크로스 플랫폼 프레임워크다.
이번에는 PageView와 Timer, StatefulWidget, SystemChrome을 이용하여 전자 액자 APP을 만들어 본다.
StatelessWidget vs StatefulWidget
이전 웹앱 예제는 StatelessWidget을 사용하였다. 이번에는 StatefulWidget이 필요하니 이번 기회에 한 번 비교 분석해 보기로 하자.
StatelessWidget
StatelessWidget은 말 그대로 ‘상태가 없는 위젯’이다. 생명 주기도 오른쪽 그림처럼 단순하다.
StatelessWidget이 빌드되면 생성자가 실행되고 이어서 필수 오버라이드 함수인 build() 함수가 실행된다. build() 함수로 반환한 위젯이 화면에 렌더링 되고 끝이다.
플러터에서 모든 위젯은 Widget 클래스를 상속하는데 이 Widget 클래스는 기본적으로 불변(Immutable)이다. 불변이란 클래스를 한 번 생성하고 나면 속성을 변경할 수 없다는 것이다.

허나 위젯의 속성을 변경해야 할 때가 있다. 예를 들자면 생성자에 새로운 인자가 입력되는 경우가 있다. 이 때, build() 함수에서 해당 인자 값을 이용하고 있다면 build() 함수를 재실행 해주어야 한다. 하지만 StatelessWidget은 불변이기에 한 번 생성된 인스턴스의 build() 함수는 재실행될 수 없다. 대신 인스턴스 자체를 아예 새로 생성한 후 기존 인스턴스를 대체해 반영해야 한다.
StatefulWidget
앞서 이야기한 위젯에서 build() 함수를 재실행해야 하는 상황이 있을 경우 StatefulWidget을 사용하면 된다.
StatefulWidget은 위젯 클래스와 상태를 관리하는 스테이트 클래스 이렇게 2개로 구성되어 있다.
StatefulWidget 생명주기

StatefulWidget 상태변경이 없을 때 생명주기
상태 변경이 없는 상태로 파괴될 때까지의 생명주기는 생성과 파괴까지 수직으로 initState()부터 dispose()까지 일자로 흐른다고 보면 된다.
StatefulWidget 생성자의 매개변수가 변경됐을 때 생명주기
위젯이 생성된 후 삭제가 되기 전 initState()부터 clean 상태까지 흘러간 상태에 있다가 매개변수가 변경되면, didUpdateWidget() 함수가 실행 된다.
그러면 State가 dirty 상태로 변경되며 build()함수가 실행 된다. build()가 실행된 후 State가 clean 상태로 변경 된다.
State 자체적으로 build()를 재실행할 때 생명주기
State가 clean 상태에서 setState() 함수가 호출되면, State가 dirty 상태로 들어가게 되고 build()함수가 실행 된다. build()가 실행된 후 역시 State가 clean 상태로 돌아가게 된다.
Timer
Timer는 특정 시간이 지난 후에 일회성 또는 반복적으로 함수를 실행합니다. 이 중 이번 예제에서는 반복적으로 함수를 실행하는 것이 필요하다.
Timer.periodic() 예제 코드
Timer.periodic(
Duration(seconds: 3), // 1.주기
(Timer timer) {} // 2. 콜백 함수
);
- 콜백 함수 실행 주기를 설정하는 Duration은 days, hours, minutes, seconds, milliseconds, microseconds까지 다양하게 지정할 수 있다.
- Timer 객체가 인자로 제공되는 콜백 함수를 지정할 수 있다.
예제 : 전자 액자 APP

PageView와 Timer를 이용하여 주기적으로 사진이 전환되는 전자 액자 APP을 만들어 보자.
이미지 파일 준비
예제 작성을 위해 먼저 전환할 사진을 준비하자. 이전 웹앱 예제에도 asset을 추가하는 법에 대해 기록 되어 있다.
프로젝트 폴더 아래에 asset 폴더를 생성하고 그 아래 Img 폴더를 생성한다. 그리고 원하는 이미지 파일을 해당 폴더에 넣는다.

나는 간단히 2개의 이미지를 사용해 보았다.
pubspec.yaml 설정
추가한 asset 경로 사용을 위해 pubspec.yaml 파일을 수정해 준다.

예제 코드와 파일 구조
파일 구조는 역시나 screen 폴더를 구분하여 작성한다.

home_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<StatefulWidget> createState() {
return _HomeScreenState();
}
}
class _HomeScreenState extends State<HomeScreen> {
final PageController pageController = PageController();
@override
void initState() {
// TODO: implement initState
super.initState();
Timer.periodic(
Duration(seconds: 3),
(timer) {
int? nextPage = pageController.page?.toInt();
if (nextPage == null) {
return;
}
if (nextPage == 1) {
nextPage = 0;
}
else {
nextPage++;
}
pageController.animateToPage(
nextPage,
duration: Duration(milliseconds: 500),
curve: Curves.ease
);
}
);
}
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light); // System UI의 그래픽 설정 변경
return Scaffold(
body: PageView(
controller: pageController,
children: [6284, 6287]
.map(
(number) => Image.asset(
'asset/Img/IMG_$number.jpeg',
fit: BoxFit.cover, // BoxFit 설정
),
).toList(),
),
);
}
}
System UI의 그래픽 설정을 변경하는 SystemChrome.setSystemUIOverlayStyle() 함수
- setEnabledSystemUIMode() : 앱의 풀스크린 모드를 지정한다. 핸드폰 상단의 시간, 배터리, 잔량이 보이지 않게 할 수 있다.
- setPreferredOrientations() : 앱을 실행하는 방향을 지정한다. 가로, 가로 좌우 반전, 세로, 세로 위아래 반전 옵션이 있다.
- setSystemUIChangeCallback() : 시스템 UI가 변경되면 콜백을 실행한다.
- setSystemUIOverlayStyle() : 시스템 UI의 색상을 변경한다.
BoxFit 속성
- BoxFit.contain : 이미지가 잘리지 않는 한 최대 크기로 늘린다.
- BoxFit.cover : 부모 위젯을 덮는 한 최소한의 크기로 조정된다.
- BoxFit.fill : 이미지 비율은 무시되며 자신이 속한 부모 위젯의 크기대로 늘어진다.
- BoxFit.fitHeight : 이미지 비율은 유지하고 자신이 속한 부모 위젯의 높이에 이미지 높이를 맞춘다.
- BoxFit.fitWidth : BoxFit.fitHeight과 같은데 방향이 가로(넓이)라고 보면 된다.
- BoxFit.none : 원본 이미지 크기와 비율대로 사용한다.
- BoxFit.scaleDown : BixFit.none을 베이스로 자신이 속한 부모 위젯이 이미지보다 작으면 이미지 크기를 줄여서 맞춘다.
main.dart
import 'package:flutter/material.dart';
import 'package:image_carousel/screen/home_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomeScreen(),
);
}
}
참고
Flutter 기본 기능 | as, show, hide | 변수나 함수, 클래스명이 같은 경우 해결 방법 | example